diff --git a/.github/workflows/alerts-ee.yaml b/.github/workflows/alerts-ee.yaml new file mode 100644 index 000000000..10482a7cb --- /dev/null +++ b/.github/workflows/alerts-ee.yaml @@ -0,0 +1,154 @@ +# This action will push the alerts changes to aws +on: + workflow_dispatch: + inputs: + skip_security_checks: + description: 'Skip Security checks if there is a unfixable vuln or error. Value: true/false' + required: false + default: 'false' + push: + branches: + - api-v1.10.0 + paths: + - "ee/api/**" + - "api/**" + - "!api/.gitignore" + - "!api/routers" + - "!api/app.py" + - "!api/*-dev.sh" + - "!api/requirements.txt" + - "!api/requirements-crons.txt" + - "!ee/api/.gitignore" + - "!ee/api/routers" + - "!ee/api/app.py" + - "!ee/api/*-dev.sh" + - "!ee/api/requirements.txt" + - "!ee/api/requirements-crons.txt" + + +name: Build and Deploy Alerts EE + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # We need to diff with old commit + # to see which workers got changed. + fetch-depth: 2 + + - name: Docker login + run: | + docker login ${{ secrets.EE_REGISTRY_URL }} -u ${{ secrets.EE_DOCKER_USERNAME }} -p "${{ secrets.EE_REGISTRY_TOKEN }}" + + - uses: azure/k8s-set-context@v1 + with: + method: kubeconfig + kubeconfig: ${{ secrets.EE_KUBECONFIG }} # Use content of kubeconfig in secret. + id: setcontext + + # Caching docker images + - uses: satackey/action-docker-layer-caching@v0.0.11 + # Ignore the failure of a step and avoid terminating the job. + continue-on-error: true + + + - name: Building and Pushing api image + id: build-image + env: + DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }}-ee + ENVIRONMENT: staging + run: | + skip_security_checks=${{ github.event.inputs.skip_security_checks }} + cd api + PUSH_IMAGE=0 bash -x ./build_alerts.sh ee + [[ "x$skip_security_checks" == "xtrue" ]] || { + curl -L https://github.com/aquasecurity/trivy/releases/download/v0.34.0/trivy_0.34.0_Linux-64bit.tar.gz | tar -xzf - -C ./ + images=("alerts") + for image in ${images[*]};do + ./trivy image --exit-code 1 --security-checks vuln --vuln-type os,library --severity "HIGH,CRITICAL" --ignore-unfixed $DOCKER_REPO/$image:$IMAGE_TAG + done + err_code=$? + [[ $err_code -ne 0 ]] && { + exit $err_code + } + } && { + echo "Skipping Security Checks" + } + images=("alerts") + for image in ${images[*]};do + docker push $DOCKER_REPO/$image:$IMAGE_TAG + done + - name: Creating old image input + run: | + # + # Create yaml with existing image tags + # + kubectl get pods -n app -o jsonpath="{.items[*].spec.containers[*].image}" |\ + tr -s '[[:space:]]' '\n' | sort | uniq -c | grep '/foss/' | cut -d '/' -f3 > /tmp/image_tag.txt + + echo > /tmp/image_override.yaml + + for line in `cat /tmp/image_tag.txt`; + do + image_array=($(echo "$line" | tr ':' '\n')) + cat <> /tmp/image_override.yaml + ${image_array[0]}: + image: + # We've to strip off the -ee, as helm will append it. + tag: `echo ${image_array[1]} | cut -d '-' -f 1` + EOF + done + + - name: Deploy to kubernetes + run: | + cd scripts/helmcharts/ + + ## Update secerts + sed -i "s#openReplayContainerRegistry.*#openReplayContainerRegistry: \"${{ secrets.OSS_REGISTRY_URL }}\"#g" vars.yaml + sed -i "s/postgresqlPassword: \"changeMePassword\"/postgresqlPassword: \"${{ secrets.EE_PG_PASSWORD }}\"/g" vars.yaml + sed -i "s/accessKey: \"changeMeMinioAccessKey\"/accessKey: \"${{ secrets.EE_MINIO_ACCESS_KEY }}\"/g" vars.yaml + sed -i "s/secretKey: \"changeMeMinioPassword\"/secretKey: \"${{ secrets.EE_MINIO_SECRET_KEY }}\"/g" vars.yaml + sed -i "s/jwt_secret: \"SetARandomStringHere\"/jwt_secret: \"${{ secrets.EE_JWT_SECRET }}\"/g" vars.yaml + sed -i "s/domainName: \"\"/domainName: \"${{ secrets.EE_DOMAIN_NAME }}\"/g" vars.yaml + sed -i "s/enterpriseEditionLicense: \"\"/enterpriseEditionLicense: \"${{ secrets.EE_LICENSE_KEY }}\"/g" vars.yaml + + # Update changed image tag + sed -i "/alerts/{n;n;n;s/.*/ tag: ${IMAGE_TAG}/}" /tmp/image_override.yaml + + cat /tmp/image_override.yaml + # Deploy command + mv openreplay/charts/{ingress-nginx,alerts,quickwit} /tmp + rm -rf openreplay/charts/* + mv /tmp/{ingress-nginx,alerts,quickwit} openreplay/charts/ + helm template openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set ingress-nginx.enabled=false --set skipMigration=true --no-hooks --kube-version=$k_version | kubectl apply -f - + env: + DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} + # We're not passing -ee flag, because helm will add that. + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} + ENVIRONMENT: staging + + - name: Alert slack + if: ${{ failure() }} + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_CHANNEL: ee + SLACK_TITLE: "Failed ${{ github.workflow }}" + SLACK_COLOR: ${{ job.status }} # or a specific color like 'good' or '#ff00ff' + SLACK_WEBHOOK: ${{ secrets.SLACK_WEB_HOOK }} + SLACK_USERNAME: "OR Bot" + SLACK_MESSAGE: 'Build failed :bomb:' + +# - name: Debug Job +# # if: ${{ failure() }} +# uses: mxschmitt/action-tmate@v3 +# env: +# DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} +# IMAGE_TAG: ${{ github.sha }}-ee +# ENVIRONMENT: staging + diff --git a/.github/workflows/alerts.yaml b/.github/workflows/alerts.yaml new file mode 100644 index 000000000..539cc5e65 --- /dev/null +++ b/.github/workflows/alerts.yaml @@ -0,0 +1,143 @@ +# This action will push the alerts changes to aws +on: + workflow_dispatch: + inputs: + skip_security_checks: + description: 'Skip Security checks if there is a unfixable vuln or error. Value: true/false' + required: false + default: 'false' + push: + branches: + - api-v1.10.0 + paths: + - "api/**" + - "!api/.gitignore" + - "!api/routers" + - "!api/app.py" + - "!api/*-dev.sh" + - "!api/requirements.txt" + - "!api/requirements-crons.txt" + +name: Build and Deploy Alerts + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # We need to diff with old commit + # to see which workers got changed. + fetch-depth: 2 + + - name: Docker login + run: | + docker login ${{ secrets.OSS_REGISTRY_URL }} -u ${{ secrets.OSS_DOCKER_USERNAME }} -p "${{ secrets.OSS_REGISTRY_TOKEN }}" + + - uses: azure/k8s-set-context@v1 + with: + method: kubeconfig + kubeconfig: ${{ secrets.OSS_KUBECONFIG }} # Use content of kubeconfig in secret. + id: setcontext + + # Caching docker images + - uses: satackey/action-docker-layer-caching@v0.0.11 + # Ignore the failure of a step and avoid terminating the job. + continue-on-error: true + + + - name: Building and Pushing Alerts image + id: build-image + env: + DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} + ENVIRONMENT: staging + run: | + skip_security_checks=${{ github.event.inputs.skip_security_checks }} + cd api + PUSH_IMAGE=0 bash -x ./build_alerts.sh + [[ "x$skip_security_checks" == "xtrue" ]] || { + curl -L https://github.com/aquasecurity/trivy/releases/download/v0.34.0/trivy_0.34.0_Linux-64bit.tar.gz | tar -xzf - -C ./ + images=("alerts") + for image in ${images[*]};do + ./trivy image --exit-code 1 --security-checks vuln --vuln-type os,library --severity "HIGH,CRITICAL" --ignore-unfixed $DOCKER_REPO/$image:$IMAGE_TAG + done + err_code=$? + [[ $err_code -ne 0 ]] && { + exit $err_code + } + } && { + echo "Skipping Security Checks" + } + images=("alerts") + for image in ${images[*]};do + docker push $DOCKER_REPO/$image:$IMAGE_TAG + done + - name: Creating old image input + run: | + # + # Create yaml with existing image tags + # + kubectl get pods -n app -o jsonpath="{.items[*].spec.containers[*].image}" |\ + tr -s '[[:space:]]' '\n' | sort | uniq -c | grep '/foss/' | cut -d '/' -f3 > /tmp/image_tag.txt + + echo > /tmp/image_override.yaml + + for line in `cat /tmp/image_tag.txt`; + do + image_array=($(echo "$line" | tr ':' '\n')) + cat <> /tmp/image_override.yaml + ${image_array[0]}: + image: + tag: ${image_array[1]} + EOF + done + + - name: Deploy to kubernetes + run: | + cd scripts/helmcharts/ + + ## Update secerts + sed -i "s#openReplayContainerRegistry.*#openReplayContainerRegistry: \"${{ secrets.OSS_REGISTRY_URL }}\"#g" vars.yaml + sed -i "s/postgresqlPassword: \"changeMePassword\"/postgresqlPassword: \"${{ secrets.OSS_PG_PASSWORD }}\"/g" vars.yaml + sed -i "s/accessKey: \"changeMeMinioAccessKey\"/accessKey: \"${{ secrets.OSS_MINIO_ACCESS_KEY }}\"/g" vars.yaml + sed -i "s/secretKey: \"changeMeMinioPassword\"/secretKey: \"${{ secrets.OSS_MINIO_SECRET_KEY }}\"/g" vars.yaml + sed -i "s/jwt_secret: \"SetARandomStringHere\"/jwt_secret: \"${{ secrets.OSS_JWT_SECRET }}\"/g" vars.yaml + sed -i "s/domainName: \"\"/domainName: \"${{ secrets.OSS_DOMAIN_NAME }}\"/g" vars.yaml + + # Update changed image tag + sed -i "/alerts/{n;n;s/.*/ tag: ${IMAGE_TAG}/}" /tmp/image_override.yaml + + cat /tmp/image_override.yaml + # Deploy command + mv openreplay/charts/{ingress-nginx,alerts,quickwit} /tmp + rm -rf openreplay/charts/* + mv /tmp/{ingress-nginx,alerts,quickwit} openreplay/charts/ + helm template openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set ingress-nginx.enabled=false --set skipMigration=true --no-hooks | kubectl apply -n app -f - + env: + DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} + ENVIRONMENT: staging + + - name: Alert slack + if: ${{ failure() }} + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_CHANNEL: foss + SLACK_TITLE: "Failed ${{ github.workflow }}" + SLACK_COLOR: ${{ job.status }} # or a specific color like 'good' or '#ff00ff' + SLACK_WEBHOOK: ${{ secrets.SLACK_WEB_HOOK }} + SLACK_USERNAME: "OR Bot" + SLACK_MESSAGE: 'Build failed :bomb:' + + # - name: Debug Job + # if: ${{ failure() }} + # uses: mxschmitt/action-tmate@v3 + # env: + # DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} + # IMAGE_TAG: ${{ github.sha }} + # ENVIRONMENT: staging + diff --git a/.github/workflows/api-ee.yaml b/.github/workflows/api-ee.yaml index 9eb23910a..b2a31f276 100644 --- a/.github/workflows/api-ee.yaml +++ b/.github/workflows/api-ee.yaml @@ -8,10 +8,20 @@ on: default: 'false' push: branches: - - dev + - api-v1.10.0 paths: - - ee/api/** - - api/** + - "ee/api/**" + - "api/**" + - "!api/.gitignore" + - "!api/app_alerts.py" + - "!api/*-dev.sh" + - "!api/requirements-*.txt" + - "!ee/api/.gitignore" + - "!ee/api/app_alerts.py" + - "!ee/api/app_crons.py" + - "!ee/api/*-dev.sh" + - "!ee/api/requirements-*.txt" + name: Build and Deploy Chalice EE @@ -44,7 +54,7 @@ jobs: continue-on-error: true - - name: Building and Pusing api image + - name: Building and Pushing api image id: build-image env: DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} @@ -56,7 +66,7 @@ jobs: PUSH_IMAGE=0 bash -x ./build.sh ee [[ "x$skip_security_checks" == "xtrue" ]] || { curl -L https://github.com/aquasecurity/trivy/releases/download/v0.34.0/trivy_0.34.0_Linux-64bit.tar.gz | tar -xzf - -C ./ - images=("chalice" "alerts") + images=("chalice") for image in ${images[*]};do ./trivy image --exit-code 1 --security-checks vuln --vuln-type os,library --severity "HIGH,CRITICAL" --ignore-unfixed $DOCKER_REPO/$image:$IMAGE_TAG done @@ -67,7 +77,10 @@ jobs: } && { echo "Skipping Security Checks" } - PUSH_IMAGE=1 bash -x ./build.sh ee + images=("chalice") + for image in ${images[*]};do + docker push $DOCKER_REPO/$image:$IMAGE_TAG + done - name: Creating old image input run: | # @@ -107,7 +120,10 @@ jobs: cat /tmp/image_override.yaml # Deploy command - helm upgrade --install openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set skipMigration=true --no-hooks + mv openreplay/charts/{ingress-nginx,chalice,quickwit} /tmp + rm -rf openreplay/charts/* + mv /tmp/{ingress-nginx,chalice,quickwit} openreplay/charts/ + helm template openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set ingress-nginx.enabled=false --set skipMigration=true --no-hooks --kube-version=$k_version | kubectl apply -f - env: DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} # We're not passing -ee flag, because helm will add that. diff --git a/.github/workflows/api.yaml b/.github/workflows/api.yaml index e4d85ff24..26d59ff87 100644 --- a/.github/workflows/api.yaml +++ b/.github/workflows/api.yaml @@ -8,9 +8,13 @@ on: default: 'false' push: branches: - - dev + - api-v1.10.0 paths: - - api/** + - "api/**" + - "!api/.gitignore" + - "!api/app_alerts.py" + - "!api/*-dev.sh" + - "!api/requirements-*.txt" name: Build and Deploy Chalice @@ -43,7 +47,7 @@ jobs: continue-on-error: true - - name: Building and Pusing api image + - name: Building and Pushing api image id: build-image env: DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} @@ -55,7 +59,7 @@ jobs: PUSH_IMAGE=0 bash -x ./build.sh [[ "x$skip_security_checks" == "xtrue" ]] || { curl -L https://github.com/aquasecurity/trivy/releases/download/v0.34.0/trivy_0.34.0_Linux-64bit.tar.gz | tar -xzf - -C ./ - images=("chalice" "alerts") + images=("chalice") for image in ${images[*]};do ./trivy image --exit-code 1 --security-checks vuln --vuln-type os,library --severity "HIGH,CRITICAL" --ignore-unfixed $DOCKER_REPO/$image:$IMAGE_TAG done @@ -66,7 +70,10 @@ jobs: } && { echo "Skipping Security Checks" } - PUSH_IMAGE=1 bash -x ./build.sh + images=("chalice") + for image in ${images[*]};do + docker push $DOCKER_REPO/$image:$IMAGE_TAG + done - name: Creating old image input run: | # @@ -131,4 +138,4 @@ jobs: # DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} # IMAGE_TAG: ${{ github.sha }} # ENVIRONMENT: staging - # + diff --git a/.github/workflows/assist-ee.yaml b/.github/workflows/assist-ee.yaml index e5b5c62e1..76dcc4a2d 100644 --- a/.github/workflows/assist-ee.yaml +++ b/.github/workflows/assist-ee.yaml @@ -117,4 +117,4 @@ jobs: # DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} # IMAGE_TAG: ${{ github.sha }} # ENVIRONMENT: staging - # \ No newline at end of file + # diff --git a/.github/workflows/assist.yaml b/.github/workflows/assist.yaml index ef93fc157..65ca0348c 100644 --- a/.github/workflows/assist.yaml +++ b/.github/workflows/assist.yaml @@ -116,4 +116,4 @@ jobs: # DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} # IMAGE_TAG: ${{ github.sha }} # ENVIRONMENT: staging - # \ No newline at end of file + # diff --git a/.github/workflows/crons-ee.yaml b/.github/workflows/crons-ee.yaml new file mode 100644 index 000000000..762dae33e --- /dev/null +++ b/.github/workflows/crons-ee.yaml @@ -0,0 +1,153 @@ +# This action will push the crons changes to aws +on: + workflow_dispatch: + inputs: + skip_security_checks: + description: 'Skip Security checks if there is a unfixable vuln or error. Value: true/false' + required: false + default: 'false' + push: + branches: + - api-v1.10.0 + paths: + - "ee/api/**" + - "api/**" + - "!api/.gitignore" + - "!api/app.py" + - "!api/app_alerts.py" + - "!api/*-dev.sh" + - "!api/requirements.txt" + - "!api/requirements-alerts.txt" + - "!ee/api/.gitignore" + - "!ee/api/app.py" + - "!ee/api/app_alerts.py" + - "!ee/api/*-dev.sh" + - "!ee/api/requirements.txt" + - "!ee/api/requirements-crons.txt" + +name: Build and Deploy Crons EE + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # We need to diff with old commit + # to see which workers got changed. + fetch-depth: 2 + + - name: Docker login + run: | + docker login ${{ secrets.EE_REGISTRY_URL }} -u ${{ secrets.EE_DOCKER_USERNAME }} -p "${{ secrets.EE_REGISTRY_TOKEN }}" + + - uses: azure/k8s-set-context@v1 + with: + method: kubeconfig + kubeconfig: ${{ secrets.EE_KUBECONFIG }} # Use content of kubeconfig in secret. + id: setcontext + + # Caching docker images + - uses: satackey/action-docker-layer-caching@v0.0.11 + # Ignore the failure of a step and avoid terminating the job. + continue-on-error: true + + + - name: Building and Pushing api image + id: build-image + env: + DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }}-ee + ENVIRONMENT: staging + run: | + skip_security_checks=${{ github.event.inputs.skip_security_checks }} + cd api + PUSH_IMAGE=0 bash -x ./build_crons.sh ee + [[ "x$skip_security_checks" == "xtrue" ]] || { + curl -L https://github.com/aquasecurity/trivy/releases/download/v0.34.0/trivy_0.34.0_Linux-64bit.tar.gz | tar -xzf - -C ./ + images=("crons") + for image in ${images[*]};do + ./trivy image --exit-code 1 --security-checks vuln --vuln-type os,library --severity "HIGH,CRITICAL" --ignore-unfixed $DOCKER_REPO/$image:$IMAGE_TAG + done + err_code=$? + [[ $err_code -ne 0 ]] && { + exit $err_code + } + } && { + echo "Skipping Security Checks" + } + images=("crons") + for image in ${images[*]};do + docker push $DOCKER_REPO/$image:$IMAGE_TAG + done + - name: Creating old image input + run: | + # + # Create yaml with existing image tags + # + kubectl get pods -n app -o jsonpath="{.items[*].spec.containers[*].image}" |\ + tr -s '[[:space:]]' '\n' | sort | uniq -c | grep '/foss/' | cut -d '/' -f3 > /tmp/image_tag.txt + + echo > /tmp/image_override.yaml + + for line in `cat /tmp/image_tag.txt`; + do + image_array=($(echo "$line" | tr ':' '\n')) + cat <> /tmp/image_override.yaml + ${image_array[0]}: + image: + # We've to strip off the -ee, as helm will append it. + tag: `echo ${image_array[1]} | cut -d '-' -f 1` + EOF + done + + - name: Deploy to kubernetes + run: | + cd scripts/helmcharts/ + + ## Update secerts + sed -i "s#openReplayContainerRegistry.*#openReplayContainerRegistry: \"${{ secrets.OSS_REGISTRY_URL }}\"#g" vars.yaml + sed -i "s/postgresqlPassword: \"changeMePassword\"/postgresqlPassword: \"${{ secrets.EE_PG_PASSWORD }}\"/g" vars.yaml + sed -i "s/accessKey: \"changeMeMinioAccessKey\"/accessKey: \"${{ secrets.EE_MINIO_ACCESS_KEY }}\"/g" vars.yaml + sed -i "s/secretKey: \"changeMeMinioPassword\"/secretKey: \"${{ secrets.EE_MINIO_SECRET_KEY }}\"/g" vars.yaml + sed -i "s/jwt_secret: \"SetARandomStringHere\"/jwt_secret: \"${{ secrets.EE_JWT_SECRET }}\"/g" vars.yaml + sed -i "s/domainName: \"\"/domainName: \"${{ secrets.EE_DOMAIN_NAME }}\"/g" vars.yaml + sed -i "s/enterpriseEditionLicense: \"\"/enterpriseEditionLicense: \"${{ secrets.EE_LICENSE_KEY }}\"/g" vars.yaml + + # Update changed image tag + sed -i "/crons/{n;n;n;s/.*/ tag: ${IMAGE_TAG}/}" /tmp/image_override.yaml + + cat /tmp/image_override.yaml + # Deploy command + mv openreplay/charts/{ingress-nginx,utilities,quickwit} /tmp + rm -rf openreplay/charts/* + mv /tmp/{ingress-nginx,utilities,quickwit} openreplay/charts/ + helm template openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set ingress-nginx.enabled=false --set skipMigration=true --no-hooks --kube-version=$k_version | kubectl apply -f - + env: + DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} + # We're not passing -ee flag, because helm will add that. + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} + ENVIRONMENT: staging + + - name: Alert slack + if: ${{ failure() }} + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_CHANNEL: ee + SLACK_TITLE: "Failed ${{ github.workflow }}" + SLACK_COLOR: ${{ job.status }} # or a specific color like 'good' or '#ff00ff' + SLACK_WEBHOOK: ${{ secrets.SLACK_WEB_HOOK }} + SLACK_USERNAME: "OR Bot" + SLACK_MESSAGE: 'Build failed :bomb:' + + # - name: Debug Job + # if: ${{ failure() }} + # uses: mxschmitt/action-tmate@v3 + # env: + # DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} + # IMAGE_TAG: ${{ github.sha }}-ee + # ENVIRONMENT: staging + # diff --git a/.github/workflows/frontend-dev.yaml b/.github/workflows/frontend-dev.yaml index 5e957c8da..fa7137d1c 100644 --- a/.github/workflows/frontend-dev.yaml +++ b/.github/workflows/frontend-dev.yaml @@ -20,9 +20,11 @@ jobs: restore-keys: | ${{ runner.OS }}-build- ${{ runner.OS }}- + - name: Docker login run: | docker login ${{ secrets.OSS_REGISTRY_URL }} -u ${{ secrets.OSS_DOCKER_USERNAME }} -p "${{ secrets.OSS_REGISTRY_TOKEN }}" + - uses: azure/k8s-set-context@v1 with: method: kubeconfig @@ -45,15 +47,18 @@ jobs: docker tag $DOCKER_REPO/frontend:${IMAGE_TAG} $DOCKER_REPO/frontend:${IMAGE_TAG}-ee docker push $DOCKER_REPO/frontend:${IMAGE_TAG} docker push $DOCKER_REPO/frontend:${IMAGE_TAG}-ee + - name: Deploy to kubernetes foss run: | cd scripts/helmcharts/ + set -x cat <>/tmp/image_override.yaml frontend: image: tag: ${IMAGE_TAG} EOF + ## Update secerts sed -i "s#openReplayContainerRegistry.*#openReplayContainerRegistry: \"${{ secrets.OSS_REGISTRY_URL }}\"#g" vars.yaml sed -i "s/postgresqlPassword: \"changeMePassword\"/postgresqlPassword: \"${{ secrets.DEV_PG_PASSWORD }}\"/g" vars.yaml @@ -61,8 +66,10 @@ jobs: sed -i "s/secretKey: \"changeMeMinioPassword\"/secretKey: \"${{ secrets.DEV_MINIO_SECRET_KEY }}\"/g" vars.yaml sed -i "s/jwt_secret: \"SetARandomStringHere\"/jwt_secret: \"${{ secrets.DEV_JWT_SECRET }}\"/g" vars.yaml sed -i "s/domainName: \"\"/domainName: \"${{ secrets.DEV_DOMAIN_NAME }}\"/g" vars.yaml + # Update changed image tag sed -i "/frontend/{n;n;s/.*/ tag: ${IMAGE_TAG}/}" /tmp/image_override.yaml + cat /tmp/image_override.yaml # Deploy command mv openreplay/charts/{ingress-nginx,frontend,quickwit} /tmp diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index 2f2fd3989..db90235ee 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -1,4 +1,4 @@ -name: Frontend FOSS Deployment +name: Frontend Foss Deployment on: workflow_dispatch: push: diff --git a/.github/workflows/peers-ee.yaml b/.github/workflows/peers-ee.yaml new file mode 100644 index 000000000..5db7436da --- /dev/null +++ b/.github/workflows/peers-ee.yaml @@ -0,0 +1,69 @@ +# This action will push the peers changes to aws +on: + workflow_dispatch: + push: + branches: + - dev + paths: + - "ee/peers/**" + - "peers/**" + - "!peers/.gitignore" + - "!peers/*-dev.sh" + +name: Build and Deploy Peers + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # We need to diff with old commit + # to see which workers got changed. + fetch-depth: 2 + + - name: Docker login + run: | + docker login ${{ secrets.EE_REGISTRY_URL }} -u ${{ secrets.EE_DOCKER_USERNAME }} -p "${{ secrets.EE_REGISTRY_TOKEN }}" + + - uses: azure/k8s-set-context@v1 + with: + method: kubeconfig + kubeconfig: ${{ secrets.EE_KUBECONFIG }} # Use content of kubeconfig in secret. + id: setcontext + + - name: Building and Pushing api image + id: build-image + env: + DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} + ENVIRONMENT: staging + run: | + cd peers + PUSH_IMAGE=1 bash build.sh ee + - name: Deploy to kubernetes + run: | + cd scripts/helmcharts/ + sed -i "s#openReplayContainerRegistry.*#openReplayContainerRegistry: \"${{ secrets.EE_REGISTRY_URL }}\"#g" vars.yaml + sed -i "s#minio_access_key.*#minio_access_key: \"${{ secrets.EE_MINIO_ACCESS_KEY }}\" #g" vars.yaml + sed -i "s#minio_secret_key.*#minio_secret_key: \"${{ secrets.EE_MINIO_SECRET_KEY }}\" #g" vars.yaml + sed -i "s#domain_name.*#domain_name: \"ee.openreplay.com\" #g" vars.yaml + sed -i "s#kubeconfig.*#kubeconfig_path: ${KUBECONFIG}#g" vars.yaml + sed -i "s/image_tag:.*/image_tag: \"$IMAGE_TAG\"/g" vars.yaml + bash kube-install.sh --app peers + env: + DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} + ENVIRONMENT: staging + + # - name: Debug Job + # if: ${{ failure() }} + # uses: mxschmitt/action-tmate@v3 + # env: + # DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} + # IMAGE_TAG: ${{ github.sha }} + # ENVIRONMENT: staging + # diff --git a/.github/workflows/peers.yaml b/.github/workflows/peers.yaml new file mode 100644 index 000000000..7b2a715d8 --- /dev/null +++ b/.github/workflows/peers.yaml @@ -0,0 +1,68 @@ +# This action will push the peers changes to aws +on: + workflow_dispatch: + push: + branches: + - dev + paths: + - "peers/**" + - "!peers/.gitignore" + - "!peers/*-dev.sh" + +name: Build and Deploy Peers + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # We need to diff with old commit + # to see which workers got changed. + fetch-depth: 2 + + - name: Docker login + run: | + docker login ${{ secrets.OSS_REGISTRY_URL }} -u ${{ secrets.OSS_DOCKER_USERNAME }} -p "${{ secrets.OSS_REGISTRY_TOKEN }}" + + - uses: azure/k8s-set-context@v1 + with: + method: kubeconfig + kubeconfig: ${{ secrets.OSS_KUBECONFIG }} # Use content of kubeconfig in secret. + id: setcontext + + - name: Building and Pushing api image + id: build-image + env: + DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} + ENVIRONMENT: staging + run: | + cd peers + PUSH_IMAGE=1 bash build.sh + - name: Deploy to kubernetes + run: | + cd scripts/helmcharts/ + sed -i "s#openReplayContainerRegistry.*#openReplayContainerRegistry: \"${{ secrets.OSS_REGISTRY_URL }}\"#g" vars.yaml + sed -i "s#minio_access_key.*#minio_access_key: \"${{ secrets.OSS_MINIO_ACCESS_KEY }}\" #g" vars.yaml + sed -i "s#minio_secret_key.*#minio_secret_key: \"${{ secrets.OSS_MINIO_SECRET_KEY }}\" #g" vars.yaml + sed -i "s#domain_name.*#domain_name: \"foss.openreplay.com\" #g" vars.yaml + sed -i "s#kubeconfig.*#kubeconfig_path: ${KUBECONFIG}#g" vars.yaml + sed -i "s/image_tag:.*/image_tag: \"$IMAGE_TAG\"/g" vars.yaml + bash kube-install.sh --app peers + env: + DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} + ENVIRONMENT: staging + + # - name: Debug Job + # if: ${{ failure() }} + # uses: mxschmitt/action-tmate@v3 + # env: + # DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} + # IMAGE_TAG: ${{ github.sha }} + # ENVIRONMENT: staging + # diff --git a/.github/workflows/sourcemaps-reader.yaml b/.github/workflows/sourcemaps-reader.yaml index 547295425..095a70784 100644 --- a/.github/workflows/sourcemaps-reader.yaml +++ b/.github/workflows/sourcemaps-reader.yaml @@ -5,9 +5,11 @@ on: branches: - dev paths: - - sourcemap-reader/** + - "sourcemap-reader/**" + - "!sourcemap-reader/.gitignore" + - "!sourcemap-reader/*-dev.sh" -name: Build and Deploy Chalice +name: Build and Deploy sourcemap-reader jobs: deploy: @@ -38,7 +40,7 @@ jobs: continue-on-error: true - - name: Building and Pusing sourcemaps-reader image + - name: Building and Pushing sourcemaps-reader image id: build-image env: DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} diff --git a/.github/workflows/ui-tests.js.yml b/.github/workflows/ui-tests.js.yml index 641a3ec88..dbc6d377e 100644 --- a/.github/workflows/ui-tests.js.yml +++ b/.github/workflows/ui-tests.js.yml @@ -7,34 +7,131 @@ on: branches: [ "main" ] paths: - frontend/** + - tracker/** pull_request: branches: [ "dev", "main" ] paths: - frontend/** + - tracker/** +env: + API: ${{ secrets.E2E_API_ORIGIN }} + ASSETS: ${{ secrets.E2E_ASSETS_ORIGIN }} + APIEDP: ${{ secrets.E2E_EDP_ORIGIN }} + CY_ACC: ${{ secrets.CYPRESS_ACCOUNT }} + CY_PASS: ${{ secrets.CYPRESS_PASSWORD }} + FOSS_PROJECT_KEY: ${{ secrets.FOSS_PROJECT_KEY }} + FOSS_INGEST: ${{ secrets.FOSS_INGEST }} jobs: - build: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./frontend + build-and-test: + runs-on: macos-latest + name: Build and test Tracker plus Replayer strategy: matrix: - node-version: [16.x] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ - + node-version: [ 16.x ] steps: - - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - - name: Setup packages - run: npm i --legacy-peer-deps --ignore-scripts - - name: Run unit tests - run: npm test --if-resent - - name: Run Frontend - run: npm start - - name: Wait for frontend - run: npx wait-on http://0.0.0.0:3333 - - name: Run visual tests - run: npm run cy:test --if-present + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - name: Cache tracker modules + uses: actions/cache@v1 + with: + path: tracker/tracker/node_modules + key: ${{ runner.OS }}-test_tracker_build-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + test_tracker_build{{ runner.OS }}-build- + test_tracker_build{{ runner.OS }}- + - name: Setup Testing packages + run: | + cd tracker/tracker + npm i -g yarn + yarn + - name: Jest tests + run: | + cd tracker/tracker + yarn test + - name: Build tracker inst + run: | + cd tracker/tracker + yarn build + - name: Setup Testing UI Env + run: | + cd tracker/tracker-testing-playground + echo "REACT_APP_KEY=$FOSS_PROJECT_KEY" >> .env + echo "REACT_APP_INGEST=$FOSS_INGEST" >> .env + - name: Setup Testing packages + run: | + cd tracker/tracker-testing-playground + yarn + - name: Wait for Testing Frontend + run: | + cd tracker/tracker-testing-playground + yarn start &> ui.log & + npx wait-on http://localhost:3000 + cd ../../frontend + - name: Cache node modules + uses: actions/cache@v1 + with: + path: frontend/node_modules + key: ${{ runner.OS }}-build-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.OS }}-build- + ${{ runner.OS }}- + - name: Setup env + run: | + cd frontend + echo "NODE_ENV=development" >> .env + echo "SOURCEMAP=true" >> .env + echo "ORIGIN=$API" >> .env + echo "ASSETS_HOST=$ASSETS" >> .env + echo "API_EDP=$APIEDP" >> .env + echo "SENTRY_ENABLED = false" >> .env + echo "SENTRY_URL = ''" >> .env + echo "CAPTCHA_ENABLED = false" >> .env + echo "CAPTCHA_SITE_KEY = 'asdad'" >> .env + echo "MINIO_ENDPOINT = ''" >> .env + echo "MINIO_PORT = ''" >> .env + echo "MINIO_USE_SSL = ''" >> .env + echo "MINIO_ACCESS_KEY = ''" >> .env + echo "MINIO_SECRET_KEY = ''" >> .env + echo "VERSION = '1.9.0'" >> .env + echo "TRACKER_VERSION = '4.0.0'" >> .env + echo "COMMIT_HASH = 'dev'" >> .env + echo "{ \"account\": \"$CY_ACC\", \"password\": \"$CY_PASS\" }" >> cypress.env.json + + - name: Setup packages + run: | + cd frontend + yarn + - name: Run unit tests + run: | + cd frontend + yarn test + - name: Run Frontend + run: | + cd frontend + yarn start &> frontend.log & + - name: Wait for frontend + run: | + cd frontend + npx wait-on http://0.0.0.0:3333 + - name: (Chrome) Run visual tests + run: | + cd frontend + yarn cy:test +# firefox have different viewport somehow +# - name: (Firefox) Run visual tests +# run: yarn cy:test-firefox +# - name: (Edge) Run visual tests +# run: yarn cy:test-edge + - name: Upload Debug + if: ${{ failure() }} + uses: actions/upload-artifact@v3 + with: + name: 'Snapshots' + path: | + frontend/cypress/videos + frontend/cypress/snapshots/replayer.cy.ts + frontend/cypress/screenshots + frontend/cypress/snapshots/generalStability.cy.ts \ No newline at end of file diff --git a/.github/workflows/workers-ee.yaml b/.github/workflows/workers-ee.yaml index c573dff26..b75d22093 100644 --- a/.github/workflows/workers-ee.yaml +++ b/.github/workflows/workers-ee.yaml @@ -71,12 +71,10 @@ jobs: case ${build_param} in false) { - git diff --name-only HEAD HEAD~1 | grep -E "backend/cmd|backend/services" | grep -vE ^ee/ | cut -d '/' -f3 - git diff --name-only HEAD HEAD~1 | grep -E "backend/pkg|backend/internal" | grep -vE ^ee/ | cut -d '/' -f3 | uniq | while read -r pkg_name ; do grep -rl "pkg/$pkg_name" backend/services backend/cmd | cut -d '/' -f3 done - } | uniq > /tmp/images_to_build.txt + } | awk '!seen[$0]++' > /tmp/images_to_build.txt ;; all) ls backend/cmd > /tmp/images_to_build.txt @@ -95,6 +93,7 @@ jobs: # Pushing image to registry # cd backend + cat /tmp/images_to_build.txt for image in $(cat /tmp/images_to_build.txt); do echo "Bulding $image" @@ -109,7 +108,7 @@ jobs: } && { echo "Skipping Security Checks" } - PUSH_IMAGE=1 bash -x ./build.sh ee $image + docker push $DOCKER_REPO/$image:$IMAGE_TAG echo "::set-output name=image::$DOCKER_REPO/$image:$IMAGE_TAG" done @@ -156,22 +155,19 @@ jobs: mv /tmp/helmcharts/* openreplay/charts/ ls openreplay/charts - cat /tmp/image_override.yaml - # Deploy command helm template openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set ingress-nginx.enabled=false --set skipMigration=true | kubectl apply -f - - #- name: Alert slack - # if: ${{ failure() }} - # uses: rtCamp/action-slack-notify@v2 - # env: - # SLACK_CHANNEL: ee - # SLACK_TITLE: "Failed ${{ github.workflow }}" - # SLACK_COLOR: ${{ job.status }} # or a specific color like 'good' or '#ff00ff' - # SLACK_WEBHOOK: ${{ secrets.SLACK_WEB_HOOK }} - # SLACK_USERNAME: "OR Bot" - # SLACK_MESSAGE: 'Build failed :bomb:' - + - name: Alert slack + if: ${{ failure() }} + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_CHANNEL: ee + SLACK_TITLE: "Failed ${{ github.workflow }}" + SLACK_COLOR: ${{ job.status }} # or a specific color like 'good' or '#ff00ff' + SLACK_WEBHOOK: ${{ secrets.SLACK_WEB_HOOK }} + SLACK_USERNAME: "OR Bot" + SLACK_MESSAGE: 'Build failed :bomb:' # - name: Debug Job # if: ${{ failure() }} diff --git a/.github/workflows/workers.yaml b/.github/workflows/workers.yaml index e222e00fb..0d9927df9 100644 --- a/.github/workflows/workers.yaml +++ b/.github/workflows/workers.yaml @@ -71,12 +71,10 @@ jobs: case ${build_param} in false) { - git diff --name-only HEAD HEAD~1 | grep -E "backend/cmd|backend/services" | grep -vE ^ee/ | cut -d '/' -f3 - git diff --name-only HEAD HEAD~1 | grep -E "backend/pkg|backend/internal" | grep -vE ^ee/ | cut -d '/' -f3 | uniq | while read -r pkg_name ; do grep -rl "pkg/$pkg_name" backend/services backend/cmd | cut -d '/' -f3 done - } | uniq > /tmp/images_to_build.txt + } | awk '!seen[$0]++' > /tmp/images_to_build.txt ;; all) ls backend/cmd > /tmp/images_to_build.txt @@ -95,6 +93,7 @@ jobs: # Pushing image to registry # cd backend + cat /tmp/images_to_build.txt for image in $(cat /tmp/images_to_build.txt); do echo "Bulding $image" @@ -109,7 +108,7 @@ jobs: } && { echo "Skipping Security Checks" } - PUSH_IMAGE=1 bash -x ./build.sh skip $image + docker push $DOCKER_REPO/$image:$IMAGE_TAG echo "::set-output name=image::$DOCKER_REPO/$image:$IMAGE_TAG" done @@ -154,8 +153,6 @@ jobs: mv /tmp/helmcharts/* openreplay/charts/ ls openreplay/charts - cat /tmp/image_override.yaml - # Deploy command helm template openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set ingress-nginx.enabled=false --set skipMigration=true | kubectl apply -f - diff --git a/.gitignore b/.gitignore index ee79ca544..5eeb1c83b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ public node_modules *DS_Store *.env -.idea \ No newline at end of file +*.log +**/*.envrc +.idea diff --git a/README.md b/README.md index 363c64d1c..05608a3c1 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ OpenReplay is a session replay suite you can host yourself, that lets you see what users do on your web app, helping you troubleshoot issues faster. It's the only open-source alternative to products such as FullStory and LogRocket. - **Session replay.** OpenReplay replays what users do, but not only. It also shows you what went under the hood, how your website or app behaves by capturing network activity, console logs, JS errors, store actions/state, page speed metrics, cpu/memory usage and much more. -- **Low footprint**. With a ~18KB (.gz) tracker that asynchronously sends minimal data for a very limited impact on performance. +- **Low footprint**. With a ~19KB (.gz) tracker that asynchronously sends minimal data for a very limited impact on performance. - **Self-hosted**. No more security compliance checks, 3rd-parties processing user data. Everything OpenReplay captures stays in your cloud for a complete control over your data. - **Privacy controls**. Fine-grained security features for sanitizing user data. - **Easy deploy**. With support of major public cloud providers (AWS, GCP, Azure, DigitalOcean). diff --git a/api/.gitignore b/api/.gitignore index 773f59916..5a72abd47 100644 --- a/api/.gitignore +++ b/api/.gitignore @@ -84,6 +84,7 @@ wheels/ *.egg MANIFEST Pipfile +Pipfile.lock # PyInstaller # Usually these files are written by a python script from a template @@ -175,5 +176,4 @@ SUBNETS.json ./chalicelib/.configs README/* -.local -build_crons.sh \ No newline at end of file +.local \ No newline at end of file diff --git a/api/.trivyignore b/api/.trivyignore new file mode 100644 index 000000000..02f167862 --- /dev/null +++ b/api/.trivyignore @@ -0,0 +1,3 @@ +# Accept the risk until +# python setup tools recently fixed. Not yet avaialable in distros. +CVE-2022-40897 exp:2023-02-01 diff --git a/api/Dockerfile b/api/Dockerfile index 036dcb0f2..8c2be6a22 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,6 +1,9 @@ -FROM python:3.10-alpine +FROM python:3.11-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" +ARG GIT_SHA +LABEL GIT_SHA=$GIT_SHA + RUN apk add --no-cache build-base tini ARG envarg # Add Tini @@ -9,7 +12,8 @@ ENV SOURCE_MAP_VERSION=0.7.4 \ APP_NAME=chalice \ LISTEN_PORT=8000 \ PRIVATE_ENDPOINTS=false \ - ENTERPRISE_BUILD=${envarg} + ENTERPRISE_BUILD=${envarg} \ + GIT_SHA=$GIT_SHA WORKDIR /work COPY requirements.txt ./requirements.txt diff --git a/api/Dockerfile_alerts b/api/Dockerfile_alerts index 881b21fb9..495d1969f 100644 --- a/api/Dockerfile_alerts +++ b/api/Dockerfile_alerts @@ -1,6 +1,9 @@ -FROM python:3.10-alpine +FROM python:3.11-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" +ARG GIT_SHA +LABEL GIT_SHA=$GIT_SHA + RUN apk add --no-cache build-base tini ARG envarg ENV APP_NAME=alerts \ @@ -8,6 +11,7 @@ ENV APP_NAME=alerts \ PG_MAXCONN=10 \ LISTEN_PORT=8000 \ PRIVATE_ENDPOINTS=true \ + GIT_SHA=$GIT_SHA \ ENTERPRISE_BUILD=${envarg} WORKDIR /work diff --git a/api/Dockerfile_bundle b/api/Dockerfile_bundle deleted file mode 100644 index b047f6d6c..000000000 --- a/api/Dockerfile_bundle +++ /dev/null @@ -1,29 +0,0 @@ -FROM python:3.9.12-slim -LABEL Maintainer="Rajesh Rajendran" -WORKDIR /work -COPY . . -COPY ../utilities ./utilities -RUN rm entrypoint.sh && rm .chalice/config.json -RUN mv entrypoint.bundle.sh entrypoint.sh && mv .chalice/config.bundle.json .chalice/config.json -RUN pip install -r requirements.txt -t ./vendor --upgrade -RUN pip install chalice==1.22.2 -# Installing Nodejs -RUN apt update && apt install -y curl && \ - curl -fsSL https://deb.nodesource.com/setup_12.x | bash - && \ - apt install -y nodejs && \ - apt remove --purge -y curl && \ - rm -rf /var/lib/apt/lists/* && \ - cd utilities && \ - npm install - -# Add Tini -# Startup daemon -ENV TINI_VERSION v0.19.0 -ARG envarg -ENV ENTERPRISE_BUILD ${envarg} -ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini -RUN chmod +x /tini -RUN adduser -u 1001 openreplay -D -USER 1001 -ENTRYPOINT ["/tini", "--"] -CMD ./entrypoint.sh diff --git a/api/app.py b/api/app.py index 26342484c..43c3b7945 100644 --- a/api/app.py +++ b/api/app.py @@ -12,7 +12,7 @@ from chalicelib.utils import pg_client from routers import core, core_dynamic from routers.crons import core_crons from routers.crons import core_dynamic_crons -from routers.subs import dashboard, insights, metrics, v1_api +from routers.subs import insights, metrics, v1_api app = FastAPI(root_path="/api", docs_url=config("docs_url", default=""), redoc_url=config("redoc_url", default="")) app.add_middleware(GZipMiddleware, minimum_size=1000) @@ -48,7 +48,6 @@ app.include_router(core.app_apikey) app.include_router(core_dynamic.public_app) app.include_router(core_dynamic.app) app.include_router(core_dynamic.app_apikey) -app.include_router(dashboard.app) app.include_router(metrics.app) app.include_router(insights.app) app.include_router(v1_api.app_apikey) diff --git a/api/app_alerts.py b/api/app_alerts.py index 7107423de..111bad2a1 100644 --- a/api/app_alerts.py +++ b/api/app_alerts.py @@ -53,3 +53,10 @@ async def stop_server(): await shutdown() import os, signal os.kill(1, signal.SIGTERM) + + +if config("LOCAL_DEV", default=False, cast=bool): + @app.get('/private/trigger', tags=["private"]) + async def trigger_main_cron(): + logging.info("Triggering main cron") + alerts_processor.process() diff --git a/api/build.sh b/api/build.sh index 895f9bb8e..bd4c35861 100644 --- a/api/build.sh +++ b/api/build.sh @@ -16,7 +16,8 @@ exit_err() { } environment=$1 -git_sha1=${IMAGE_TAG:-$(git rev-parse HEAD)} +git_sha=$(git rev-parse --short HEAD) +image_tag=${IMAGE_TAG:-git_sha} envarg="default-foss" check_prereq() { which docker || { @@ -41,25 +42,28 @@ function build_api(){ tag="ee-" } mv Dockerfile.dockerignore .dockerignore - docker build -f ./Dockerfile --build-arg envarg=$envarg -t ${DOCKER_REPO:-'local'}/chalice:${git_sha1} . + docker build -f ./Dockerfile --build-arg envarg=$envarg --build-arg GIT_SHA=$git_sha -t ${DOCKER_REPO:-'local'}/chalice:${image_tag} . cd ../api rm -rf ../${destination} [[ $PUSH_IMAGE -eq 1 ]] && { - docker push ${DOCKER_REPO:-'local'}/chalice:${git_sha1} - docker tag ${DOCKER_REPO:-'local'}/chalice:${git_sha1} ${DOCKER_REPO:-'local'}/chalice:${tag}latest + docker push ${DOCKER_REPO:-'local'}/chalice:${image_tag} + docker tag ${DOCKER_REPO:-'local'}/chalice:${image_tag} ${DOCKER_REPO:-'local'}/chalice:${tag}latest docker push ${DOCKER_REPO:-'local'}/chalice:${tag}latest } + [[ $SIGN_IMAGE -eq 1 ]] && { + cosign sign --key $SIGN_KEY ${DOCKER_REPO:-'local'}/chalice:${image_tag} + } echo "api docker build completed" } check_prereq build_api $environment echo buil_complete -IMAGE_TAG=$IMAGE_TAG PUSH_IMAGE=$PUSH_IMAGE DOCKER_REPO=$DOCKER_REPO bash build_alerts.sh $1 - -[[ $environment == "ee" ]] && { - cp ../ee/api/build_crons.sh . - IMAGE_TAG=$IMAGE_TAG PUSH_IMAGE=$PUSH_IMAGE DOCKER_REPO=$DOCKER_REPO bash build_crons.sh $1 - exit_err $? - rm build_crons.sh -} || true +#IMAGE_TAG=$IMAGE_TAG PUSH_IMAGE=$PUSH_IMAGE DOCKER_REPO=$DOCKER_REPO SIGN_IMAGE=$SIGN_IMAGE SIGN_KEY=$SIGN_KEY bash build_alerts.sh $1 +# +#[[ $environment == "ee" ]] && { +# cp ../ee/api/build_crons.sh . +# IMAGE_TAG=$IMAGE_TAG PUSH_IMAGE=$PUSH_IMAGE DOCKER_REPO=$DOCKER_REPO SIGN_IMAGE=$SIGN_IMAGE SIGN_KEY=$SIGN_KEY bash build_crons.sh $1 +# exit_err $? +# rm build_crons.sh +#} || true diff --git a/api/build_alerts.sh b/api/build_alerts.sh index a36472a8d..b3c738b99 100644 --- a/api/build_alerts.sh +++ b/api/build_alerts.sh @@ -7,7 +7,8 @@ # Usage: IMAGE_TAG=latest DOCKER_REPO=myDockerHubID bash build.sh -git_sha1=${IMAGE_TAG:-$(git rev-parse HEAD)} +git_sha=$(git rev-parse --short HEAD) +image_tag=${IMAGE_TAG:-git_sha} envarg="default-foss" check_prereq() { which docker || { @@ -31,14 +32,17 @@ function build_alerts(){ tag="ee-" } mv Dockerfile_alerts.dockerignore .dockerignore - docker build -f ./Dockerfile_alerts --build-arg envarg=$envarg -t ${DOCKER_REPO:-'local'}/alerts:${git_sha1} . + docker build -f ./Dockerfile_alerts --build-arg envarg=$envarg --build-arg GIT_SHA=$git_sha -t ${DOCKER_REPO:-'local'}/alerts:${image_tag} . cd ../api rm -rf ../${destination} [[ $PUSH_IMAGE -eq 1 ]] && { - docker push ${DOCKER_REPO:-'local'}/alerts:${git_sha1} - docker tag ${DOCKER_REPO:-'local'}/alerts:${git_sha1} ${DOCKER_REPO:-'local'}/alerts:${tag}latest + docker push ${DOCKER_REPO:-'local'}/alerts:${image_tag} + docker tag ${DOCKER_REPO:-'local'}/alerts:${image_tag} ${DOCKER_REPO:-'local'}/alerts:${tag}latest docker push ${DOCKER_REPO:-'local'}/alerts:${tag}latest } + [[ $SIGN_IMAGE -eq 1 ]] && { + cosign sign --key $SIGN_KEY ${DOCKER_REPO:-'local'}/alerts:${image_tag} + } echo "completed alerts build" } diff --git a/ee/api/build_crons.sh b/api/build_crons.sh similarity index 87% rename from ee/api/build_crons.sh rename to api/build_crons.sh index 2f9d84a81..5e09696d4 100644 --- a/ee/api/build_crons.sh +++ b/api/build_crons.sh @@ -36,8 +36,13 @@ function build_crons(){ docker tag ${DOCKER_REPO:-'local'}/crons:${git_sha1} ${DOCKER_REPO:-'local'}/crons:${tag}latest docker push ${DOCKER_REPO:-'local'}/crons:${tag}latest } + [[ $SIGN_IMAGE -eq 1 ]] && { + cosign sign --key $SIGN_KEY ${DOCKER_REPO:-'local'}/crons:${git_sha1} + } echo "completed crons build" } check_prereq -build_crons $1 +[[ $1 == "ee" ]] && { + build_crons $1 +} diff --git a/api/chalicelib/core/__init__.py b/api/chalicelib/core/__init__.py index e69de29bb..f5667e079 100644 --- a/api/chalicelib/core/__init__.py +++ b/api/chalicelib/core/__init__.py @@ -0,0 +1 @@ +from . import sessions as sessions_legacy \ No newline at end of file diff --git a/api/chalicelib/core/alerts.py b/api/chalicelib/core/alerts.py index da6211687..3c8b00c54 100644 --- a/api/chalicelib/core/alerts.py +++ b/api/chalicelib/core/alerts.py @@ -1,9 +1,14 @@ import json import logging import time +from datetime import datetime + +from decouple import config import schemas -from chalicelib.core import notifications, slack, webhook +from chalicelib.core import notifications, webhook +from chalicelib.core.collaboration_msteams import MSTeams +from chalicelib.core.collaboration_slack import Slack from chalicelib.utils import pg_client, helper, email_helper from chalicelib.utils.TimeUTC import TimeUTC @@ -24,10 +29,15 @@ def get(id): def get_all(project_id): with pg_client.PostgresClient() as cur: query = cur.mogrify("""\ - SELECT * - FROM public.alerts - WHERE project_id =%(project_id)s AND deleted_at ISNULL - ORDER BY created_at;""", + SELECT alerts.*, + COALESCE(metrics.name || '.' || (COALESCE(metric_series.name, 'series ' || index)) || '.count', + query ->> 'left') AS series_name + FROM public.alerts + LEFT JOIN metric_series USING (series_id) + LEFT JOIN metrics USING (metric_id) + WHERE alerts.project_id =%(project_id)s + AND alerts.deleted_at ISNULL + ORDER BY alerts.created_at;""", {"project_id": project_id}) cur.execute(query=query) all = helper.list_to_camel_case(cur.fetchall()) @@ -95,7 +105,7 @@ def process_notifications(data): for c in n["options"].pop("message"): if c["type"] not in full: full[c["type"]] = [] - if c["type"] in ["slack", "email"]: + if c["type"] in ["slack", "msteams", "email"]: full[c["type"]].append({ "notification": n, "destination": c["value"] @@ -107,13 +117,21 @@ def process_notifications(data): for t in full.keys(): for i in range(0, len(full[t]), BATCH_SIZE): notifications_list = full[t][i:i + BATCH_SIZE] + if notifications_list is None or len(notifications_list) == 0: + break if t == "slack": try: - slack.send_batch(notifications_list=notifications_list) + send_to_slack_batch(notifications_list=notifications_list) except Exception as e: logging.error("!!!Error while sending slack notifications batch") logging.error(str(e)) + elif t == "msteams": + try: + send_to_msteams_batch(notifications_list=notifications_list) + except Exception as e: + logging.error("!!!Error while sending msteams notifications batch") + logging.error(str(e)) elif t == "email": try: send_by_email_batch(notifications_list=notifications_list) @@ -149,16 +167,60 @@ def send_by_email_batch(notifications_list): time.sleep(1) +def send_to_slack_batch(notifications_list): + webhookId_map = {} + for n in notifications_list: + if n.get("destination") not in webhookId_map: + webhookId_map[n.get("destination")] = {"tenantId": n["notification"]["tenantId"], "batch": []} + webhookId_map[n.get("destination")]["batch"].append({"text": n["notification"]["description"] \ + + f"\n<{config('SITE_URL')}{n['notification']['buttonUrl']}|{n['notification']['buttonText']}>", + "title": n["notification"]["title"], + "title_link": n["notification"]["buttonUrl"], + "ts": datetime.now().timestamp()}) + for batch in webhookId_map.keys(): + Slack.send_batch(tenant_id=webhookId_map[batch]["tenantId"], webhook_id=batch, + attachments=webhookId_map[batch]["batch"]) + + +def send_to_msteams_batch(notifications_list): + webhookId_map = {} + for n in notifications_list: + if n.get("destination") not in webhookId_map: + webhookId_map[n.get("destination")] = {"tenantId": n["notification"]["tenantId"], "batch": []} + + link = f"[{n['notification']['buttonText']}]({config('SITE_URL')}{n['notification']['buttonUrl']})" + webhookId_map[n.get("destination")]["batch"].append({"type": "ColumnSet", + "style": "emphasis", + "separator": True, + "bleed": True, + "columns": [{ + "width": "stretch", + "items": [ + {"type": "TextBlock", + "text": n["notification"]["title"], + "style": "heading", + "size": "Large"}, + {"type": "TextBlock", + "spacing": "small", + "text": n["notification"]["description"], + "wrap": True}, + {"type": "TextBlock", + "spacing": "small", + "text": link} + ] + }]}) + for batch in webhookId_map.keys(): + MSTeams.send_batch(tenant_id=webhookId_map[batch]["tenantId"], webhook_id=batch, + attachments=webhookId_map[batch]["batch"]) + + def delete(project_id, alert_id): with pg_client.PostgresClient() as cur: cur.execute( - cur.mogrify("""\ - UPDATE public.alerts - SET - deleted_at = timezone('utc'::text, now()), - active = FALSE - WHERE - alert_id = %(alert_id)s AND project_id=%(project_id)s;""", + cur.mogrify(""" UPDATE public.alerts + SET deleted_at = timezone('utc'::text, now()), + active = FALSE + WHERE alert_id = %(alert_id)s AND project_id=%(project_id)s;""", {"alert_id": alert_id, "project_id": project_id}) ) return {"data": {"state": "success"}} diff --git a/api/chalicelib/core/alerts_listener.py b/api/chalicelib/core/alerts_listener.py index 0fa193964..1a0a75a28 100644 --- a/api/chalicelib/core/alerts_listener.py +++ b/api/chalicelib/core/alerts_listener.py @@ -5,7 +5,7 @@ def get_all_alerts(): with pg_client.PostgresClient(long_query=True) as cur: query = """SELECT -1 AS tenant_id, alert_id, - project_id, + projects.project_id, detection_method, query, options, @@ -13,10 +13,13 @@ def get_all_alerts(): alerts.name, alerts.series_id, filter, - change + change, + COALESCE(metrics.name || '.' || (COALESCE(metric_series.name, 'series ' || index)) || '.count', + query ->> 'left') AS series_name FROM public.alerts - LEFT JOIN metric_series USING (series_id) INNER JOIN projects USING (project_id) + LEFT JOIN metric_series USING (series_id) + LEFT JOIN metrics USING (metric_id) WHERE alerts.deleted_at ISNULL AND alerts.active AND projects.active diff --git a/api/chalicelib/core/alerts_processor.py b/api/chalicelib/core/alerts_processor.py index 76ae5c615..4babe64ce 100644 --- a/api/chalicelib/core/alerts_processor.py +++ b/api/chalicelib/core/alerts_processor.py @@ -49,10 +49,12 @@ LeftToDb = { schemas.AlertColumn.errors__4xx_5xx__count: { "table": "events.resources INNER JOIN public.sessions USING(session_id)", "formula": "COUNT(session_id)", "condition": "status/100!=2"}, - schemas.AlertColumn.errors__4xx__count: {"table": "events.resources INNER JOIN public.sessions USING(session_id)", - "formula": "COUNT(session_id)", "condition": "status/100=4"}, - schemas.AlertColumn.errors__5xx__count: {"table": "events.resources INNER JOIN public.sessions USING(session_id)", - "formula": "COUNT(session_id)", "condition": "status/100=5"}, + schemas.AlertColumn.errors__4xx__count: { + "table": "events.resources INNER JOIN public.sessions USING(session_id)", + "formula": "COUNT(session_id)", "condition": "status/100=4"}, + schemas.AlertColumn.errors__5xx__count: { + "table": "events.resources INNER JOIN public.sessions USING(session_id)", + "formula": "COUNT(session_id)", "condition": "status/100=5"}, schemas.AlertColumn.errors__javascript__impacted_sessions__count: { "table": "events.resources INNER JOIN public.sessions USING(session_id)", "formula": "COUNT(DISTINCT session_id)", "condition": "success= FALSE AND type='script'"}, @@ -95,7 +97,7 @@ def can_check(a) -> bool: a["options"].get("lastNotification") is None or a["options"]["lastNotification"] <= 0 or ((now - a["options"]["lastNotification"]) > a["options"]["renotifyInterval"] * 60 * 1000)) \ - and ((now - a["createdAt"]) % (TimeInterval[repetitionBase] * 60 * 1000)) < 60 * 1000 + and ((now - a["createdAt"]) % (TimeInterval[repetitionBase] * 60 * 1000)) < 60 * 1000 def Build(a): @@ -119,7 +121,7 @@ def Build(a): subQ = f"""SELECT {colDef["formula"]} AS value FROM {colDef["table"]} WHERE project_id = %(project_id)s - {"AND " + colDef["condition"] if colDef.get("condition") is not None else ""}""" + {"AND " + colDef["condition"] if colDef.get("condition") else ""}""" j_s = colDef.get("joinSessions", True) main_table = colDef["table"] is_ss = main_table == "public.sessions" @@ -142,8 +144,7 @@ def Build(a): "startDate": TimeUTC.now() - a["options"]["currentPeriod"] * 60 * 1000, "timestamp_sub2": TimeUTC.now() - 2 * a["options"]["currentPeriod"] * 60 * 1000} else: - sub1 = f"""{subQ} AND timestamp>=%(startDate)s - AND timestamp<=%(now)s + sub1 = f"""{subQ} {"AND timestamp >= %(startDate)s AND timestamp <= %(now)s" if not is_ss else ""} {"AND start_ts >= %(startDate)s AND start_ts <= %(now)s" if j_s else ""}""" params["startDate"] = TimeUTC.now() - a["options"]["currentPeriod"] * 60 * 1000 sub2 = f"""{subQ} {"AND timestamp < %(startDate)s AND timestamp >= %(timestamp_sub2)s" if not is_ss else ""} @@ -206,7 +207,7 @@ def process(): cur = cur.recreate(rollback=True) if len(notifications) > 0: cur.execute( - cur.mogrify(f"""UPDATE public.Alerts + cur.mogrify(f"""UPDATE public.alerts SET options = options||'{{"lastNotification":{TimeUTC.now()}}}'::jsonb WHERE alert_id IN %(ids)s;""", {"ids": tuple([n["alertId"] for n in notifications])})) if len(notifications) > 0: @@ -228,7 +229,7 @@ def generate_notification(alert, result): "alertId": alert["alertId"], "tenantId": alert["tenantId"], "title": alert["name"], - "description": f"has been triggered, {alert['query']['left']} = {left} ({alert['query']['operator']} {right}).", + "description": f"has been triggered, {alert['seriesName']} = {left} ({alert['query']['operator']} {right}).", "buttonText": "Check metrics for more details", "buttonUrl": f"/{alert['projectId']}/metrics", "imageUrl": None, diff --git a/api/chalicelib/core/assist.py b/api/chalicelib/core/assist.py index 7cc947395..288d8a9b7 100644 --- a/api/chalicelib/core/assist.py +++ b/api/chalicelib/core/assist.py @@ -4,7 +4,8 @@ from os.path import exists as path_exists, getsize import jwt import requests from decouple import config -from starlette.exceptions import HTTPException +from starlette import status +from fastapi import HTTPException import schemas from chalicelib.core import projects @@ -181,6 +182,8 @@ def autocomplete(project_id, q: str, key: str = None): except: print("couldn't get response") return {"errors": ["Something went wrong wile calling assist"]} + for r in results: + r["type"] = __change_keys(r["type"]) return {"data": results} @@ -192,10 +195,11 @@ def get_ice_servers(): def __get_efs_path(): efs_path = config("FS_DIR") if not path_exists(efs_path): - raise HTTPException(400, f"EFS not found in path: {efs_path}") + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"EFS not found in path: {efs_path}") if not access(efs_path, R_OK): - raise HTTPException(400, f"EFS found under: {efs_path}; but it is not readable, please check permissions") + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, + detail=f"EFS found under: {efs_path}; but it is not readable, please check permissions") return efs_path @@ -209,11 +213,12 @@ def get_raw_mob_by_id(project_id, session_id): path_to_file = efs_path + "/" + __get_mob_path(project_id=project_id, session_id=session_id) if path_exists(path_to_file): if not access(path_to_file, R_OK): - raise HTTPException(400, f"Replay file found under: {efs_path};" + - f" but it is not readable, please check permissions") + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Replay file found under: {efs_path};" + + " but it is not readable, please check permissions") # getsize return size in bytes, UNPROCESSED_MAX_SIZE is in Kb if (getsize(path_to_file) / 1000) >= config("UNPROCESSED_MAX_SIZE", cast=int, default=200 * 1000): - raise HTTPException(413, "Replay file too large") + raise HTTPException(status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, detail="Replay file too large") return path_to_file return None @@ -229,8 +234,9 @@ def get_raw_devtools_by_id(project_id, session_id): path_to_file = efs_path + "/" + __get_devtools_path(project_id=project_id, session_id=session_id) if path_exists(path_to_file): if not access(path_to_file, R_OK): - raise HTTPException(400, f"Devtools file found under: {efs_path};" - f" but it is not readable, please check permissions") + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Devtools file found under: {efs_path};" + " but it is not readable, please check permissions") return path_to_file @@ -262,3 +268,27 @@ def session_exists(project_id, session_id): except: print("couldn't get response") return False + + +def __change_keys(key): + return { + "PAGETITLE": schemas.LiveFilterType.page_title.value, + "ACTIVE": "active", + "LIVE": "live", + "SESSIONID": schemas.LiveFilterType.session_id.value, + "METADATA": schemas.LiveFilterType.metadata.value, + "USERID": schemas.LiveFilterType.user_id.value, + "USERUUID": schemas.LiveFilterType.user_UUID.value, + "PROJECTKEY": "projectKey", + "REVID": schemas.LiveFilterType.rev_id.value, + "TIMESTAMP": "timestamp", + "TRACKERVERSION": schemas.LiveFilterType.tracker_version.value, + "ISSNIPPET": "isSnippet", + "USEROS": schemas.LiveFilterType.user_os.value, + "USERBROWSER": schemas.LiveFilterType.user_browser.value, + "USERBROWSERVERSION": schemas.LiveFilterType.user_browser_version.value, + "USERDEVICE": schemas.LiveFilterType.user_device.value, + "USERDEVICETYPE": schemas.LiveFilterType.user_device_type.value, + "USERCOUNTRY": schemas.LiveFilterType.user_country.value, + "PROJECTID": "projectId" + }.get(key.upper(), key) diff --git a/api/chalicelib/core/authorizers.py b/api/chalicelib/core/authorizers.py index 2ec3fa01f..46ec9e25a 100644 --- a/api/chalicelib/core/authorizers.py +++ b/api/chalicelib/core/authorizers.py @@ -15,7 +15,7 @@ def jwt_authorizer(token): token[1], config("jwt_secret"), algorithms=config("jwt_algorithm"), - audience=[ f"front:{helper.get_stage_name()}"] + audience=[f"front:{helper.get_stage_name()}"] ) except jwt.ExpiredSignatureError: print("! JWT Expired signature") @@ -37,12 +37,16 @@ def jwt_context(context): } +def get_jwt_exp(iat): + return iat // 1000 + config("JWT_EXPIRATION", cast=int) + TimeUTC.get_utc_offset() // 1000 + + def generate_jwt(id, tenant_id, iat, aud): token = jwt.encode( payload={ "userId": id, "tenantId": tenant_id, - "exp": iat // 1000 + config("JWT_EXPIRATION", cast=int) + TimeUTC.get_utc_offset() // 1000, + "exp": get_jwt_exp(iat), "iss": config("JWT_ISSUER"), "iat": iat // 1000, "aud": aud diff --git a/api/chalicelib/core/autocomplete.py b/api/chalicelib/core/autocomplete.py index d064856f1..00eff7d61 100644 --- a/api/chalicelib/core/autocomplete.py +++ b/api/chalicelib/core/autocomplete.py @@ -25,24 +25,24 @@ def __get_autocomplete_table(value, project_id): if e == schemas.FilterType.user_country: c_list = countries.get_country_code_autocomplete(value) if len(c_list) > 0: - sub_queries.append(f"""(SELECT DISTINCT ON(value) type, value + sub_queries.append(f"""(SELECT DISTINCT ON(value) '{e.value}' AS _type, value FROM {TABLE} WHERE project_id = %(project_id)s - AND type= '{e}' + AND type= '{e.value.upper()}' AND value IN %(c_list)s)""") continue - sub_queries.append(f"""(SELECT type, value + sub_queries.append(f"""(SELECT '{e.value}' AS _type, value FROM {TABLE} WHERE project_id = %(project_id)s - AND type= '{e}' + AND type= '{e.value.upper()}' AND value ILIKE %(svalue)s ORDER BY value LIMIT 5)""") if len(value) > 2: - sub_queries.append(f"""(SELECT type, value + sub_queries.append(f"""(SELECT '{e.value}' AS _type, value FROM {TABLE} WHERE project_id = %(project_id)s - AND type= '{e}' + AND type= '{e.value.upper()}' AND value ILIKE %(value)s ORDER BY value LIMIT 5)""") @@ -62,8 +62,11 @@ def __get_autocomplete_table(value, project_id): print(value) print("--------------------") raise err - results = helper.list_to_camel_case(cur.fetchall()) - return results + results = cur.fetchall() + for r in results: + r["type"] = r.pop("_type") + results = helper.list_to_camel_case(results) + return results def __generic_query(typename, value_length=None): @@ -72,7 +75,7 @@ def __generic_query(typename, value_length=None): FROM {TABLE} WHERE project_id = %(project_id)s - AND type='{typename}' + AND type='{typename.upper()}' AND value IN %(value)s ORDER BY value""" @@ -81,7 +84,7 @@ def __generic_query(typename, value_length=None): FROM {TABLE} WHERE project_id = %(project_id)s - AND type='{typename}' + AND type='{typename.upper()}' AND value ILIKE %(svalue)s ORDER BY value LIMIT 5) @@ -90,7 +93,7 @@ def __generic_query(typename, value_length=None): FROM {TABLE} WHERE project_id = %(project_id)s - AND type='{typename}' + AND type='{typename.upper()}' AND value ILIKE %(value)s ORDER BY value LIMIT 5);""" @@ -98,7 +101,7 @@ def __generic_query(typename, value_length=None): FROM {TABLE} WHERE project_id = %(project_id)s - AND type='{typename}' + AND type='{typename.upper()}' AND value ILIKE %(svalue)s ORDER BY value LIMIT 10;""" @@ -135,13 +138,13 @@ def __generic_autocomplete_metas(typename): return f -def __pg_errors_query(source=None, value_length=None): +def __errors_query(source=None, value_length=None): if value_length is None or value_length > 2: return f"""((SELECT DISTINCT ON(lg.message) lg.message AS value, source, - '{events.event_type.ERROR.ui_type}' AS type - FROM {events.event_type.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) + '{events.EventType.ERROR.ui_type}' AS type + FROM {events.EventType.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.message ILIKE %(svalue)s @@ -152,8 +155,8 @@ def __pg_errors_query(source=None, value_length=None): (SELECT DISTINCT ON(lg.name) lg.name AS value, source, - '{events.event_type.ERROR.ui_type}' AS type - FROM {events.event_type.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) + '{events.EventType.ERROR.ui_type}' AS type + FROM {events.EventType.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.name ILIKE %(svalue)s @@ -164,8 +167,8 @@ def __pg_errors_query(source=None, value_length=None): (SELECT DISTINCT ON(lg.message) lg.message AS value, source, - '{events.event_type.ERROR.ui_type}' AS type - FROM {events.event_type.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) + '{events.EventType.ERROR.ui_type}' AS type + FROM {events.EventType.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.message ILIKE %(value)s @@ -176,8 +179,8 @@ def __pg_errors_query(source=None, value_length=None): (SELECT DISTINCT ON(lg.name) lg.name AS value, source, - '{events.event_type.ERROR.ui_type}' AS type - FROM {events.event_type.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) + '{events.EventType.ERROR.ui_type}' AS type + FROM {events.EventType.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.name ILIKE %(value)s @@ -187,8 +190,8 @@ def __pg_errors_query(source=None, value_length=None): return f"""((SELECT DISTINCT ON(lg.message) lg.message AS value, source, - '{events.event_type.ERROR.ui_type}' AS type - FROM {events.event_type.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) + '{events.EventType.ERROR.ui_type}' AS type + FROM {events.EventType.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.message ILIKE %(svalue)s @@ -199,8 +202,8 @@ def __pg_errors_query(source=None, value_length=None): (SELECT DISTINCT ON(lg.name) lg.name AS value, source, - '{events.event_type.ERROR.ui_type}' AS type - FROM {events.event_type.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) + '{events.EventType.ERROR.ui_type}' AS type + FROM {events.EventType.ERROR.table} INNER JOIN public.errors AS lg USING (error_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.name ILIKE %(svalue)s @@ -209,11 +212,11 @@ def __pg_errors_query(source=None, value_length=None): LIMIT 5));""" -def __search_pg_errors(project_id, value, key=None, source=None): +def __search_errors(project_id, value, key=None, source=None): with pg_client.PostgresClient() as cur: cur.execute( - cur.mogrify(__pg_errors_query(source, - value_length=len(value)), + cur.mogrify(__errors_query(source, + value_length=len(value)), {"project_id": project_id, "value": helper.string_to_sql_like(value), "svalue": helper.string_to_sql_like("^" + value), "source": source})) @@ -221,12 +224,12 @@ def __search_pg_errors(project_id, value, key=None, source=None): return results -def __search_pg_errors_ios(project_id, value, key=None, source=None): +def __search_errors_ios(project_id, value, key=None, source=None): if len(value) > 2: query = f"""(SELECT DISTINCT ON(lg.reason) lg.reason AS value, - '{events.event_type.ERROR_IOS.ui_type}' AS type - FROM {events.event_type.ERROR_IOS.table} INNER JOIN public.crashes_ios AS lg USING (crash_id) LEFT JOIN public.sessions AS s USING(session_id) + '{events.EventType.ERROR_IOS.ui_type}' AS type + FROM {events.EventType.ERROR_IOS.table} INNER JOIN public.crashes_ios AS lg USING (crash_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.project_id = %(project_id)s @@ -235,8 +238,8 @@ def __search_pg_errors_ios(project_id, value, key=None, source=None): UNION ALL (SELECT DISTINCT ON(lg.name) lg.name AS value, - '{events.event_type.ERROR_IOS.ui_type}' AS type - FROM {events.event_type.ERROR_IOS.table} INNER JOIN public.crashes_ios AS lg USING (crash_id) LEFT JOIN public.sessions AS s USING(session_id) + '{events.EventType.ERROR_IOS.ui_type}' AS type + FROM {events.EventType.ERROR_IOS.table} INNER JOIN public.crashes_ios AS lg USING (crash_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.project_id = %(project_id)s @@ -245,8 +248,8 @@ def __search_pg_errors_ios(project_id, value, key=None, source=None): UNION ALL (SELECT DISTINCT ON(lg.reason) lg.reason AS value, - '{events.event_type.ERROR_IOS.ui_type}' AS type - FROM {events.event_type.ERROR_IOS.table} INNER JOIN public.crashes_ios AS lg USING (crash_id) LEFT JOIN public.sessions AS s USING(session_id) + '{events.EventType.ERROR_IOS.ui_type}' AS type + FROM {events.EventType.ERROR_IOS.table} INNER JOIN public.crashes_ios AS lg USING (crash_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.project_id = %(project_id)s @@ -255,8 +258,8 @@ def __search_pg_errors_ios(project_id, value, key=None, source=None): UNION ALL (SELECT DISTINCT ON(lg.name) lg.name AS value, - '{events.event_type.ERROR_IOS.ui_type}' AS type - FROM {events.event_type.ERROR_IOS.table} INNER JOIN public.crashes_ios AS lg USING (crash_id) LEFT JOIN public.sessions AS s USING(session_id) + '{events.EventType.ERROR_IOS.ui_type}' AS type + FROM {events.EventType.ERROR_IOS.table} INNER JOIN public.crashes_ios AS lg USING (crash_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.project_id = %(project_id)s @@ -265,8 +268,8 @@ def __search_pg_errors_ios(project_id, value, key=None, source=None): else: query = f"""(SELECT DISTINCT ON(lg.reason) lg.reason AS value, - '{events.event_type.ERROR_IOS.ui_type}' AS type - FROM {events.event_type.ERROR_IOS.table} INNER JOIN public.crashes_ios AS lg USING (crash_id) LEFT JOIN public.sessions AS s USING(session_id) + '{events.EventType.ERROR_IOS.ui_type}' AS type + FROM {events.EventType.ERROR_IOS.table} INNER JOIN public.crashes_ios AS lg USING (crash_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.project_id = %(project_id)s @@ -275,8 +278,8 @@ def __search_pg_errors_ios(project_id, value, key=None, source=None): UNION ALL (SELECT DISTINCT ON(lg.name) lg.name AS value, - '{events.event_type.ERROR_IOS.ui_type}' AS type - FROM {events.event_type.ERROR_IOS.table} INNER JOIN public.crashes_ios AS lg USING (crash_id) LEFT JOIN public.sessions AS s USING(session_id) + '{events.EventType.ERROR_IOS.ui_type}' AS type + FROM {events.EventType.ERROR_IOS.table} INNER JOIN public.crashes_ios AS lg USING (crash_id) LEFT JOIN public.sessions AS s USING(session_id) WHERE s.project_id = %(project_id)s AND lg.project_id = %(project_id)s @@ -289,7 +292,7 @@ def __search_pg_errors_ios(project_id, value, key=None, source=None): return results -def __search_pg_metadata(project_id, value, key=None, source=None): +def __search_metadata(project_id, value, key=None, source=None): meta_keys = metadata.get(project_id=project_id) meta_keys = {m["key"]: m["index"] for m in meta_keys} if len(meta_keys) == 0 or key is not None and key not in meta_keys.keys(): @@ -323,4 +326,4 @@ def __search_pg_metadata(project_id, value, key=None, source=None): LIMIT 5;""", {"project_id": project_id, "value": helper.string_to_sql_like(value), "svalue": helper.string_to_sql_like("^" + value)})) results = helper.list_to_camel_case(cur.fetchall()) - return results \ No newline at end of file + return results diff --git a/api/chalicelib/core/click_maps.py b/api/chalicelib/core/click_maps.py new file mode 100644 index 000000000..2383aeb1c --- /dev/null +++ b/api/chalicelib/core/click_maps.py @@ -0,0 +1,77 @@ +import schemas +from chalicelib.core import sessions_mobs, sessions_legacy as sessions_search, events +from chalicelib.utils import pg_client, helper + +SESSION_PROJECTION_COLS = """s.project_id, +s.session_id::text AS session_id, +s.user_uuid, +s.user_id, +s.user_os, +s.user_browser, +s.user_device, +s.user_device_type, +s.user_country, +s.start_ts, +s.duration, +s.events_count, +s.pages_count, +s.errors_count, +s.user_anonymous_id, +s.platform, +s.issue_score, +to_jsonb(s.issue_types) AS issue_types, +favorite_sessions.session_id NOTNULL AS favorite, +COALESCE((SELECT TRUE + FROM public.user_viewed_sessions AS fs + WHERE s.session_id = fs.session_id + AND fs.user_id = %(userId)s LIMIT 1), FALSE) AS viewed """ + + +def search_short_session(data: schemas.FlatClickMapSessionsSearch, project_id, user_id, include_mobs: bool = True): + no_platform = True + for f in data.filters: + if f.type == schemas.FilterType.platform: + no_platform = False + break + if no_platform: + data.filters.append(schemas.SessionSearchFilterSchema(type=schemas.FilterType.platform, + value=[schemas.PlatformType.desktop], + operator=schemas.SearchEventOperator._is)) + + full_args, query_part = sessions_search.search_query_parts(data=data, error_status=None, errors_only=False, + favorite_only=data.bookmarked, issue=None, + project_id=project_id, user_id=user_id) + + with pg_client.PostgresClient() as cur: + data.order = schemas.SortOrderType.desc + data.sort = 'duration' + + # meta_keys = metadata.get(project_id=project_id) + meta_keys = [] + main_query = cur.mogrify(f"""SELECT {SESSION_PROJECTION_COLS} + {"," if len(meta_keys) > 0 else ""}{",".join([f'metadata_{m["index"]}' for m in meta_keys])} + {query_part} + ORDER BY {data.sort} {data.order.value} + LIMIT 1;""", full_args) + # print("--------------------") + # print(main_query) + # print("--------------------") + try: + cur.execute(main_query) + except Exception as err: + print("--------- CLICK MAP SHORT SESSION SEARCH QUERY EXCEPTION -----------") + print(main_query.decode('UTF-8')) + print("--------- PAYLOAD -----------") + print(data.json()) + print("--------------------") + raise err + + session = cur.fetchone() + if session: + if include_mobs: + session['domURL'] = sessions_mobs.get_urls(session_id=session["session_id"], project_id=project_id) + session['mobsUrl'] = sessions_mobs.get_urls_depercated(session_id=session["session_id"]) + session['events'] = events.get_by_session_id(project_id=project_id, session_id=session["session_id"], + event_type=schemas.EventType.location) + + return helper.dict_to_camel_case(session) diff --git a/api/chalicelib/core/collaboration_base.py b/api/chalicelib/core/collaboration_base.py new file mode 100644 index 000000000..b0aa2c9bb --- /dev/null +++ b/api/chalicelib/core/collaboration_base.py @@ -0,0 +1,45 @@ +from abc import ABC, abstractmethod + +import schemas + + +class BaseCollaboration(ABC): + @classmethod + @abstractmethod + def add(cls, tenant_id, data: schemas.AddCollaborationSchema): + pass + + @classmethod + @abstractmethod + def say_hello(cls, url): + pass + + @classmethod + @abstractmethod + def send_raw(cls, tenant_id, webhook_id, body): + pass + + @classmethod + @abstractmethod + def send_batch(cls, tenant_id, webhook_id, attachments): + pass + + @classmethod + @abstractmethod + def __share(cls, tenant_id, integration_id, attachments): + pass + + @classmethod + @abstractmethod + def share_session(cls, tenant_id, project_id, session_id, user, comment, integration_id=None): + pass + + @classmethod + @abstractmethod + def share_error(cls, tenant_id, project_id, error_id, user, comment, integration_id=None): + pass + + @classmethod + @abstractmethod + def get_integration(cls, tenant_id, integration_id=None): + pass diff --git a/api/chalicelib/core/collaboration_msteams.py b/api/chalicelib/core/collaboration_msteams.py new file mode 100644 index 000000000..eb60fd653 --- /dev/null +++ b/api/chalicelib/core/collaboration_msteams.py @@ -0,0 +1,195 @@ +import json + +import requests +from decouple import config +from fastapi import HTTPException +from starlette import status + +import schemas +from chalicelib.core import webhook +from chalicelib.core.collaboration_base import BaseCollaboration + + +class MSTeams(BaseCollaboration): + @classmethod + def add(cls, tenant_id, data: schemas.AddCollaborationSchema): + if webhook.exists_by_name(tenant_id=tenant_id, name=data.name, exclude_id=None, + webhook_type=schemas.WebhookType.msteams): + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"name already exists.") + if cls.say_hello(data.url): + return webhook.add(tenant_id=tenant_id, + endpoint=data.url, + webhook_type=schemas.WebhookType.msteams, + name=data.name) + return None + + # https://messagecardplayground.azurewebsites.net + # https://adaptivecards.io/designer/ + @classmethod + def say_hello(cls, url): + r = requests.post( + url=url, + json={ + "@type": "MessageCard", + "@context": "https://schema.org/extensions", + "summary": "Hello message", + "title": "Welcome to OpenReplay" + }) + if r.status_code != 200: + print("MSTeams integration failed") + print(r.text) + return False + return True + + @classmethod + def send_raw(cls, tenant_id, webhook_id, body): + integration = cls.get_integration(tenant_id=tenant_id, integration_id=webhook_id) + if integration is None: + return {"errors": ["msteams integration not found"]} + try: + r = requests.post( + url=integration["endpoint"], + json=body, + timeout=5) + if r.status_code != 200: + print(f"!! issue sending msteams raw; webhookId:{webhook_id} code:{r.status_code}") + print(r.text) + return None + except requests.exceptions.Timeout: + print(f"!! Timeout sending msteams raw webhookId:{webhook_id}") + return None + except Exception as e: + print(f"!! Issue sending msteams raw webhookId:{webhook_id}") + print(str(e)) + return None + return {"data": r.text} + + @classmethod + def send_batch(cls, tenant_id, webhook_id, attachments): + integration = cls.get_integration(tenant_id=tenant_id, integration_id=webhook_id) + if integration is None: + return {"errors": ["msteams integration not found"]} + print(f"====> sending msteams batch notification: {len(attachments)}") + for i in range(0, len(attachments), 100): + print(json.dumps({"type": "message", + "attachments": [ + {"contentType": "application/vnd.microsoft.card.adaptive", + "contentUrl": None, + "content": { + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.2", + "body": attachments[i:i + 100]}} + ]})) + r = requests.post( + url=integration["endpoint"], + json={"type": "message", + "attachments": [ + {"contentType": "application/vnd.microsoft.card.adaptive", + "contentUrl": None, + "content": { + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.2", + "body": attachments[i:i + 100]}} + ]}) + if r.status_code != 200: + print("!!!! something went wrong") + print(r) + print(r.text) + + @classmethod + def __share(cls, tenant_id, integration_id, attachement): + integration = cls.get_integration(tenant_id=tenant_id, integration_id=integration_id) + if integration is None: + return {"errors": ["Microsoft Teams integration not found"]} + r = requests.post( + url=integration["endpoint"], + json={"type": "message", + "attachments": [ + {"contentType": "application/vnd.microsoft.card.adaptive", + "contentUrl": None, + "content": { + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.5", + "body": [attachement]}} + ] + }) + + return r.text + + @classmethod + def share_session(cls, tenant_id, project_id, session_id, user, comment, integration_id=None): + title = f"[{user}](mailto:{user}) has shared the below session!" + link = f"{config('SITE_URL')}/{project_id}/session/{session_id}" + link = f"[{link}]({link})" + args = {"type": "ColumnSet", + "style": "emphasis", + "separator": True, + "bleed": True, + "columns": [{ + "width": "stretch", + "items": [ + {"type": "TextBlock", + "text": title, + "style": "heading", + "size": "Large"}, + {"type": "TextBlock", + "spacing": "small", + "text": link} + ] + }]} + if comment and len(comment) > 0: + args["columns"][0]["items"].append({ + "type": "TextBlock", + "spacing": "small", + "text": comment + }) + data = cls.__share(tenant_id, integration_id, attachement=args) + if "errors" in data: + return data + return {"data": data} + + @classmethod + def share_error(cls, tenant_id, project_id, error_id, user, comment, integration_id=None): + title = f"[{user}](mailto:{user}) has shared the below error!" + link = f"{config('SITE_URL')}/{project_id}/errors/{error_id}" + link = f"[{link}]({link})" + args = {"type": "ColumnSet", + "style": "emphasis", + "separator": True, + "bleed": True, + "columns": [{ + "width": "stretch", + "items": [ + {"type": "TextBlock", + "text": title, + "style": "heading", + "size": "Large"}, + {"type": "TextBlock", + "spacing": "small", + "text": link} + ] + }]} + if comment and len(comment) > 0: + args["columns"][0]["items"].append({ + "type": "TextBlock", + "spacing": "small", + "text": comment + }) + data = cls.__share(tenant_id, integration_id, attachement=args) + if "errors" in data: + return data + return {"data": data} + + @classmethod + def get_integration(cls, tenant_id, integration_id=None): + if integration_id is not None: + return webhook.get_webhook(tenant_id=tenant_id, webhook_id=integration_id, + webhook_type=schemas.WebhookType.msteams) + + integrations = webhook.get_by_type(tenant_id=tenant_id, webhook_type=schemas.WebhookType.msteams) + if integrations is None or len(integrations) == 0: + return None + return integrations[0] diff --git a/api/chalicelib/core/collaboration_slack.py b/api/chalicelib/core/collaboration_slack.py index 5e7797430..1879b3c2d 100644 --- a/api/chalicelib/core/collaboration_slack.py +++ b/api/chalicelib/core/collaboration_slack.py @@ -1,19 +1,26 @@ import requests from decouple import config from datetime import datetime + +from fastapi import HTTPException +from starlette import status + +import schemas from chalicelib.core import webhook +from chalicelib.core.collaboration_base import BaseCollaboration -class Slack: +class Slack(BaseCollaboration): @classmethod - def add_channel(cls, tenant_id, **args): - url = args["url"] - name = args["name"] - if cls.say_hello(url): + def add(cls, tenant_id, data: schemas.AddCollaborationSchema): + if webhook.exists_by_name(tenant_id=tenant_id, name=data.name, exclude_id=None, + webhook_type=schemas.WebhookType.slack): + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"name already exists.") + if cls.say_hello(data.url): return webhook.add(tenant_id=tenant_id, - endpoint=url, - webhook_type="slack", - name=name) + endpoint=data.url, + webhook_type=schemas.WebhookType.slack, + name=data.name) return None @classmethod @@ -34,40 +41,9 @@ class Slack: return False return True - @classmethod - def send_text_attachments(cls, tenant_id, webhook_id, text, **args): - integration = cls.__get(tenant_id=tenant_id, integration_id=webhook_id) - if integration is None: - return {"errors": ["slack integration not found"]} - try: - r = requests.post( - url=integration["endpoint"], - json={ - "attachments": [ - { - "text": text, - "ts": datetime.now().timestamp(), - **args - } - ] - }, - timeout=5) - if r.status_code != 200: - print(f"!! issue sending slack text attachments; webhookId:{webhook_id} code:{r.status_code}") - print(r.text) - return None - except requests.exceptions.Timeout: - print(f"!! Timeout sending slack text attachments webhookId:{webhook_id}") - return None - except Exception as e: - print(f"!! Issue sending slack text attachments webhookId:{webhook_id}") - print(str(e)) - return None - return {"data": r.text} - @classmethod def send_raw(cls, tenant_id, webhook_id, body): - integration = cls.__get(tenant_id=tenant_id, integration_id=webhook_id) + integration = cls.get_integration(tenant_id=tenant_id, integration_id=webhook_id) if integration is None: return {"errors": ["slack integration not found"]} try: @@ -90,7 +66,7 @@ class Slack: @classmethod def send_batch(cls, tenant_id, webhook_id, attachments): - integration = cls.__get(tenant_id=tenant_id, integration_id=webhook_id) + integration = cls.get_integration(tenant_id=tenant_id, integration_id=webhook_id) if integration is None: return {"errors": ["slack integration not found"]} print(f"====> sending slack batch notification: {len(attachments)}") @@ -105,24 +81,12 @@ class Slack: print(r.text) @classmethod - def __share_to_slack(cls, tenant_id, integration_id, fallback, pretext, title, title_link, text): - integration = cls.__get(tenant_id=tenant_id, integration_id=integration_id) + def __share(cls, tenant_id, integration_id, attachement): + integration = cls.get_integration(tenant_id=tenant_id, integration_id=integration_id) if integration is None: return {"errors": ["slack integration not found"]} - r = requests.post( - url=integration["endpoint"], - json={ - "attachments": [ - { - "fallback": fallback, - "pretext": pretext, - "title": title, - "title_link": title_link, - "text": text, - "ts": datetime.now().timestamp() - } - ] - }) + attachement["ts"] = datetime.now().timestamp() + r = requests.post(url=integration["endpoint"], json={"attachments": [attachement]}) return r.text @classmethod @@ -132,7 +96,10 @@ class Slack: "title": f"{config('SITE_URL')}/{project_id}/session/{session_id}", "title_link": f"{config('SITE_URL')}/{project_id}/session/{session_id}", "text": comment} - return {"data": cls.__share_to_slack(tenant_id, integration_id, **args)} + data = cls.__share(tenant_id, integration_id, attachement=args) + if "errors" in data: + return data + return {"data": data} @classmethod def share_error(cls, tenant_id, project_id, error_id, user, comment, integration_id=None): @@ -141,19 +108,18 @@ class Slack: "title": f"{config('SITE_URL')}/{project_id}/errors/{error_id}", "title_link": f"{config('SITE_URL')}/{project_id}/errors/{error_id}", "text": comment} - return {"data": cls.__share_to_slack(tenant_id, integration_id, **args)} + data = cls.__share(tenant_id, integration_id, attachement=args) + if "errors" in data: + return data + return {"data": data} @classmethod - def has_slack(cls, tenant_id): - integration = cls.__get(tenant_id=tenant_id) - return not (integration is None or len(integration) == 0) - - @classmethod - def __get(cls, tenant_id, integration_id=None): + def get_integration(cls, tenant_id, integration_id=None): if integration_id is not None: - return webhook.get(tenant_id=tenant_id, webhook_id=integration_id) + return webhook.get_webhook(tenant_id=tenant_id, webhook_id=integration_id, + webhook_type=schemas.WebhookType.slack) - integrations = webhook.get_by_type(tenant_id=tenant_id, webhook_type="slack") + integrations = webhook.get_by_type(tenant_id=tenant_id, webhook_type=schemas.WebhookType.slack) if integrations is None or len(integrations) == 0: return None return integrations[0] diff --git a/api/chalicelib/core/custom_metrics.py b/api/chalicelib/core/custom_metrics.py index 691b8e1ba..c40316067 100644 --- a/api/chalicelib/core/custom_metrics.py +++ b/api/chalicelib/core/custom_metrics.py @@ -1,15 +1,19 @@ import json from typing import Union +from decouple import config +from fastapi import HTTPException +from starlette import status + import schemas -from chalicelib.core import sessions, funnels, errors, issues -from chalicelib.utils import helper, pg_client +from chalicelib.core import sessions, funnels, errors, issues, metrics, click_maps, sessions_mobs +from chalicelib.utils import helper, pg_client, s3 from chalicelib.utils.TimeUTC import TimeUTC PIE_CHART_GROUP = 5 -def __try_live(project_id, data: schemas.TryCustomMetricsPayloadSchema): +def __try_live(project_id, data: schemas.CreateCardSchema): results = [] for i, s in enumerate(data.series): s.filter.startDate = data.startTimestamp @@ -42,11 +46,11 @@ def __try_live(project_id, data: schemas.TryCustomMetricsPayloadSchema): return results -def __is_funnel_chart(data: schemas.TryCustomMetricsPayloadSchema): +def __is_funnel_chart(data: schemas.CreateCardSchema): return data.metric_type == schemas.MetricType.funnel -def __get_funnel_chart(project_id, data: schemas.TryCustomMetricsPayloadSchema): +def __get_funnel_chart(project_id, data: schemas.CreateCardSchema): if len(data.series) == 0: return { "stages": [], @@ -57,12 +61,12 @@ def __get_funnel_chart(project_id, data: schemas.TryCustomMetricsPayloadSchema): return funnels.get_top_insights_on_the_fly_widget(project_id=project_id, data=data.series[0].filter) -def __is_errors_list(data): +def __is_errors_list(data: schemas.CreateCardSchema): return data.metric_type == schemas.MetricType.table \ - and data.metric_of == schemas.TableMetricOfType.errors + and data.metric_of == schemas.MetricOfTable.errors -def __get_errors_list(project_id, user_id, data): +def __get_errors_list(project_id, user_id, data: schemas.CreateCardSchema): if len(data.series) == 0: return { "total": 0, @@ -75,12 +79,12 @@ def __get_errors_list(project_id, user_id, data): return errors.search(data.series[0].filter, project_id=project_id, user_id=user_id) -def __is_sessions_list(data): +def __is_sessions_list(data: schemas.CreateCardSchema): return data.metric_type == schemas.MetricType.table \ - and data.metric_of == schemas.TableMetricOfType.sessions + and data.metric_of == schemas.MetricOfTable.sessions -def __get_sessions_list(project_id, user_id, data): +def __get_sessions_list(project_id, user_id, data: schemas.CreateCardSchema): if len(data.series) == 0: print("empty series") return { @@ -94,14 +98,37 @@ def __get_sessions_list(project_id, user_id, data): return sessions.search_sessions(data=data.series[0].filter, project_id=project_id, user_id=user_id) -def merged_live(project_id, data: schemas.TryCustomMetricsPayloadSchema, user_id=None): - if __is_funnel_chart(data): +def __is_predefined(data: schemas.CreateCardSchema): + return data.is_template + + +def __is_click_map(data: schemas.CreateCardSchema): + return data.metric_type == schemas.MetricType.click_map + + +def __get_click_map_chart(project_id, user_id, data: schemas.CreateCardSchema, include_mobs: bool = True): + if len(data.series) == 0: + return None + data.series[0].filter.startDate = data.startTimestamp + data.series[0].filter.endDate = data.endTimestamp + return click_maps.search_short_session(project_id=project_id, user_id=user_id, + data=schemas.FlatClickMapSessionsSearch(**data.series[0].filter.dict()), + include_mobs=include_mobs) + + +def merged_live(project_id, data: schemas.CreateCardSchema, user_id=None): + if data.is_template: + return get_predefined_metric(key=data.metric_of, project_id=project_id, data=data.dict()) + elif __is_funnel_chart(data): return __get_funnel_chart(project_id=project_id, data=data) elif __is_errors_list(data): return __get_errors_list(project_id=project_id, user_id=user_id, data=data) elif __is_sessions_list(data): return __get_sessions_list(project_id=project_id, user_id=user_id, data=data) - + elif __is_click_map(data): + return __get_click_map_chart(project_id=project_id, user_id=user_id, data=data) + elif len(data.series) == 0: + return [] series_charts = __try_live(project_id=project_id, data=data) if data.view_type == schemas.MetricTimeseriesViewType.progress or data.metric_type == schemas.MetricType.table: return series_charts @@ -113,69 +140,67 @@ def merged_live(project_id, data: schemas.TryCustomMetricsPayloadSchema, user_id return results -def __merge_metric_with_data(metric, data: Union[schemas.CustomMetricChartPayloadSchema, - schemas.CustomMetricSessionsPayloadSchema]) \ - -> Union[schemas.CreateCustomMetricsSchema, None]: +def __merge_metric_with_data(metric: schemas.CreateCardSchema, + data: schemas.CardChartSchema) -> schemas.CreateCardSchema: if data.series is not None and len(data.series) > 0: - metric["series"] = data.series - metric: schemas.CreateCustomMetricsSchema = schemas.CreateCustomMetricsSchema.parse_obj({**data.dict(), **metric}) + metric.series = data.series + metric: schemas.CreateCardSchema = schemas.CreateCardSchema( + **{**data.dict(by_alias=True), **metric.dict(by_alias=True)}) if len(data.filters) > 0 or len(data.events) > 0: for s in metric.series: if len(data.filters) > 0: s.filter.filters += data.filters if len(data.events) > 0: s.filter.events += data.events + metric.limit = data.limit + metric.page = data.page + metric.startTimestamp = data.startTimestamp + metric.endTimestamp = data.endTimestamp return metric -def make_chart(project_id, user_id, metric_id, data: schemas.CustomMetricChartPayloadSchema, metric=None): - if metric is None: - metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) +def make_chart(project_id, user_id, data: schemas.CardChartSchema, metric: schemas.CreateCardSchema): if metric is None: return None - metric: schemas.CreateCustomMetricsSchema = __merge_metric_with_data(metric=metric, data=data) + metric: schemas.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) return merged_live(project_id=project_id, data=metric, user_id=user_id) - # if __is_funnel_chart(metric): - # return __get_funnel_chart(project_id=project_id, data=metric) - # elif __is_errors_list(metric): - # return __get_errors_list(project_id=project_id, user_id=user_id, data=metric) - # - # series_charts = __try_live(project_id=project_id, data=metric) - # if metric.view_type == schemas.MetricTimeseriesViewType.progress or metric.metric_type == schemas.MetricType.table: - # return series_charts - # results = [{}] * len(series_charts[0]) - # for i in range(len(results)): - # for j, series_chart in enumerate(series_charts): - # results[i] = {**results[i], "timestamp": series_chart[i]["timestamp"], - # metric.series[j].name: series_chart[i]["count"]} - # return results -def get_sessions(project_id, user_id, metric_id, data: schemas.CustomMetricSessionsPayloadSchema): - metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) - if metric is None: +def get_sessions(project_id, user_id, metric_id, data: schemas.CardSessionsSchema): + # raw_metric = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False, include_data=True) + raw_metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) + if raw_metric is None: return None - metric: schemas.CreateCustomMetricsSchema = __merge_metric_with_data(metric=metric, data=data) + metric: schemas.CreateCardSchema = schemas.CreateCardSchema(**raw_metric) + metric: schemas.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) if metric is None: return None results = [] + # is_click_map = False + # if __is_click_map(metric) and raw_metric.get("data") is not None: + # is_click_map = True for s in metric.series: s.filter.startDate = data.startTimestamp s.filter.endDate = data.endTimestamp s.filter.limit = data.limit s.filter.page = data.page + # if is_click_map: + # results.append( + # {"seriesId": s.series_id, "seriesName": s.name, "total": 1, "sessions": [raw_metric["data"]]}) + # break results.append({"seriesId": s.series_id, "seriesName": s.name, **sessions.search_sessions(data=s.filter, project_id=project_id, user_id=user_id)}) return results -def get_funnel_issues(project_id, user_id, metric_id, data: schemas.CustomMetricSessionsPayloadSchema): - metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) - if metric is None: +def get_funnel_issues(project_id, user_id, metric_id, data: schemas.CardSessionsSchema): + raw_metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) + if raw_metric is None: return None - metric: schemas.CreateCustomMetricsSchema = __merge_metric_with_data(metric=metric, data=data) + metric: schemas.CreateCardSchema = schemas.CreateCardSchema(**raw_metric) + metric: schemas.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) if metric is None: return None for s in metric.series: @@ -187,11 +212,12 @@ def get_funnel_issues(project_id, user_id, metric_id, data: schemas.CustomMetric **funnels.get_issues_on_the_fly_widget(project_id=project_id, data=s.filter)} -def get_errors_list(project_id, user_id, metric_id, data: schemas.CustomMetricSessionsPayloadSchema): - metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) - if metric is None: +def get_errors_list(project_id, user_id, metric_id, data: schemas.CardSessionsSchema): + raw_metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) + if raw_metric is None: return None - metric: schemas.CreateCustomMetricsSchema = __merge_metric_with_data(metric=metric, data=data) + metric: schemas.CreateCardSchema = schemas.CreateCardSchema(**raw_metric) + metric: schemas.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) if metric is None: return None for s in metric.series: @@ -203,7 +229,7 @@ def get_errors_list(project_id, user_id, metric_id, data: schemas.CustomMetricSe **errors.search(data=s.filter, project_id=project_id, user_id=user_id)} -def try_sessions(project_id, user_id, data: schemas.CustomMetricSessionsPayloadSchema): +def try_sessions(project_id, user_id, data: schemas.CardSessionsSchema): results = [] if data.series is None: return results @@ -212,50 +238,60 @@ def try_sessions(project_id, user_id, data: schemas.CustomMetricSessionsPayloadS s.filter.endDate = data.endTimestamp s.filter.limit = data.limit s.filter.page = data.page + if len(data.filters) > 0: + s.filter.filters += data.filters + if len(data.events) > 0: + s.filter.events += data.events results.append({"seriesId": None, "seriesName": s.name, **sessions.search_sessions(data=s.filter, project_id=project_id, user_id=user_id)}) return results -def create(project_id, user_id, data: schemas.CreateCustomMetricsSchema, dashboard=False): +def create(project_id, user_id, data: schemas.CreateCardSchema, dashboard=False): with pg_client.PostgresClient() as cur: - _data = {} + session_data = None + if __is_click_map(data): + session_data = __get_click_map_chart(project_id=project_id, user_id=user_id, + data=data, include_mobs=False) + if session_data is not None: + session_data = json.dumps(session_data) + _data = {"session_data": session_data} for i, s in enumerate(data.series): for k in s.dict().keys(): _data[f"{k}_{i}"] = s.__getattribute__(k) _data[f"index_{i}"] = i _data[f"filter_{i}"] = s.filter.json() series_len = len(data.series) - data.series = None - params = {"user_id": user_id, "project_id": project_id, - "default_config": json.dumps(data.config.dict()), - **data.dict(), **_data} - query = cur.mogrify(f"""\ - WITH m AS (INSERT INTO metrics (project_id, user_id, name, is_public, - view_type, metric_type, metric_of, metric_value, - metric_format, default_config) - VALUES (%(project_id)s, %(user_id)s, %(name)s, %(is_public)s, - %(view_type)s, %(metric_type)s, %(metric_of)s, %(metric_value)s, - %(metric_format)s, %(default_config)s) - RETURNING *) - INSERT - INTO metric_series(metric_id, index, name, filter) - VALUES {",".join([f"((SELECT metric_id FROM m), %(index_{i})s, %(name_{i})s, %(filter_{i})s::jsonb)" - for i in range(series_len)])} - RETURNING metric_id;""", params) + params = {"user_id": user_id, "project_id": project_id, **data.dict(), **_data} + params["default_config"] = json.dumps(data.default_config.dict()) + query = """INSERT INTO metrics (project_id, user_id, name, is_public, + view_type, metric_type, metric_of, metric_value, + metric_format, default_config, thumbnail, data) + VALUES (%(project_id)s, %(user_id)s, %(name)s, %(is_public)s, + %(view_type)s, %(metric_type)s, %(metric_of)s, %(metric_value)s, + %(metric_format)s, %(default_config)s, %(thumbnail)s, %(session_data)s) + RETURNING metric_id""" + if len(data.series) > 0: + query = f"""WITH m AS ({query}) + INSERT INTO metric_series(metric_id, index, name, filter) + VALUES {",".join([f"((SELECT metric_id FROM m), %(index_{i})s, %(name_{i})s, %(filter_{i})s::jsonb)" + for i in range(series_len)])} + RETURNING metric_id;""" - cur.execute( - query - ) + query = cur.mogrify(query, params) + # print("-------") + # print(query) + # print("-------") + cur.execute(query) r = cur.fetchone() if dashboard: return r["metric_id"] - return {"data": get(metric_id=r["metric_id"], project_id=project_id, user_id=user_id)} + return {"data": get_card(metric_id=r["metric_id"], project_id=project_id, user_id=user_id)} -def update(metric_id, user_id, project_id, data: schemas.UpdateCustomMetricsSchema): - metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) +def update(metric_id, user_id, project_id, data: schemas.UpdateCardSchema): + metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) if metric is None: return None series_ids = [r["seriesId"] for r in metric["series"]] @@ -267,7 +303,7 @@ def update(metric_id, user_id, project_id, data: schemas.UpdateCustomMetricsSche "user_id": user_id, "project_id": project_id, "view_type": data.view_type, "metric_type": data.metric_type, "metric_of": data.metric_of, "metric_value": data.metric_value, "metric_format": data.metric_format, - "config": json.dumps(data.config.dict())} + "config": json.dumps(data.default_config.dict()), "thumbnail": data.thumbnail} for i, s in enumerate(data.series): prefix = "u_" if s.index is None: @@ -318,16 +354,33 @@ def update(metric_id, user_id, project_id, data: schemas.UpdateCustomMetricsSche metric_of= %(metric_of)s, metric_value= %(metric_value)s, metric_format= %(metric_format)s, edited_at = timezone('utc'::text, now()), - default_config = %(config)s + default_config = %(config)s, + thumbnail = %(thumbnail)s WHERE metric_id = %(metric_id)s AND project_id = %(project_id)s AND (user_id = %(user_id)s OR is_public) RETURNING metric_id;""", params) cur.execute(query) - return get(metric_id=metric_id, project_id=project_id, user_id=user_id) + return get_card(metric_id=metric_id, project_id=project_id, user_id=user_id) -def get_all(project_id, user_id, include_series=False): +def search_all(project_id, user_id, data: schemas.SearchCardsSchema, include_series=False): + constraints = ["metrics.project_id = %(project_id)s", + "metrics.deleted_at ISNULL"] + params = {"project_id": project_id, "user_id": user_id, + "offset": (data.page - 1) * data.limit, + "limit": data.limit, } + if data.mine_only: + constraints.append("user_id = %(user_id)s") + else: + constraints.append("(user_id = %(user_id)s OR metrics.is_public)") + if data.shared_only: + constraints.append("is_public") + + if data.query is not None and len(data.query) > 0: + constraints.append("(name ILIKE %(query)s OR owner.owner_email ILIKE %(query)s)") + params["query"] = helper.values_for_operator(value=data.query, + op=schemas.SearchEventOperator._contains) with pg_client.PostgresClient() as cur: sub_join = "" if include_series: @@ -336,35 +389,32 @@ def get_all(project_id, user_id, include_series=False): WHERE metric_series.metric_id = metrics.metric_id AND metric_series.deleted_at ISNULL ) AS metric_series ON (TRUE)""" - cur.execute( - cur.mogrify( - f"""SELECT * - FROM metrics - {sub_join} - LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(connected_dashboards.* ORDER BY is_public,name),'[]'::jsonb) AS dashboards - FROM (SELECT DISTINCT dashboard_id, name, is_public - FROM dashboards INNER JOIN dashboard_widgets USING (dashboard_id) - WHERE deleted_at ISNULL - AND dashboard_widgets.metric_id = metrics.metric_id - AND project_id = %(project_id)s - AND ((dashboards.user_id = %(user_id)s OR is_public))) AS connected_dashboards - ) AS connected_dashboards ON (TRUE) - LEFT JOIN LATERAL (SELECT email AS owner_email - FROM users - WHERE deleted_at ISNULL - AND users.user_id = metrics.user_id - ) AS owner ON (TRUE) - WHERE metrics.project_id = %(project_id)s - AND metrics.deleted_at ISNULL - AND (user_id = %(user_id)s OR metrics.is_public) - ORDER BY metrics.edited_at DESC, metrics.created_at DESC;""", - {"project_id": project_id, "user_id": user_id} - ) - ) + query = cur.mogrify( + f"""SELECT metric_id, project_id, user_id, name, is_public, created_at, edited_at, + metric_type, metric_of, metric_format, metric_value, view_type, is_pinned, + dashboards, owner_email, default_config AS config, thumbnail + FROM metrics + {sub_join} + LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(connected_dashboards.* ORDER BY is_public,name),'[]'::jsonb) AS dashboards + FROM (SELECT DISTINCT dashboard_id, name, is_public + FROM dashboards INNER JOIN dashboard_widgets USING (dashboard_id) + WHERE deleted_at ISNULL + AND dashboard_widgets.metric_id = metrics.metric_id + AND project_id = %(project_id)s + AND ((dashboards.user_id = %(user_id)s OR is_public))) AS connected_dashboards + ) AS connected_dashboards ON (TRUE) + LEFT JOIN LATERAL (SELECT email AS owner_email + FROM users + WHERE deleted_at ISNULL + AND users.user_id = metrics.user_id + ) AS owner ON (TRUE) + WHERE {" AND ".join(constraints)} + ORDER BY created_at {data.order.value} + LIMIT %(limit)s OFFSET %(offset)s;""", params) + cur.execute(query) rows = cur.fetchall() if include_series: for r in rows: - # r["created_at"] = TimeUTC.datetime_to_timestamp(r["created_at"]) for s in r["series"]: s["filter"] = helper.old_search_payload_to_flat(s["filter"]) else: @@ -375,6 +425,17 @@ def get_all(project_id, user_id, include_series=False): return rows +def get_all(project_id, user_id): + default_search = schemas.SearchCardsSchema() + result = rows = search_all(project_id=project_id, user_id=user_id, data=default_search) + while len(rows) == default_search.limit: + default_search.page += 1 + rows = search_all(project_id=project_id, user_id=user_id, data=default_search) + result += rows + + return result + + def delete(project_id, metric_id, user_id): with pg_client.PostgresClient() as cur: cur.execute( @@ -390,37 +451,40 @@ def delete(project_id, metric_id, user_id): return {"state": "success"} -def get(metric_id, project_id, user_id, flatten=True): +def get_card(metric_id, project_id, user_id, flatten: bool = True, include_data: bool = False): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - """SELECT *, default_config AS config - FROM metrics - LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(metric_series.* ORDER BY index),'[]'::jsonb) AS series - FROM metric_series - WHERE metric_series.metric_id = metrics.metric_id - AND metric_series.deleted_at ISNULL - ) AS metric_series ON (TRUE) - LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(connected_dashboards.* ORDER BY is_public,name),'[]'::jsonb) AS dashboards - FROM (SELECT dashboard_id, name, is_public - FROM dashboards - WHERE deleted_at ISNULL - AND project_id = %(project_id)s - AND ((user_id = %(user_id)s OR is_public))) AS connected_dashboards - ) AS connected_dashboards ON (TRUE) - LEFT JOIN LATERAL (SELECT email AS owner_email - FROM users - WHERE deleted_at ISNULL - AND users.user_id = metrics.user_id - ) AS owner ON (TRUE) - WHERE metrics.project_id = %(project_id)s - AND metrics.deleted_at ISNULL - AND (metrics.user_id = %(user_id)s OR metrics.is_public) - AND metrics.metric_id = %(metric_id)s - ORDER BY created_at;""", - {"metric_id": metric_id, "project_id": project_id, "user_id": user_id} - ) + query = cur.mogrify( + f"""SELECT metric_id, project_id, user_id, name, is_public, created_at, deleted_at, edited_at, metric_type, + view_type, metric_of, metric_value, metric_format, is_pinned, default_config, + default_config AS config,series, dashboards, owner_email + {',data' if include_data else ''} + FROM metrics + LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(metric_series.* ORDER BY index),'[]'::jsonb) AS series + FROM metric_series + WHERE metric_series.metric_id = metrics.metric_id + AND metric_series.deleted_at ISNULL + ) AS metric_series ON (TRUE) + LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(connected_dashboards.* ORDER BY is_public,name),'[]'::jsonb) AS dashboards + FROM (SELECT dashboard_id, name, is_public + FROM dashboards INNER JOIN dashboard_widgets USING (dashboard_id) + WHERE deleted_at ISNULL + AND project_id = %(project_id)s + AND ((dashboards.user_id = %(user_id)s OR is_public)) + AND metric_id = %(metric_id)s) AS connected_dashboards + ) AS connected_dashboards ON (TRUE) + LEFT JOIN LATERAL (SELECT email AS owner_email + FROM users + WHERE deleted_at ISNULL + AND users.user_id = metrics.user_id + ) AS owner ON (TRUE) + WHERE metrics.project_id = %(project_id)s + AND metrics.deleted_at ISNULL + AND (metrics.user_id = %(user_id)s OR metrics.is_public) + AND metrics.metric_id = %(metric_id)s + ORDER BY created_at;""", + {"metric_id": metric_id, "project_id": project_id, "user_id": user_id} ) + cur.execute(query) row = cur.fetchone() if row is None: return None @@ -432,39 +496,6 @@ def get(metric_id, project_id, user_id, flatten=True): return helper.dict_to_camel_case(row) -def get_with_template(metric_id, project_id, user_id, include_dashboard=True): - with pg_client.PostgresClient() as cur: - sub_query = "" - if include_dashboard: - sub_query = """LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(connected_dashboards.* ORDER BY is_public,name),'[]'::jsonb) AS dashboards - FROM (SELECT dashboard_id, name, is_public - FROM dashboards - WHERE deleted_at ISNULL - AND project_id = %(project_id)s - AND ((user_id = %(user_id)s OR is_public))) AS connected_dashboards - ) AS connected_dashboards ON (TRUE)""" - cur.execute( - cur.mogrify( - f"""SELECT *, default_config AS config - FROM metrics - LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(metric_series.* ORDER BY index),'[]'::jsonb) AS series - FROM metric_series - WHERE metric_series.metric_id = metrics.metric_id - AND metric_series.deleted_at ISNULL - ) AS metric_series ON (TRUE) - {sub_query} - WHERE (metrics.project_id = %(project_id)s OR metrics.project_id ISNULL) - AND metrics.deleted_at ISNULL - AND (metrics.user_id = %(user_id)s OR metrics.is_public) - AND metrics.metric_id = %(metric_id)s - ORDER BY created_at;""", - {"metric_id": metric_id, "project_id": project_id, "user_id": user_id} - ) - ) - row = cur.fetchone() - return helper.dict_to_camel_case(row) - - def get_series_for_alert(project_id, user_id): with pg_client.PostgresClient() as cur: cur.execute( @@ -499,17 +530,18 @@ def change_state(project_id, metric_id, user_id, status): AND (user_id = %(user_id)s OR is_public);""", {"metric_id": metric_id, "status": status, "user_id": user_id}) ) - return get(metric_id=metric_id, project_id=project_id, user_id=user_id) + return get_card(metric_id=metric_id, project_id=project_id, user_id=user_id) def get_funnel_sessions_by_issue(user_id, project_id, metric_id, issue_id, - data: schemas.CustomMetricSessionsPayloadSchema + data: schemas.CardSessionsSchema # , range_value=None, start_date=None, end_date=None ): - metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) + metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) if metric is None: return None - metric: schemas.CreateCustomMetricsSchema = __merge_metric_with_data(metric=metric, data=data) + metric: schemas.CreateCardSchema = schemas.CreateCardSchema(**metric) + metric: schemas.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) if metric is None: return None for s in metric.series: @@ -538,3 +570,81 @@ def get_funnel_sessions_by_issue(user_id, project_id, metric_id, issue_id, issue=issue, data=s.filter) if issue is not None else {"total": 0, "sessions": []}, "issue": issue} + + +def make_chart_from_card(project_id, user_id, metric_id, data: schemas.CardChartSchema): + raw_metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, include_data=True) + if raw_metric is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="card not found") + metric: schemas.CreateCardSchema = schemas.CreateCardSchema(**raw_metric) + if metric.is_template: + return get_predefined_metric(key=metric.metric_of, project_id=project_id, data=data.dict()) + elif __is_click_map(metric): + if raw_metric["data"]: + keys = sessions_mobs. \ + __get_mob_keys(project_id=project_id, session_id=raw_metric["data"]["sessionId"]) + mob_exists = False + for k in keys: + if s3.exists(bucket=config("sessions_bucket"), key=k): + mob_exists = True + break + if mob_exists: + raw_metric["data"]['domURL'] = sessions_mobs.get_urls(session_id=raw_metric["data"]["sessionId"], + project_id=project_id) + raw_metric["data"]['mobsUrl'] = sessions_mobs.get_urls_depercated( + session_id=raw_metric["data"]["sessionId"]) + return raw_metric["data"] + + return make_chart(project_id=project_id, user_id=user_id, data=data, metric=metric) + + +PREDEFINED = {schemas.MetricOfWebVitals.count_sessions: metrics.get_processed_sessions, + schemas.MetricOfWebVitals.avg_image_load_time: metrics.get_application_activity_avg_image_load_time, + schemas.MetricOfWebVitals.avg_page_load_time: metrics.get_application_activity_avg_page_load_time, + schemas.MetricOfWebVitals.avg_request_load_time: metrics.get_application_activity_avg_request_load_time, + schemas.MetricOfWebVitals.avg_dom_content_load_start: metrics.get_page_metrics_avg_dom_content_load_start, + schemas.MetricOfWebVitals.avg_first_contentful_pixel: metrics.get_page_metrics_avg_first_contentful_pixel, + schemas.MetricOfWebVitals.avg_visited_pages: metrics.get_user_activity_avg_visited_pages, + schemas.MetricOfWebVitals.avg_session_duration: metrics.get_user_activity_avg_session_duration, + schemas.MetricOfWebVitals.avg_pages_dom_buildtime: metrics.get_pages_dom_build_time, + schemas.MetricOfWebVitals.avg_pages_response_time: metrics.get_pages_response_time, + schemas.MetricOfWebVitals.avg_response_time: metrics.get_top_metrics_avg_response_time, + schemas.MetricOfWebVitals.avg_first_paint: metrics.get_top_metrics_avg_first_paint, + schemas.MetricOfWebVitals.avg_dom_content_loaded: metrics.get_top_metrics_avg_dom_content_loaded, + schemas.MetricOfWebVitals.avg_till_first_byte: metrics.get_top_metrics_avg_till_first_bit, + schemas.MetricOfWebVitals.avg_time_to_interactive: metrics.get_top_metrics_avg_time_to_interactive, + schemas.MetricOfWebVitals.count_requests: metrics.get_top_metrics_count_requests, + schemas.MetricOfWebVitals.avg_time_to_render: metrics.get_time_to_render, + schemas.MetricOfWebVitals.avg_used_js_heap_size: metrics.get_memory_consumption, + schemas.MetricOfWebVitals.avg_cpu: metrics.get_avg_cpu, + schemas.MetricOfWebVitals.avg_fps: metrics.get_avg_fps, + schemas.MetricOfErrors.impacted_sessions_by_js_errors: metrics.get_impacted_sessions_by_js_errors, + schemas.MetricOfErrors.domains_errors_4xx: metrics.get_domains_errors_4xx, + schemas.MetricOfErrors.domains_errors_5xx: metrics.get_domains_errors_5xx, + schemas.MetricOfErrors.errors_per_domains: metrics.get_errors_per_domains, + schemas.MetricOfErrors.calls_errors: metrics.get_calls_errors, + schemas.MetricOfErrors.errors_per_type: metrics.get_errors_per_type, + schemas.MetricOfErrors.resources_by_party: metrics.get_resources_by_party, + schemas.MetricOfPerformance.speed_location: metrics.get_speed_index_location, + schemas.MetricOfPerformance.slowest_domains: metrics.get_slowest_domains, + schemas.MetricOfPerformance.sessions_per_browser: metrics.get_sessions_per_browser, + schemas.MetricOfPerformance.time_to_render: metrics.get_time_to_render, + schemas.MetricOfPerformance.impacted_sessions_by_slow_pages: metrics.get_impacted_sessions_by_slow_pages, + schemas.MetricOfPerformance.memory_consumption: metrics.get_memory_consumption, + schemas.MetricOfPerformance.cpu: metrics.get_avg_cpu, + schemas.MetricOfPerformance.fps: metrics.get_avg_fps, + schemas.MetricOfPerformance.crashes: metrics.get_crashes, + schemas.MetricOfPerformance.resources_vs_visually_complete: metrics.get_resources_vs_visually_complete, + schemas.MetricOfPerformance.pages_dom_buildtime: metrics.get_pages_dom_build_time, + schemas.MetricOfPerformance.pages_response_time: metrics.get_pages_response_time, + schemas.MetricOfPerformance.pages_response_time_distribution: metrics.get_pages_response_time_distribution, + schemas.MetricOfResources.missing_resources: metrics.get_missing_resources_trend, + schemas.MetricOfResources.slowest_resources: metrics.get_slowest_resources, + schemas.MetricOfResources.resources_loading_time: metrics.get_resources_loading_time, + schemas.MetricOfResources.resource_type_vs_response_end: metrics.resource_type_vs_response_end, + schemas.MetricOfResources.resources_count_by_type: metrics.get_resources_count_by_type, } + + +def get_predefined_metric(key: Union[schemas.MetricOfWebVitals, schemas.MetricOfErrors, \ + schemas.MetricOfPerformance, schemas.MetricOfResources], project_id: int, data: dict): + return PREDEFINED.get(key, lambda *args: None)(project_id=project_id, **data) diff --git a/api/chalicelib/core/dashboards.py b/api/chalicelib/core/dashboards.py index ac98b44e7..89f56176b 100644 --- a/api/chalicelib/core/dashboards.py +++ b/api/chalicelib/core/dashboards.py @@ -1,48 +1,11 @@ import json import schemas -from chalicelib.core import custom_metrics, metrics +from chalicelib.core import custom_metrics from chalicelib.utils import helper from chalicelib.utils import pg_client from chalicelib.utils.TimeUTC import TimeUTC -# category name should be lower cased -CATEGORY_DESCRIPTION = { - 'web vitals': 'A set of metrics that assess app performance on criteria such as load time, load performance, and stability.', - 'custom': 'Previously created custom metrics by me and my team.', - 'errors': 'Keep a closer eye on errors and track their type, origin and domain.', - 'performance': 'Optimize your app’s performance by tracking slow domains, page response times, memory consumption, CPU usage and more.', - 'resources': 'Find out which resources are missing and those that may be slowing your web app.' -} - - -def get_templates(project_id, user_id): - with pg_client.PostgresClient() as cur: - pg_query = cur.mogrify(f"""SELECT category, jsonb_agg(metrics ORDER BY name) AS widgets - FROM (SELECT * , default_config AS config - FROM metrics LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(metric_series.* ORDER BY index), '[]'::jsonb) AS series - FROM metric_series - WHERE metric_series.metric_id = metrics.metric_id - AND metric_series.deleted_at ISNULL - ) AS metric_series ON (TRUE) - WHERE deleted_at IS NULL - AND (project_id ISNULL OR (project_id = %(project_id)s AND (is_public OR user_id= %(userId)s))) - ) AS metrics - GROUP BY category - ORDER BY ARRAY_POSITION(ARRAY ['custom','overview','errors','performance','resources'], category);""", - {"project_id": project_id, "userId": user_id}) - cur.execute(pg_query) - rows = cur.fetchall() - for r in rows: - r["description"] = CATEGORY_DESCRIPTION.get(r["category"].lower(), "") - for w in r["widgets"]: - w["created_at"] = TimeUTC.datetime_to_timestamp(w["created_at"]) - w["edited_at"] = TimeUTC.datetime_to_timestamp(w["edited_at"]) - for s in w["series"]: - s["filter"] = helper.old_search_payload_to_flat(s["filter"]) - - return helper.list_to_camel_case(rows) - def create_dashboard(project_id, user_id, data: schemas.CreateDashboardSchema): with pg_client.PostgresClient() as cur: @@ -87,13 +50,23 @@ def get_dashboard(project_id, user_id, dashboard_id): pg_query = """SELECT dashboards.*, all_metric_widgets.widgets AS widgets FROM dashboards LEFT JOIN LATERAL (SELECT COALESCE(JSONB_AGG(raw_metrics), '[]') AS widgets - FROM (SELECT dashboard_widgets.*, metrics.*, metric_series.series + FROM (SELECT dashboard_widgets.*, + metrics.name, metrics.edited_at,metrics.metric_of, + metrics.view_type,metrics.thumbnail,metrics.metric_type, + metrics.metric_format,metrics.metric_value,metrics.default_config, + metric_series.series FROM metrics - INNER JOIN dashboard_widgets USING (metric_id) - LEFT JOIN LATERAL (SELECT COALESCE(JSONB_AGG(metric_series.* ORDER BY index),'[]') AS series - FROM metric_series - WHERE metric_series.metric_id = metrics.metric_id - AND metric_series.deleted_at ISNULL + INNER JOIN dashboard_widgets USING (metric_id) + LEFT JOIN LATERAL ( + SELECT COALESCE(JSONB_AGG(metric_series.* ORDER BY index),'[]') AS series + FROM (SELECT metric_series.name, + metric_series.index, + metric_series.metric_id, + metric_series.series_id, + metric_series.created_at + FROM metric_series + WHERE metric_series.metric_id = metrics.metric_id + AND metric_series.deleted_at ISNULL) AS metric_series ) AS metric_series ON (TRUE) WHERE dashboard_widgets.dashboard_id = dashboards.dashboard_id AND metrics.deleted_at ISNULL @@ -113,6 +86,7 @@ def get_dashboard(project_id, user_id, dashboard_id): w["edited_at"] = TimeUTC.datetime_to_timestamp(w["edited_at"]) w["config"]["col"] = w["default_config"]["col"] w["config"]["row"] = w["default_config"]["row"] + w.pop("default_config") for s in w["series"]: s["created_at"] = TimeUTC.datetime_to_timestamp(s["created_at"]) return helper.dict_to_camel_case(row) @@ -140,17 +114,19 @@ def update_dashboard(project_id, user_id, dashboard_id, data: schemas.EditDashbo row = cur.fetchone() offset = row["count"] pg_query = f"""UPDATE dashboards - SET name = %(name)s, + SET name = %(name)s, description= %(description)s {", is_public = %(is_public)s" if data.is_public is not None else ""} {", is_pinned = %(is_pinned)s" if data.is_pinned is not None else ""} - WHERE dashboards.project_id = %(projectId)s + WHERE dashboards.project_id = %(projectId)s AND dashboard_id = %(dashboard_id)s - AND (dashboards.user_id = %(userId)s OR is_public)""" + AND (dashboards.user_id = %(userId)s OR is_public) + RETURNING dashboard_id,name,description,is_public,created_at;""" if data.metrics is not None and len(data.metrics) > 0: pg_query = f"""WITH dash AS ({pg_query}) - INSERT INTO dashboard_widgets(dashboard_id, metric_id, user_id, config) - VALUES {",".join([f"(%(dashboard_id)s, %(metric_id_{i})s, %(userId)s, (SELECT default_config FROM metrics WHERE metric_id=%(metric_id_{i})s)||%(config_{i})s)" for i in range(len(data.metrics))])};""" + INSERT INTO dashboard_widgets(dashboard_id, metric_id, user_id, config) + VALUES {",".join([f"(%(dashboard_id)s, %(metric_id_{i})s, %(userId)s, (SELECT default_config FROM metrics WHERE metric_id=%(metric_id_{i})s)||%(config_{i})s)" for i in range(len(data.metrics))])} + RETURNING dash.*;""" for i, m in enumerate(data.metrics): params[f"metric_id_{i}"] = m # params[f"config_{i}"] = schemas.AddWidgetToDashboardPayloadSchema.schema() \ @@ -160,8 +136,10 @@ def update_dashboard(project_id, user_id, dashboard_id, data: schemas.EditDashbo params[f"config_{i}"] = json.dumps({"position": i + offset}) cur.execute(cur.mogrify(pg_query, params)) - - return get_dashboard(project_id=project_id, user_id=user_id, dashboard_id=dashboard_id) + row = cur.fetchone() + if row: + row["created_at"] = TimeUTC.datetime_to_timestamp(row["created_at"]) + return helper.dict_to_camel_case(row) def get_widget(project_id, user_id, dashboard_id, widget_id): @@ -243,86 +221,18 @@ def pin_dashboard(project_id, user_id, dashboard_id): return helper.dict_to_camel_case(row) -def create_metric_add_widget(project_id, user_id, dashboard_id, data: schemas.CreateCustomMetricsSchema): +def create_metric_add_widget(project_id, user_id, dashboard_id, data: schemas.CreateCardSchema): metric_id = custom_metrics.create(project_id=project_id, user_id=user_id, data=data, dashboard=True) return add_widget(project_id=project_id, user_id=user_id, dashboard_id=dashboard_id, data=schemas.AddWidgetToDashboardPayloadSchema(metricId=metric_id)) - -PREDEFINED = {schemas.TemplatePredefinedKeys.count_sessions: metrics.get_processed_sessions, - schemas.TemplatePredefinedKeys.avg_image_load_time: metrics.get_application_activity_avg_image_load_time, - schemas.TemplatePredefinedKeys.avg_page_load_time: metrics.get_application_activity_avg_page_load_time, - schemas.TemplatePredefinedKeys.avg_request_load_time: metrics.get_application_activity_avg_request_load_time, - schemas.TemplatePredefinedKeys.avg_dom_content_load_start: metrics.get_page_metrics_avg_dom_content_load_start, - schemas.TemplatePredefinedKeys.avg_first_contentful_pixel: metrics.get_page_metrics_avg_first_contentful_pixel, - schemas.TemplatePredefinedKeys.avg_visited_pages: metrics.get_user_activity_avg_visited_pages, - schemas.TemplatePredefinedKeys.avg_session_duration: metrics.get_user_activity_avg_session_duration, - schemas.TemplatePredefinedKeys.avg_pages_dom_buildtime: metrics.get_pages_dom_build_time, - schemas.TemplatePredefinedKeys.avg_pages_response_time: metrics.get_pages_response_time, - schemas.TemplatePredefinedKeys.avg_response_time: metrics.get_top_metrics_avg_response_time, - schemas.TemplatePredefinedKeys.avg_first_paint: metrics.get_top_metrics_avg_first_paint, - schemas.TemplatePredefinedKeys.avg_dom_content_loaded: metrics.get_top_metrics_avg_dom_content_loaded, - schemas.TemplatePredefinedKeys.avg_till_first_bit: metrics.get_top_metrics_avg_till_first_bit, - schemas.TemplatePredefinedKeys.avg_time_to_interactive: metrics.get_top_metrics_avg_time_to_interactive, - schemas.TemplatePredefinedKeys.count_requests: metrics.get_top_metrics_count_requests, - schemas.TemplatePredefinedKeys.avg_time_to_render: metrics.get_time_to_render, - schemas.TemplatePredefinedKeys.avg_used_js_heap_size: metrics.get_memory_consumption, - schemas.TemplatePredefinedKeys.avg_cpu: metrics.get_avg_cpu, - schemas.TemplatePredefinedKeys.avg_fps: metrics.get_avg_fps, - schemas.TemplatePredefinedKeys.impacted_sessions_by_js_errors: metrics.get_impacted_sessions_by_js_errors, - schemas.TemplatePredefinedKeys.domains_errors_4xx: metrics.get_domains_errors_4xx, - schemas.TemplatePredefinedKeys.domains_errors_5xx: metrics.get_domains_errors_5xx, - schemas.TemplatePredefinedKeys.errors_per_domains: metrics.get_errors_per_domains, - schemas.TemplatePredefinedKeys.calls_errors: metrics.get_calls_errors, - schemas.TemplatePredefinedKeys.errors_by_type: metrics.get_errors_per_type, - schemas.TemplatePredefinedKeys.errors_by_origin: metrics.get_resources_by_party, - schemas.TemplatePredefinedKeys.speed_index_by_location: metrics.get_speed_index_location, - schemas.TemplatePredefinedKeys.slowest_domains: metrics.get_slowest_domains, - schemas.TemplatePredefinedKeys.sessions_per_browser: metrics.get_sessions_per_browser, - schemas.TemplatePredefinedKeys.time_to_render: metrics.get_time_to_render, - schemas.TemplatePredefinedKeys.impacted_sessions_by_slow_pages: metrics.get_impacted_sessions_by_slow_pages, - schemas.TemplatePredefinedKeys.memory_consumption: metrics.get_memory_consumption, - schemas.TemplatePredefinedKeys.cpu_load: metrics.get_avg_cpu, - schemas.TemplatePredefinedKeys.frame_rate: metrics.get_avg_fps, - schemas.TemplatePredefinedKeys.crashes: metrics.get_crashes, - schemas.TemplatePredefinedKeys.resources_vs_visually_complete: metrics.get_resources_vs_visually_complete, - schemas.TemplatePredefinedKeys.pages_dom_buildtime: metrics.get_pages_dom_build_time, - schemas.TemplatePredefinedKeys.pages_response_time: metrics.get_pages_response_time, - schemas.TemplatePredefinedKeys.pages_response_time_distribution: metrics.get_pages_response_time_distribution, - schemas.TemplatePredefinedKeys.missing_resources: metrics.get_missing_resources_trend, - schemas.TemplatePredefinedKeys.slowest_resources: metrics.get_slowest_resources, - schemas.TemplatePredefinedKeys.resources_fetch_time: metrics.get_resources_loading_time, - schemas.TemplatePredefinedKeys.resource_type_vs_response_end: metrics.resource_type_vs_response_end, - schemas.TemplatePredefinedKeys.resources_count_by_type: metrics.get_resources_count_by_type, - } - - -def get_predefined_metric(key: schemas.TemplatePredefinedKeys, project_id: int, data: dict): - return PREDEFINED.get(key, lambda *args: None)(project_id=project_id, **data) - - -def make_chart_metrics(project_id, user_id, metric_id, data: schemas.CustomMetricChartPayloadSchema): - raw_metric = custom_metrics.get_with_template(metric_id=metric_id, project_id=project_id, user_id=user_id, - include_dashboard=False) - if raw_metric is None: - return None - metric: schemas.CustomMetricAndTemplate = schemas.CustomMetricAndTemplate.parse_obj(raw_metric) - if metric.is_template and metric.predefined_key is None: - return None - if metric.is_template: - return get_predefined_metric(key=metric.predefined_key, project_id=project_id, data=data.dict()) - else: - return custom_metrics.make_chart(project_id=project_id, user_id=user_id, metric_id=metric_id, data=data, - metric=raw_metric) - - -def make_chart_widget(dashboard_id, project_id, user_id, widget_id, data: schemas.CustomMetricChartPayloadSchema): - raw_metric = get_widget(widget_id=widget_id, project_id=project_id, user_id=user_id, dashboard_id=dashboard_id) - if raw_metric is None: - return None - metric = schemas.CustomMetricAndTemplate = schemas.CustomMetricAndTemplate.parse_obj(raw_metric) - if metric.is_template: - return get_predefined_metric(key=metric.predefined_key, project_id=project_id, data=data.dict()) - else: - return custom_metrics.make_chart(project_id=project_id, user_id=user_id, metric_id=raw_metric["metricId"], - data=data, metric=raw_metric) +# def make_chart_widget(dashboard_id, project_id, user_id, widget_id, data: schemas.CardChartSchema): +# raw_metric = get_widget(widget_id=widget_id, project_id=project_id, user_id=user_id, dashboard_id=dashboard_id) +# if raw_metric is None: +# return None +# metric = schemas.CustomMetricAndTemplate = schemas.CustomMetricAndTemplate(**raw_metric) +# if metric.is_template: +# return get_predefined_metric(key=metric.predefined_key, project_id=project_id, data=data.dict()) +# else: +# return custom_metrics.make_chart(project_id=project_id, user_id=user_id, metric_id=raw_metric["metricId"], +# data=data, metric=raw_metric) diff --git a/api/chalicelib/core/errors.py b/api/chalicelib/core/errors.py index 9c89844f9..3ff0cf584 100644 --- a/api/chalicelib/core/errors.py +++ b/api/chalicelib/core/errors.py @@ -2,6 +2,7 @@ import json import schemas from chalicelib.core import sourcemaps, sessions +from chalicelib.utils import errors_helper from chalicelib.utils import pg_client, helper from chalicelib.utils.TimeUTC import TimeUTC from chalicelib.utils.metrics_helper import __get_step_size @@ -277,7 +278,7 @@ def get_details(project_id, error_id, user_id, **data): status = cur.fetchone() if status is not None: - row["stack"] = format_first_stack_frame(status).pop("stack") + row["stack"] = errors_helper.format_first_stack_frame(status).pop("stack") row["status"] = status.pop("status") row["parent_error_id"] = status.pop("parent_error_id") row["favorite"] = status.pop("favorite") @@ -469,9 +470,9 @@ def search(data: schemas.SearchErrorsSchema, project_id, user_id): sort = __get_sort_key('datetime') if data.sort is not None: sort = __get_sort_key(data.sort) - order = schemas.SortOrderType.desc + order = schemas.SortOrderType.desc.value if data.order is not None: - order = data.order + order = data.order.value extra_join = "" params = { @@ -721,19 +722,6 @@ def __status_rank(status): }.get(status) -def format_first_stack_frame(error): - error["stack"] = sourcemaps.format_payload(error.pop("payload"), truncate_to_first=True) - for s in error["stack"]: - for c in s.get("context", []): - for sci, sc in enumerate(c): - if isinstance(sc, str) and len(sc) > 1000: - c[sci] = sc[:1000] - # convert bytes to string: - if isinstance(s["filename"], bytes): - s["filename"] = s["filename"].decode("utf-8") - return error - - def stats(project_id, user_id, startTimestamp=TimeUTC.now(delta_days=-7), endTimestamp=TimeUTC.now()): with pg_client.PostgresClient() as cur: query = cur.mogrify( diff --git a/api/chalicelib/core/events.py b/api/chalicelib/core/events.py index 4887c94b1..17f601180 100644 --- a/api/chalicelib/core/events.py +++ b/api/chalicelib/core/events.py @@ -1,16 +1,15 @@ -import schemas -from chalicelib.core import issues -from chalicelib.core import metadata -from chalicelib.core import sessions_metas +from typing import Optional +import schemas +from chalicelib.core import autocomplete +from chalicelib.core import issues +from chalicelib.core import sessions_metas from chalicelib.utils import pg_client, helper from chalicelib.utils.TimeUTC import TimeUTC from chalicelib.utils.event_filter_definition import SupportedFilter, Event -from chalicelib.core import autocomplete - -def get_customs_by_sessionId2_pg(session_id, project_id): +def get_customs_by_session_id(session_id, project_id): with pg_client.PostgresClient() as cur: cur.execute(cur.mogrify("""\ SELECT @@ -53,50 +52,53 @@ def __get_grouped_clickrage(rows, session_id, project_id): return rows -def get_by_sessionId2_pg(session_id, project_id, group_clickrage=False): +def get_by_session_id(session_id, project_id, group_clickrage=False, event_type: Optional[schemas.EventType] = None): with pg_client.PostgresClient() as cur: - cur.execute(cur.mogrify("""\ - SELECT - c.*, - 'CLICK' AS type - FROM events.clicks AS c - WHERE - c.session_id = %(session_id)s - ORDER BY c.timestamp;""", - {"project_id": project_id, "session_id": session_id}) - ) - rows = cur.fetchall() - if group_clickrage: - rows = __get_grouped_clickrage(rows=rows, session_id=session_id, project_id=project_id) - - cur.execute(cur.mogrify(""" - SELECT - i.*, - 'INPUT' AS type - FROM events.inputs AS i - WHERE - i.session_id = %(session_id)s - ORDER BY i.timestamp;""", - {"project_id": project_id, "session_id": session_id}) - ) - rows += cur.fetchall() - cur.execute(cur.mogrify("""\ - SELECT - l.*, - l.path AS value, - l.path AS url, - 'LOCATION' AS type - FROM events.pages AS l - WHERE - l.session_id = %(session_id)s - ORDER BY l.timestamp;""", {"project_id": project_id, "session_id": session_id})) - rows += cur.fetchall() + rows = [] + if event_type is None or event_type == schemas.EventType.click: + cur.execute(cur.mogrify("""\ + SELECT + c.*, + 'CLICK' AS type + FROM events.clicks AS c + WHERE + c.session_id = %(session_id)s + ORDER BY c.timestamp;""", + {"project_id": project_id, "session_id": session_id}) + ) + rows += cur.fetchall() + if group_clickrage: + rows = __get_grouped_clickrage(rows=rows, session_id=session_id, project_id=project_id) + if event_type is None or event_type == schemas.EventType.input: + cur.execute(cur.mogrify(""" + SELECT + i.*, + 'INPUT' AS type + FROM events.inputs AS i + WHERE + i.session_id = %(session_id)s + ORDER BY i.timestamp;""", + {"project_id": project_id, "session_id": session_id}) + ) + rows += cur.fetchall() + if event_type is None or event_type == schemas.EventType.location: + cur.execute(cur.mogrify("""\ + SELECT + l.*, + l.path AS value, + l.path AS url, + 'LOCATION' AS type + FROM events.pages AS l + WHERE + l.session_id = %(session_id)s + ORDER BY l.timestamp;""", {"project_id": project_id, "session_id": session_id})) + rows += cur.fetchall() rows = helper.list_to_camel_case(rows) rows = sorted(rows, key=lambda k: (k["timestamp"], k["messageId"])) return rows -class event_type: +class EventType: CLICK = Event(ui_type=schemas.EventType.click, table="events.clicks", column="label") INPUT = Event(ui_type=schemas.EventType.input, table="events.inputs", column="label") LOCATION = Event(ui_type=schemas.EventType.location, table="events.pages", column="path") @@ -118,46 +120,46 @@ class event_type: SUPPORTED_TYPES = { - event_type.CLICK.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.CLICK), - query=autocomplete.__generic_query(typename=event_type.CLICK.ui_type)), - event_type.INPUT.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.INPUT), - query=autocomplete.__generic_query(typename=event_type.INPUT.ui_type)), - event_type.LOCATION.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.LOCATION), - query=autocomplete.__generic_query( - typename=event_type.LOCATION.ui_type)), - event_type.CUSTOM.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.CUSTOM), - query=autocomplete.__generic_query(typename=event_type.CUSTOM.ui_type)), - event_type.REQUEST.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.REQUEST), + EventType.CLICK.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.CLICK), + query=autocomplete.__generic_query(typename=EventType.CLICK.ui_type)), + EventType.INPUT.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.INPUT), + query=autocomplete.__generic_query(typename=EventType.INPUT.ui_type)), + EventType.LOCATION.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.LOCATION), query=autocomplete.__generic_query( - typename=event_type.REQUEST.ui_type)), - event_type.GRAPHQL.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.GRAPHQL), - query=autocomplete.__generic_query( - typename=event_type.GRAPHQL.ui_type)), - event_type.STATEACTION.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.STATEACTION), - query=autocomplete.__generic_query( - typename=event_type.STATEACTION.ui_type)), - event_type.ERROR.ui_type: SupportedFilter(get=autocomplete.__search_pg_errors, - query=None), - event_type.METADATA.ui_type: SupportedFilter(get=autocomplete.__search_pg_metadata, - query=None), - # IOS - event_type.CLICK_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.CLICK_IOS), - query=autocomplete.__generic_query( - typename=event_type.CLICK_IOS.ui_type)), - event_type.INPUT_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.INPUT_IOS), - query=autocomplete.__generic_query( - typename=event_type.INPUT_IOS.ui_type)), - event_type.VIEW_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.VIEW_IOS), - query=autocomplete.__generic_query( - typename=event_type.VIEW_IOS.ui_type)), - event_type.CUSTOM_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.CUSTOM_IOS), + typename=EventType.LOCATION.ui_type)), + EventType.CUSTOM.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.CUSTOM), + query=autocomplete.__generic_query(typename=EventType.CUSTOM.ui_type)), + EventType.REQUEST.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.REQUEST), + query=autocomplete.__generic_query( + typename=EventType.REQUEST.ui_type)), + EventType.GRAPHQL.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.GRAPHQL), + query=autocomplete.__generic_query( + typename=EventType.GRAPHQL.ui_type)), + EventType.STATEACTION.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.STATEACTION), query=autocomplete.__generic_query( - typename=event_type.CUSTOM_IOS.ui_type)), - event_type.REQUEST_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.REQUEST_IOS), - query=autocomplete.__generic_query( - typename=event_type.REQUEST_IOS.ui_type)), - event_type.ERROR_IOS.ui_type: SupportedFilter(get=autocomplete.__search_pg_errors_ios, - query=None), + typename=EventType.STATEACTION.ui_type)), + EventType.ERROR.ui_type: SupportedFilter(get=autocomplete.__search_errors, + query=None), + EventType.METADATA.ui_type: SupportedFilter(get=autocomplete.__search_metadata, + query=None), + # IOS + EventType.CLICK_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.CLICK_IOS), + query=autocomplete.__generic_query( + typename=EventType.CLICK_IOS.ui_type)), + EventType.INPUT_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.INPUT_IOS), + query=autocomplete.__generic_query( + typename=EventType.INPUT_IOS.ui_type)), + EventType.VIEW_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.VIEW_IOS), + query=autocomplete.__generic_query( + typename=EventType.VIEW_IOS.ui_type)), + EventType.CUSTOM_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.CUSTOM_IOS), + query=autocomplete.__generic_query( + typename=EventType.CUSTOM_IOS.ui_type)), + EventType.REQUEST_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.REQUEST_IOS), + query=autocomplete.__generic_query( + typename=EventType.REQUEST_IOS.ui_type)), + EventType.ERROR_IOS.ui_type: SupportedFilter(get=autocomplete.__search_errors_ios, + query=None), } @@ -165,7 +167,7 @@ def get_errors_by_session_id(session_id, project_id): with pg_client.PostgresClient() as cur: cur.execute(cur.mogrify(f"""\ SELECT er.*,ur.*, er.timestamp - s.start_ts AS time - FROM {event_type.ERROR.table} AS er INNER JOIN public.errors AS ur USING (error_id) INNER JOIN public.sessions AS s USING (session_id) + FROM {EventType.ERROR.table} AS er INNER JOIN public.errors AS ur USING (error_id) INNER JOIN public.sessions AS s USING (session_id) WHERE er.session_id = %(session_id)s AND s.project_id=%(project_id)s ORDER BY timestamp;""", {"session_id": session_id, "project_id": project_id})) errors = cur.fetchall() @@ -182,11 +184,9 @@ def search(text, event_type, project_id, source, key): rows = SUPPORTED_TYPES[event_type].get(project_id=project_id, value=text, key=key, source=source) # for IOS events autocomplete # if event_type + "_IOS" in SUPPORTED_TYPES.keys(): - # rows += SUPPORTED_TYPES[event_type + "_IOS"].get(project_id=project_id, value=text, key=key, - # source=source) + # rows += SUPPORTED_TYPES[event_type + "_IOS"].get(project_id=project_id, value=text, key=key,source=source) elif event_type + "_IOS" in SUPPORTED_TYPES.keys(): - rows = SUPPORTED_TYPES[event_type + "_IOS"].get(project_id=project_id, value=text, key=key, - source=source) + rows = SUPPORTED_TYPES[event_type + "_IOS"].get(project_id=project_id, value=text, key=key, source=source) elif event_type in sessions_metas.SUPPORTED_TYPES.keys(): return sessions_metas.search(text, event_type, project_id) elif event_type.endswith("_IOS") \ diff --git a/api/chalicelib/core/events_ios.py b/api/chalicelib/core/events_ios.py index bae48599f..c5205ba15 100644 --- a/api/chalicelib/core/events_ios.py +++ b/api/chalicelib/core/events_ios.py @@ -7,8 +7,8 @@ def get_customs_by_sessionId(session_id, project_id): cur.execute(cur.mogrify(f"""\ SELECT c.*, - '{events.event_type.CUSTOM_IOS.ui_type}' AS type - FROM {events.event_type.CUSTOM_IOS.table} AS c + '{events.EventType.CUSTOM_IOS.ui_type}' AS type + FROM {events.EventType.CUSTOM_IOS.table} AS c WHERE c.session_id = %(session_id)s ORDER BY c.timestamp;""", @@ -23,8 +23,8 @@ def get_by_sessionId(session_id, project_id): cur.execute(cur.mogrify(f""" SELECT c.*, - '{events.event_type.CLICK_IOS.ui_type}' AS type - FROM {events.event_type.CLICK_IOS.table} AS c + '{events.EventType.CLICK_IOS.ui_type}' AS type + FROM {events.EventType.CLICK_IOS.table} AS c WHERE c.session_id = %(session_id)s ORDER BY c.timestamp;""", @@ -35,8 +35,8 @@ def get_by_sessionId(session_id, project_id): cur.execute(cur.mogrify(f""" SELECT i.*, - '{events.event_type.INPUT_IOS.ui_type}' AS type - FROM {events.event_type.INPUT_IOS.table} AS i + '{events.EventType.INPUT_IOS.ui_type}' AS type + FROM {events.EventType.INPUT_IOS.table} AS i WHERE i.session_id = %(session_id)s ORDER BY i.timestamp;""", @@ -46,8 +46,8 @@ def get_by_sessionId(session_id, project_id): cur.execute(cur.mogrify(f""" SELECT v.*, - '{events.event_type.VIEW_IOS.ui_type}' AS type - FROM {events.event_type.VIEW_IOS.table} AS v + '{events.EventType.VIEW_IOS.ui_type}' AS type + FROM {events.EventType.VIEW_IOS.table} AS v WHERE v.session_id = %(session_id)s ORDER BY v.timestamp;""", {"project_id": project_id, "session_id": session_id})) @@ -61,7 +61,7 @@ def get_crashes_by_session_id(session_id): with pg_client.PostgresClient() as cur: cur.execute(cur.mogrify(f""" SELECT cr.*,uc.*, cr.timestamp - s.start_ts AS time - FROM {events.event_type.ERROR_IOS.table} AS cr INNER JOIN public.crashes_ios AS uc USING (crash_id) INNER JOIN public.sessions AS s USING (session_id) + FROM {events.EventType.ERROR_IOS.table} AS cr INNER JOIN public.crashes_ios AS uc USING (crash_id) INNER JOIN public.sessions AS s USING (session_id) WHERE cr.session_id = %(session_id)s ORDER BY timestamp;""", {"session_id": session_id})) diff --git a/api/chalicelib/core/funnels.py b/api/chalicelib/core/funnels.py index 65cb7b09a..c9f51fcc9 100644 --- a/api/chalicelib/core/funnels.py +++ b/api/chalicelib/core/funnels.py @@ -1,16 +1,9 @@ -import json from typing import List -import chalicelib.utils.helper import schemas -from chalicelib.core import significance, sessions -from chalicelib.utils import dev -from chalicelib.utils import helper, pg_client -from chalicelib.utils.TimeUTC import TimeUTC - -REMOVE_KEYS = ["key", "_key", "startDate", "endDate"] - -ALLOW_UPDATE_FOR = ["name", "filter"] +from chalicelib.core import significance +from chalicelib.utils import helper +from chalicelib.utils import sql_helper as sh def filter_stages(stages: List[schemas._SessionSearchEventSchema]): @@ -25,10 +18,6 @@ def __parse_events(f_events: List[dict]): return [schemas._SessionSearchEventSchema.parse_obj(e) for e in f_events] -def __unparse_events(f_events: List[schemas._SessionSearchEventSchema]): - return [e.dict() for e in f_events] - - def __fix_stages(f_events: List[schemas._SessionSearchEventSchema]): if f_events is None: return @@ -39,220 +28,15 @@ def __fix_stages(f_events: List[schemas._SessionSearchEventSchema]): if not isinstance(e.value, list): e.value = [e.value] - is_any = sessions._isAny_opreator(e.operator) + is_any = sh.isAny_opreator(e.operator) if not is_any and isinstance(e.value, list) and len(e.value) == 0: continue events.append(e) return events -def __transform_old_funnels(events): - for e in events: - if not isinstance(e.get("value"), list): - e["value"] = [e["value"]] - return events - - -def create(project_id, user_id, name, filter: schemas.FunnelSearchPayloadSchema, is_public): - helper.delete_keys_from_dict(filter, REMOVE_KEYS) - filter.events = filter_stages(stages=filter.events) - with pg_client.PostgresClient() as cur: - query = cur.mogrify("""\ - INSERT INTO public.funnels (project_id, user_id, name, filter,is_public) - VALUES (%(project_id)s, %(user_id)s, %(name)s, %(filter)s::jsonb,%(is_public)s) - RETURNING *;""", - {"user_id": user_id, "project_id": project_id, "name": name, - "filter": json.dumps(filter.dict()), - "is_public": is_public}) - - cur.execute( - query - ) - r = cur.fetchone() - r["created_at"] = TimeUTC.datetime_to_timestamp(r["created_at"]) - r = helper.dict_to_camel_case(r) - r["filter"]["startDate"], r["filter"]["endDate"] = TimeUTC.get_start_end_from_range(r["filter"]["rangeValue"]) - return {"data": r} - - -def update(funnel_id, user_id, project_id, name=None, filter=None, is_public=None): - s_query = [] - if filter is not None: - helper.delete_keys_from_dict(filter, REMOVE_KEYS) - s_query.append("filter = %(filter)s::jsonb") - if name is not None and len(name) > 0: - s_query.append("name = %(name)s") - if is_public is not None: - s_query.append("is_public = %(is_public)s") - if len(s_query) == 0: - return {"errors": ["Nothing to update"]} - with pg_client.PostgresClient() as cur: - query = cur.mogrify(f"""\ - UPDATE public.funnels - SET {" , ".join(s_query)} - WHERE funnel_id=%(funnel_id)s - AND project_id = %(project_id)s - AND (user_id = %(user_id)s OR is_public) - RETURNING *;""", {"user_id": user_id, "funnel_id": funnel_id, "name": name, - "filter": json.dumps(filter) if filter is not None else None, "is_public": is_public, - "project_id": project_id}) - # print("--------------------") - # print(query) - # print("--------------------") - cur.execute( - query - ) - r = cur.fetchone() - if r is None: - return {"errors": ["funnel not found"]} - r["created_at"] = TimeUTC.datetime_to_timestamp(r["created_at"]) - r = helper.dict_to_camel_case(r) - r["filter"]["startDate"], r["filter"]["endDate"] = TimeUTC.get_start_end_from_range(r["filter"]["rangeValue"]) - r["filter"] = helper.old_search_payload_to_flat(r["filter"]) - return {"data": r} - - -def get_by_user(project_id, user_id, range_value=None, start_date=None, end_date=None, details=False): - with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - f"""\ - SELECT funnel_id, project_id, user_id, name, created_at, deleted_at, is_public - {",filter" if details else ""} - FROM public.funnels - WHERE project_id = %(project_id)s - AND funnels.deleted_at IS NULL - AND (funnels.user_id = %(user_id)s OR funnels.is_public);""", - {"project_id": project_id, "user_id": user_id} - ) - ) - - rows = cur.fetchall() - rows = helper.list_to_camel_case(rows) - for row in rows: - row["createdAt"] = TimeUTC.datetime_to_timestamp(row["createdAt"]) - if details: - row["filter"]["events"] = filter_stages(__parse_events(row["filter"]["events"])) - if row.get("filter") is not None and row["filter"].get("events") is not None: - row["filter"]["events"] = __transform_old_funnels(__unparse_events(row["filter"]["events"])) - - get_start_end_time(filter_d=row["filter"], range_value=range_value, start_date=start_date, - end_date=end_date) - counts = sessions.search_sessions(data=schemas.SessionsSearchPayloadSchema.parse_obj(row["filter"]), - project_id=project_id, user_id=None, count_only=True) - row["sessionsCount"] = counts["countSessions"] - row["usersCount"] = counts["countUsers"] - filter_clone = dict(row["filter"]) - overview = significance.get_overview(filter_d=row["filter"], project_id=project_id) - row["stages"] = overview["stages"] - row.pop("filter") - row["stagesCount"] = len(row["stages"]) - # TODO: ask david to count it alone - row["criticalIssuesCount"] = overview["criticalIssuesCount"] - row["missedConversions"] = 0 if len(row["stages"]) < 2 \ - else row["stages"][0]["sessionsCount"] - row["stages"][-1]["sessionsCount"] - row["filter"] = helper.old_search_payload_to_flat(filter_clone) - return rows - - -def get_possible_issue_types(project_id): - return [{"type": t, "title": chalicelib.utils.helper.get_issue_title(t)} for t in - ['click_rage', 'dead_click', 'excessive_scrolling', - 'bad_request', 'missing_resource', 'memory', 'cpu', - 'slow_resource', 'slow_page_load', 'crash', 'custom_event_error', - 'js_error']] - - -def get_start_end_time(filter_d, range_value, start_date, end_date): - if start_date is not None and end_date is not None: - filter_d["startDate"], filter_d["endDate"] = start_date, end_date - elif range_value is not None and len(range_value) > 0: - filter_d["rangeValue"] = range_value - filter_d["startDate"], filter_d["endDate"] = TimeUTC.get_start_end_from_range(range_value) - else: - filter_d["startDate"], filter_d["endDate"] = TimeUTC.get_start_end_from_range(filter_d["rangeValue"]) - - -def delete(project_id, funnel_id, user_id): - with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""\ - UPDATE public.funnels - SET deleted_at = timezone('utc'::text, now()) - WHERE project_id = %(project_id)s - AND funnel_id = %(funnel_id)s - AND (user_id = %(user_id)s OR is_public);""", - {"funnel_id": funnel_id, "project_id": project_id, "user_id": user_id}) - ) - - return {"data": {"state": "success"}} - - -def get_sessions(project_id, funnel_id, user_id, range_value=None, start_date=None, end_date=None): - f = get(funnel_id=funnel_id, project_id=project_id, user_id=user_id, flatten=False) - if f is None: - return {"errors": ["funnel not found"]} - get_start_end_time(filter_d=f["filter"], range_value=range_value, start_date=start_date, end_date=end_date) - return sessions.search_sessions(data=schemas.SessionsSearchPayloadSchema.parse_obj(f["filter"]), project_id=project_id, - user_id=user_id) - - -def get_sessions_on_the_fly(funnel_id, project_id, user_id, data: schemas.FunnelSearchPayloadSchema): - data.events = filter_stages(data.events) - data.events = __fix_stages(data.events) - if len(data.events) == 0: - f = get(funnel_id=funnel_id, project_id=project_id, user_id=user_id, flatten=False) - if f is None: - return {"errors": ["funnel not found"]} - get_start_end_time(filter_d=f["filter"], range_value=data.range_value, - start_date=data.startDate, end_date=data.endDate) - data = schemas.FunnelSearchPayloadSchema.parse_obj(f["filter"]) - return sessions.search_sessions(data=data, project_id=project_id, - user_id=user_id) - - -def get_top_insights(project_id, user_id, funnel_id, range_value=None, start_date=None, end_date=None): - f = get(funnel_id=funnel_id, project_id=project_id, user_id=user_id, flatten=False) - if f is None: - return {"errors": ["funnel not found"]} - get_start_end_time(filter_d=f["filter"], range_value=range_value, start_date=start_date, end_date=end_date) - insights, total_drop_due_to_issues = significance.get_top_insights(filter_d=f["filter"], project_id=project_id) - insights = helper.list_to_camel_case(insights) - if len(insights) > 0: - # fix: this fix for huge drop count - if total_drop_due_to_issues > insights[0]["sessionsCount"]: - total_drop_due_to_issues = insights[0]["sessionsCount"] - # end fix - insights[-1]["dropDueToIssues"] = total_drop_due_to_issues - return {"data": {"stages": insights, - "totalDropDueToIssues": total_drop_due_to_issues}} - - -def get_top_insights_on_the_fly(funnel_id, user_id, project_id, data: schemas.FunnelInsightsPayloadSchema): - data.events = filter_stages(__parse_events(data.events)) - if len(data.events) == 0: - f = get(funnel_id=funnel_id, project_id=project_id, user_id=user_id, flatten=False) - if f is None: - return {"errors": ["funnel not found"]} - get_start_end_time(filter_d=f["filter"], range_value=data.rangeValue, - start_date=data.startDate, - end_date=data.endDate) - data = schemas.FunnelInsightsPayloadSchema.parse_obj(f["filter"]) - data.events = __fix_stages(data.events) - insights, total_drop_due_to_issues = significance.get_top_insights(filter_d=data.dict(), project_id=project_id) - insights = helper.list_to_camel_case(insights) - if len(insights) > 0: - # fix: this fix for huge drop count - if total_drop_due_to_issues > insights[0]["sessionsCount"]: - total_drop_due_to_issues = insights[0]["sessionsCount"] - # end fix - insights[-1]["dropDueToIssues"] = total_drop_due_to_issues - return {"data": {"stages": insights, - "totalDropDueToIssues": total_drop_due_to_issues}} - - # def get_top_insights_on_the_fly_widget(project_id, data: schemas.FunnelInsightsPayloadSchema): -def get_top_insights_on_the_fly_widget(project_id, data: schemas.CustomMetricSeriesFilterSchema): +def get_top_insights_on_the_fly_widget(project_id, data: schemas.CardSeriesFilterSchema): data.events = filter_stages(__parse_events(data.events)) data.events = __fix_stages(data.events) if len(data.events) == 0: @@ -271,37 +55,8 @@ def get_top_insights_on_the_fly_widget(project_id, data: schemas.CustomMetricSer "totalDropDueToIssues": total_drop_due_to_issues} -def get_issues(project_id, user_id, funnel_id, range_value=None, start_date=None, end_date=None): - f = get(funnel_id=funnel_id, project_id=project_id, user_id=user_id, flatten=False) - if f is None: - return {"errors": ["funnel not found"]} - get_start_end_time(filter_d=f["filter"], range_value=range_value, start_date=start_date, end_date=end_date) - return {"data": { - "issues": helper.dict_to_camel_case(significance.get_issues_list(filter_d=f["filter"], project_id=project_id)) - }} - - -def get_issues_on_the_fly(funnel_id, user_id, project_id, data: schemas.FunnelSearchPayloadSchema): - data.events = filter_stages(data.events) - data.events = __fix_stages(data.events) - if len(data.events) == 0: - f = get(funnel_id=funnel_id, project_id=project_id, user_id=user_id, flatten=False) - if f is None: - return {"errors": ["funnel not found"]} - get_start_end_time(filter_d=f["filter"], range_value=data.rangeValue, - start_date=data.startDate, - end_date=data.endDate) - data = schemas.FunnelSearchPayloadSchema.parse_obj(f["filter"]) - if len(data.events) < 2: - return {"issues": []} - return { - "issues": helper.dict_to_camel_case( - significance.get_issues_list(filter_d=data.dict(), project_id=project_id, first_stage=1, - last_stage=len(data.events)))} - - # def get_issues_on_the_fly_widget(project_id, data: schemas.FunnelSearchPayloadSchema): -def get_issues_on_the_fly_widget(project_id, data: schemas.CustomMetricSeriesFilterSchema): +def get_issues_on_the_fly_widget(project_id, data: schemas.CardSeriesFilterSchema): data.events = filter_stages(data.events) data.events = __fix_stages(data.events) if len(data.events) < 0: @@ -311,62 +66,3 @@ def get_issues_on_the_fly_widget(project_id, data: schemas.CustomMetricSeriesFil "issues": helper.dict_to_camel_case( significance.get_issues_list(filter_d=data.dict(), project_id=project_id, first_stage=1, last_stage=len(data.events)))} - - -def get(funnel_id, project_id, user_id, flatten=True, fix_stages=True): - with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - """\ - SELECT - * - FROM public.funnels - WHERE project_id = %(project_id)s - AND deleted_at IS NULL - AND funnel_id = %(funnel_id)s - AND (user_id = %(user_id)s OR is_public);""", - {"funnel_id": funnel_id, "project_id": project_id, "user_id": user_id} - ) - ) - - f = helper.dict_to_camel_case(cur.fetchone()) - if f is None: - return None - if f.get("filter") is not None and f["filter"].get("events") is not None: - f["filter"]["events"] = __transform_old_funnels(f["filter"]["events"]) - f["createdAt"] = TimeUTC.datetime_to_timestamp(f["createdAt"]) - f["filter"]["events"] = __parse_events(f["filter"]["events"]) - f["filter"]["events"] = filter_stages(stages=f["filter"]["events"]) - if fix_stages: - f["filter"]["events"] = __fix_stages(f["filter"]["events"]) - f["filter"]["events"] = [e.dict() for e in f["filter"]["events"]] - if flatten: - f["filter"] = helper.old_search_payload_to_flat(f["filter"]) - return f - - -def search_by_issue(user_id, project_id, funnel_id, issue_id, data: schemas.FunnelSearchPayloadSchema, range_value=None, - start_date=None, end_date=None): - if len(data.events) == 0: - f = get(funnel_id=funnel_id, project_id=project_id, user_id=user_id, flatten=False) - if f is None: - return {"errors": ["funnel not found"]} - data.startDate = data.startDate if data.startDate is not None else start_date - data.endDate = data.endDate if data.endDate is not None else end_date - get_start_end_time(filter_d=f["filter"], range_value=range_value, start_date=data.startDate, - end_date=data.endDate) - data = schemas.FunnelSearchPayloadSchema.parse_obj(f["filter"]) - - issues = get_issues_on_the_fly(funnel_id=funnel_id, user_id=user_id, project_id=project_id, data=data) \ - .get("issues", {}) - issues = issues.get("significant", []) + issues.get("insignificant", []) - issue = None - for i in issues: - if i.get("issueId", "") == issue_id: - issue = i - break - return {"sessions": sessions.search_sessions(user_id=user_id, project_id=project_id, issue=issue, - data=data) if issue is not None else {"total": 0, "sessions": []}, - # "stages": helper.list_to_camel_case(insights), - # "totalDropDueToIssues": total_drop_due_to_issues, - "issue": issue} diff --git a/api/chalicelib/core/heatmaps.py b/api/chalicelib/core/heatmaps.py index fea3fb407..465e8ce92 100644 --- a/api/chalicelib/core/heatmaps.py +++ b/api/chalicelib/core/heatmaps.py @@ -1,26 +1,74 @@ +from chalicelib.utils import sql_helper as sh +import schemas from chalicelib.utils import helper, pg_client -from chalicelib.utils.TimeUTC import TimeUTC -def get_by_url(project_id, data): - args = {"startDate": data.get('startDate', TimeUTC.now(delta_days=-30)), - "endDate": data.get('endDate', TimeUTC.now()), - "project_id": project_id, "url": data["url"]} +def get_by_url(project_id, data: schemas.GetHeatmapPayloadSchema): + args = {"startDate": data.startDate, "endDate": data.endDate, + "project_id": project_id, "url": data.url} + constraints = ["sessions.project_id = %(project_id)s", + "(url = %(url)s OR path= %(url)s)", + "clicks.timestamp >= %(startDate)s", + "clicks.timestamp <= %(endDate)s", + "start_ts >= %(startDate)s", + "start_ts <= %(endDate)s", + "duration IS NOT NULL"] + query_from = "events.clicks INNER JOIN sessions USING (session_id)" + q_count = "count(1) AS count" + has_click_rage_filter = False + if len(data.filters) > 0: + for i, f in enumerate(data.filters): + if f.type == schemas.FilterType.issue and len(f.value) > 0: + has_click_rage_filter = True + q_count = "max(real_count) AS count,TRUE AS click_rage" + query_from += """INNER JOIN events_common.issues USING (timestamp, session_id) + INNER JOIN issues AS mis USING (issue_id) + INNER JOIN LATERAL ( + SELECT COUNT(1) AS real_count + FROM events.clicks AS sc + INNER JOIN sessions as ss USING (session_id) + WHERE ss.project_id = 2 + AND (sc.url = %(url)s OR sc.path = %(url)s) + AND sc.timestamp >= %(startDate)s + AND sc.timestamp <= %(endDate)s + AND ss.start_ts >= %(startDate)s + AND ss.start_ts <= %(endDate)s + AND sc.selector = clicks.selector) AS r_clicks ON (TRUE)""" + constraints += ["mis.project_id = %(project_id)s", + "issues.timestamp >= %(startDate)s", + "issues.timestamp <= %(endDate)s"] + f_k = f"issue_value{i}" + args = {**args, **sh.multi_values(f.value, value_key=f_k)} + constraints.append(sh.multi_conditions(f"%({f_k})s = ANY (issue_types)", + f.value, value_key=f_k)) + constraints.append(sh.multi_conditions(f"mis.type = %({f_k})s", + f.value, value_key=f_k)) + if len(f.filters) > 0: + for j, sf in enumerate(f.filters): + f_k = f"issue_svalue{i}{j}" + args = {**args, **sh.multi_values(sf.value, value_key=f_k)} + if sf.type == schemas.IssueFilterType._on_selector and len(sf.value) > 0: + constraints.append(sh.multi_conditions(f"clicks.selector = %({f_k})s", + sf.value, value_key=f_k)) + if data.click_rage and not has_click_rage_filter: + constraints.append("""(issues.session_id IS NULL + OR (issues.timestamp >= %(startDate)s + AND issues.timestamp <= %(endDate)s + AND mis.project_id = %(project_id)s + AND mis.type = 'click_rage'))""") + q_count += ",COALESCE(bool_or(mis.issue_id IS NOT NULL), FALSE) AS click_rage" + query_from += """LEFT JOIN events_common.issues USING (timestamp, session_id) + LEFT JOIN issues AS mis USING (issue_id)""" with pg_client.PostgresClient() as cur: - query = cur.mogrify("""SELECT selector, count(1) AS count - FROM events.clicks - INNER JOIN sessions USING (session_id) - WHERE project_id = %(project_id)s - AND url = %(url)s - AND timestamp >= %(startDate)s - AND timestamp <= %(endDate)s - AND start_ts >= %(startDate)s - AND start_ts <= %(endDate)s - AND duration IS NOT NULL - GROUP BY selector;""", - args) - + query = cur.mogrify(f"""SELECT selector, {q_count} + FROM {query_from} + WHERE {" AND ".join(constraints)} + GROUP BY selector + LIMIT 500;""", args) + # print("---------") + # print(query.decode('UTF-8')) + # print("---------") try: cur.execute(query) except Exception as err: @@ -31,4 +79,4 @@ def get_by_url(project_id, data): print("--------------------") raise err rows = cur.fetchall() - return helper.dict_to_camel_case(rows) + return helper.list_to_camel_case(rows) diff --git a/api/chalicelib/core/integration_github.py b/api/chalicelib/core/integration_github.py index b300aa7f7..0be412122 100644 --- a/api/chalicelib/core/integration_github.py +++ b/api/chalicelib/core/integration_github.py @@ -24,8 +24,7 @@ class GitHubIntegration(integration_base.BaseIntegration): integration = self.get() if integration is None: return None - token = "*" * (len(integration["token"]) - 4) + integration["token"][-4:] - return {"token": token, "provider": self.provider.lower()} + return {"token": helper.obfuscate(text=integration["token"]), "provider": self.provider.lower()} def update(self, changes, obfuscate=False): with pg_client.PostgresClient() as cur: @@ -40,12 +39,14 @@ class GitHubIntegration(integration_base.BaseIntegration): **changes}) ) w = helper.dict_to_camel_case(cur.fetchone()) + if w and w.get("token") and obfuscate: + w["token"] = helper.obfuscate(w["token"]) return w def _add(self, data): pass - def add(self, token): + def add(self, token, obfuscate=False): with pg_client.PostgresClient() as cur: cur.execute( cur.mogrify("""\ @@ -56,6 +57,8 @@ class GitHubIntegration(integration_base.BaseIntegration): "token": token}) ) w = helper.dict_to_camel_case(cur.fetchone()) + if w and w.get("token") and obfuscate: + w["token"] = helper.obfuscate(w["token"]) return w # TODO: make a revoke token call @@ -81,4 +84,4 @@ class GitHubIntegration(integration_base.BaseIntegration): obfuscate=True ) else: - return self.add(token=data["token"]) + return self.add(token=data["token"], obfuscate=True) diff --git a/api/chalicelib/core/integrations_global.py b/api/chalicelib/core/integrations_global.py index 5b00a28bd..66cd38a74 100644 --- a/api/chalicelib/core/integrations_global.py +++ b/api/chalicelib/core/integrations_global.py @@ -9,49 +9,52 @@ def get_global_integrations_status(tenant_id, user_id, project_id): SELECT EXISTS((SELECT 1 FROM public.oauth_authentication WHERE user_id = %(user_id)s - AND provider = 'github')) AS {schemas.IntegrationType.github}, + AND provider = 'github')) AS {schemas.IntegrationType.github.value}, EXISTS((SELECT 1 FROM public.jira_cloud - WHERE user_id = %(user_id)s)) AS {schemas.IntegrationType.jira}, + WHERE user_id = %(user_id)s)) AS {schemas.IntegrationType.jira.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='bugsnag')) AS {schemas.IntegrationType.bugsnag}, + AND provider='bugsnag')) AS {schemas.IntegrationType.bugsnag.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='cloudwatch')) AS {schemas.IntegrationType.cloudwatch}, + AND provider='cloudwatch')) AS {schemas.IntegrationType.cloudwatch.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='datadog')) AS {schemas.IntegrationType.datadog}, + AND provider='datadog')) AS {schemas.IntegrationType.datadog.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='newrelic')) AS {schemas.IntegrationType.newrelic}, + AND provider='newrelic')) AS {schemas.IntegrationType.newrelic.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='rollbar')) AS {schemas.IntegrationType.rollbar}, + AND provider='rollbar')) AS {schemas.IntegrationType.rollbar.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='sentry')) AS {schemas.IntegrationType.sentry}, + AND provider='sentry')) AS {schemas.IntegrationType.sentry.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='stackdriver')) AS {schemas.IntegrationType.stackdriver}, + AND provider='stackdriver')) AS {schemas.IntegrationType.stackdriver.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='sumologic')) AS {schemas.IntegrationType.sumologic}, + AND provider='sumologic')) AS {schemas.IntegrationType.sumologic.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='elasticsearch')) AS {schemas.IntegrationType.elasticsearch}, + AND provider='elasticsearch')) AS {schemas.IntegrationType.elasticsearch.value}, EXISTS((SELECT 1 FROM public.webhooks - WHERE type='slack')) AS {schemas.IntegrationType.slack};""", + WHERE type='slack' AND deleted_at ISNULL)) AS {schemas.IntegrationType.slack.value}, + EXISTS((SELECT 1 + FROM public.webhooks + WHERE type='msteams' AND deleted_at ISNULL)) AS {schemas.IntegrationType.ms_teams.value};""", {"user_id": user_id, "tenant_id": tenant_id, "project_id": project_id}) ) current_integrations = cur.fetchone() diff --git a/api/chalicelib/core/metadata.py b/api/chalicelib/core/metadata.py index 0f73d0962..eba0d7a22 100644 --- a/api/chalicelib/core/metadata.py +++ b/api/chalicelib/core/metadata.py @@ -1,4 +1,8 @@ import re +from typing import Optional + +from fastapi import HTTPException +from starlette import status from chalicelib.core import projects from chalicelib.utils import pg_client @@ -6,21 +10,37 @@ from chalicelib.utils import pg_client MAX_INDEXES = 10 -def _get_column_names(): +def column_names(): return [f"metadata_{i}" for i in range(1, MAX_INDEXES + 1)] +def __exists_by_name(project_id: int, name: str, exclude_index: Optional[int]) -> bool: + with pg_client.PostgresClient() as cur: + constraints = column_names() + if exclude_index: + del constraints[exclude_index - 1] + for i in range(len(constraints)): + constraints[i] += " ILIKE %(name)s" + query = cur.mogrify(f"""SELECT EXISTS(SELECT 1 + FROM public.projects + WHERE project_id = %(project_id)s + AND deleted_at ISNULL + AND ({" OR ".join(constraints)})) AS exists;""", + {"project_id": project_id, "name": name}) + cur.execute(query=query) + row = cur.fetchone() + + return row["exists"] + + def get(project_id): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - f"""\ - SELECT - {",".join(_get_column_names())} - FROM public.projects - WHERE project_id = %(project_id)s AND deleted_at ISNULL - LIMIT 1;""", {"project_id": project_id}) - ) + query = cur.mogrify(f"""SELECT {",".join(column_names())} + FROM public.projects + WHERE project_id = %(project_id)s + AND deleted_at ISNULL + LIMIT 1;""", {"project_id": project_id}) + cur.execute(query=query) metas = cur.fetchone() results = [] if metas is not None: @@ -34,15 +54,12 @@ def get_batch(project_ids): if project_ids is None or len(project_ids) == 0: return [] with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - f"""\ - SELECT - project_id, {",".join(_get_column_names())} - FROM public.projects - WHERE project_id IN %(project_ids)s - AND deleted_at ISNULL;""", {"project_ids": tuple(project_ids)}) - ) + query = cur.mogrify(f"""SELECT project_id, {",".join(column_names())} + FROM public.projects + WHERE project_id IN %(project_ids)s + AND deleted_at ISNULL;""", + {"project_ids": tuple(project_ids)}) + cur.execute(query=query) full_metas = cur.fetchall() results = {} if full_metas is not None and len(full_metas) > 0: @@ -84,17 +101,21 @@ def __edit(project_id, col_index, colname, new_name): with pg_client.PostgresClient() as cur: if old_metas[col_index]["key"] != new_name: - cur.execute(cur.mogrify(f"""UPDATE public.projects - SET {colname} = %(value)s - WHERE project_id = %(project_id)s AND deleted_at ISNULL - RETURNING {colname};""", - {"project_id": project_id, "value": new_name})) + query = cur.mogrify(f"""UPDATE public.projects + SET {colname} = %(value)s + WHERE project_id = %(project_id)s + AND deleted_at ISNULL + RETURNING {colname};""", + {"project_id": project_id, "value": new_name}) + cur.execute(query=query) new_name = cur.fetchone()[colname] old_metas[col_index]["key"] = new_name return {"data": old_metas[col_index]} def edit(tenant_id, project_id, index: int, new_name: str): + if __exists_by_name(project_id=project_id, name=new_name, exclude_index=index): + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"name already exists.") return __edit(project_id=project_id, col_index=index, colname=index_to_colname(index), new_name=new_name) @@ -127,12 +148,16 @@ def add(tenant_id, project_id, new_name): index = __get_available_index(project_id=project_id) if index < 1: return {"errors": ["maximum allowed metadata reached"]} + if __exists_by_name(project_id=project_id, name=new_name, exclude_index=None): + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"name already exists.") with pg_client.PostgresClient() as cur: colname = index_to_colname(index) - cur.execute( - cur.mogrify( - f"""UPDATE public.projects SET {colname}= %(key)s WHERE project_id =%(project_id)s RETURNING {colname};""", - {"key": new_name, "project_id": project_id})) + query = cur.mogrify(f"""UPDATE public.projects + SET {colname}= %(key)s + WHERE project_id =%(project_id)s + RETURNING {colname};""", + {"key": new_name, "project_id": project_id}) + cur.execute(query=query) col_val = cur.fetchone()[colname] return {"data": {"key": col_val, "index": index}} @@ -140,21 +165,17 @@ def add(tenant_id, project_id, new_name): def search(tenant_id, project_id, key, value): value = value + "%" s_query = [] - for f in _get_column_names(): + for f in column_names(): s_query.append(f"CASE WHEN {f}=%(key)s THEN TRUE ELSE FALSE END AS {f}") with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - f"""\ - SELECT - {",".join(s_query)} - FROM public.projects - WHERE - project_id = %(project_id)s AND deleted_at ISNULL - LIMIT 1;""", - {"key": key, "project_id": project_id}) - ) + query = cur.mogrify(f"""SELECT {",".join(s_query)} + FROM public.projects + WHERE project_id = %(project_id)s + AND deleted_at ISNULL + LIMIT 1;""", + {"key": key, "project_id": project_id}) + cur.execute(query=query) all_metas = cur.fetchone() key = None for c in all_metas: @@ -163,17 +184,13 @@ def search(tenant_id, project_id, key, value): break if key is None: return {"errors": ["key does not exist"]} - cur.execute( - cur.mogrify( - f"""\ - SELECT - DISTINCT "{key}" AS "{key}" - FROM public.sessions - {f'WHERE "{key}"::text ILIKE %(value)s' if value is not None and len(value) > 0 else ""} - ORDER BY "{key}" - LIMIT 20;""", - {"value": value, "project_id": project_id}) - ) + query = cur.mogrify(f"""SELECT DISTINCT "{key}" AS "{key}" + FROM public.sessions + {f'WHERE "{key}"::text ILIKE %(value)s' if value is not None and len(value) > 0 else ""} + ORDER BY "{key}" + LIMIT 20;""", + {"value": value, "project_id": project_id}) + cur.execute(query=query) value = cur.fetchall() return {"data": [k[key] for k in value]} @@ -189,14 +206,12 @@ def get_by_session_id(project_id, session_id): return [] keys = {index_to_colname(k["index"]): k["key"] for k in all_metas} with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - f"""\ - select {",".join(keys.keys())} - FROM public.sessions - WHERE project_id= %(project_id)s AND session_id=%(session_id)s;""", - {"session_id": session_id, "project_id": project_id}) - ) + query = cur.mogrify(f"""SELECT {",".join(keys.keys())} + FROM public.sessions + WHERE project_id= %(project_id)s + AND session_id=%(session_id)s;""", + {"session_id": session_id, "project_id": project_id}) + cur.execute(query=query) session_metas = cur.fetchall() results = [] for m in session_metas: @@ -211,14 +226,11 @@ def get_keys_by_projects(project_ids): if project_ids is None or len(project_ids) == 0: return {} with pg_client.PostgresClient() as cur: - query = cur.mogrify( - f"""\ - SELECT - project_id, - {",".join(_get_column_names())} - FROM public.projects - WHERE project_id IN %(project_ids)s AND deleted_at ISNULL;""", - {"project_ids": tuple(project_ids)}) + query = cur.mogrify(f"""SELECT project_id,{",".join(column_names())} + FROM public.projects + WHERE project_id IN %(project_ids)s + AND deleted_at ISNULL;""", + {"project_ids": tuple(project_ids)}) cur.execute(query) rows = cur.fetchall() diff --git a/api/chalicelib/core/metrics.py b/api/chalicelib/core/metrics.py index b25b441ed..1fadc2adc 100644 --- a/api/chalicelib/core/metrics.py +++ b/api/chalicelib/core/metrics.py @@ -1610,7 +1610,7 @@ def get_domains_errors(project_id, startTimestamp=TimeUTC.now(delta_days=-1), "status_code": 4, **__get_constraint_values(args)} cur.execute(cur.mogrify(pg_query, params)) rows = cur.fetchall() - rows = __nested_array_to_dict_array(rows) + rows = __nested_array_to_dict_array(rows, key="host") neutral = __get_neutral(rows) rows = __merge_rows_with_neutral(rows, neutral) @@ -1618,7 +1618,7 @@ def get_domains_errors(project_id, startTimestamp=TimeUTC.now(delta_days=-1), params["status_code"] = 5 cur.execute(cur.mogrify(pg_query, params)) rows = cur.fetchall() - rows = __nested_array_to_dict_array(rows) + rows = __nested_array_to_dict_array(rows, key="host") neutral = __get_neutral(rows) rows = __merge_rows_with_neutral(rows, neutral) result["5xx"] = rows @@ -1658,7 +1658,7 @@ def __get_domains_errors_4xx_and_5xx(status, project_id, startTimestamp=TimeUTC. "status_code": status, **__get_constraint_values(args)} cur.execute(cur.mogrify(pg_query, params)) rows = cur.fetchall() - rows = __nested_array_to_dict_array(rows) + rows = __nested_array_to_dict_array(rows, key="host") neutral = __get_neutral(rows) rows = __merge_rows_with_neutral(rows, neutral) diff --git a/api/chalicelib/core/projects.py b/api/chalicelib/core/projects.py index 10d1e7aee..24d1e01f7 100644 --- a/api/chalicelib/core/projects.py +++ b/api/chalicelib/core/projects.py @@ -1,4 +1,8 @@ import json +from typing import Optional + +from fastapi import HTTPException +from starlette import status import schemas from chalicelib.core import users @@ -6,6 +10,20 @@ from chalicelib.utils import pg_client, helper from chalicelib.utils.TimeUTC import TimeUTC +def __exists_by_name(name: str, exclude_id: Optional[int]) -> bool: + with pg_client.PostgresClient() as cur: + query = cur.mogrify(f"""SELECT EXISTS(SELECT 1 + FROM public.projects + WHERE deleted_at IS NULL + AND name ILIKE %(name)s + {"AND project_id!=%(exclude_id)s" if exclude_id else ""}) AS exists;""", + {"name": name, "exclude_id": exclude_id}) + + cur.execute(query=query) + row = cur.fetchone() + return row["exists"] + + def __update(tenant_id, project_id, changes): if len(changes.keys()) == 0: return None @@ -14,29 +32,23 @@ def __update(tenant_id, project_id, changes): for key in changes.keys(): sub_query.append(f"{helper.key_to_snake_case(key)} = %({key})s") with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify(f"""\ - UPDATE public.projects - SET - {" ,".join(sub_query)} - WHERE - project_id = %(project_id)s - AND deleted_at ISNULL - RETURNING project_id,name,gdpr;""", - {"project_id": project_id, **changes}) - ) + query = cur.mogrify(f"""UPDATE public.projects + SET {" ,".join(sub_query)} + WHERE project_id = %(project_id)s + AND deleted_at ISNULL + RETURNING project_id,name,gdpr;""", + {"project_id": project_id, **changes}) + cur.execute(query=query) return helper.dict_to_camel_case(cur.fetchone()) def __create(tenant_id, name): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify(f"""\ - INSERT INTO public.projects (name, active) - VALUES (%(name)s,TRUE) - RETURNING project_id;""", - {"name": name}) - ) + query = cur.mogrify(f"""INSERT INTO public.projects (name, active) + VALUES (%(name)s,TRUE) + RETURNING project_id;""", + {"name": name}) + cur.execute(query=query) project_id = cur.fetchone()["project_id"] return get_project(tenant_id=tenant_id, project_id=project_id, include_gdpr=True) @@ -66,8 +78,8 @@ def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, st LIMIT 1) AS stack_integrations ON TRUE""" query = cur.mogrify(f"""{"SELECT *, first_recorded IS NOT NULL AS recorded FROM (" if recorded else ""} - SELECT s.project_id, s.name, s.project_key, s.save_request_payloads, s.first_recorded_session_at - {extra_projection} + SELECT s.project_id, s.name, s.project_key, s.save_request_payloads, s.first_recorded_session_at, + created_at {extra_projection} FROM public.projects AS s {extra_join} WHERE s.deleted_at IS NULL @@ -79,6 +91,7 @@ def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, st u_values = [] params = {} for i, r in enumerate(rows): + r["created_at"] = TimeUTC.datetime_to_timestamp(r["created_at"]) if r["first_recorded_session_at"] is None: u_values.append(f"(%(project_id_{i})s,to_timestamp(%(first_recorded_{i})s/1000))") params[f"project_id_{i}"] = r["project_id"] @@ -91,7 +104,9 @@ def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, st FROM (VALUES {",".join(u_values)}) AS u(project_id,first_recorded) WHERE projects.project_id=u.project_id;""", params) cur.execute(query) - + else: + for r in rows: + r["created_at"] = TimeUTC.datetime_to_timestamp(r["created_at"]) if recording_state and len(rows) > 0: project_ids = [f'({r["project_id"]})' for r in rows] query = cur.mogrify(f"""SELECT projects.project_id, COALESCE(MAX(start_ts), 0) AS last @@ -118,49 +133,53 @@ def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, st def get_project(tenant_id, project_id, include_last_session=False, include_gdpr=None): with pg_client.PostgresClient() as cur: - query = cur.mogrify(f"""\ - SELECT - s.project_id, - s.project_key, - s.name, - s.save_request_payloads - {",(SELECT max(ss.start_ts) FROM public.sessions AS ss WHERE ss.project_id = %(project_id)s) AS last_recorded_session_at" if include_last_session else ""} - {',s.gdpr' if include_gdpr else ''} - FROM public.projects AS s - where s.project_id =%(project_id)s - AND s.deleted_at IS NULL - LIMIT 1;""", + extra_select = "" + if include_last_session: + extra_select += """,(SELECT max(ss.start_ts) + FROM public.sessions AS ss + WHERE ss.project_id = %(project_id)s) AS last_recorded_session_at""" + if include_gdpr: + extra_select += ",s.gdpr" + query = cur.mogrify(f"""SELECT s.project_id, + s.project_key, + s.name, + s.save_request_payloads + {extra_select} + FROM public.projects AS s + WHERE s.project_id =%(project_id)s + AND s.deleted_at IS NULL + LIMIT 1;""", {"project_id": project_id}) - - cur.execute( - query=query - ) + cur.execute(query=query) row = cur.fetchone() return helper.dict_to_camel_case(row) def get_project_by_key(tenant_id, project_key, include_last_session=False, include_gdpr=None): with pg_client.PostgresClient() as cur: - query = cur.mogrify(f"""\ - SELECT - s.project_key, - s.name - {",(SELECT max(ss.start_ts) FROM public.sessions AS ss WHERE ss.project_key = %(project_key)s) AS last_recorded_session_at" if include_last_session else ""} - {',s.gdpr' if include_gdpr else ''} - FROM public.projects AS s - where s.project_key =%(project_key)s - AND s.deleted_at IS NULL - LIMIT 1;""", + extra_select = "" + if include_last_session: + extra_select += """,(SELECT max(ss.start_ts) + FROM public.sessions AS ss + WHERE ss.project_key = %(project_key)s) AS last_recorded_session_at""" + if include_gdpr: + extra_select += ",s.gdpr" + query = cur.mogrify(f"""SELECT s.project_key, + s.name + {extra_select} + FROM public.projects AS s + WHERE s.project_key =%(project_key)s + AND s.deleted_at IS NULL + LIMIT 1;""", {"project_key": project_key}) - - cur.execute( - query=query - ) + cur.execute(query=query) row = cur.fetchone() return helper.dict_to_camel_case(row) def create(tenant_id, user_id, data: schemas.CreateProjectSchema, skip_authorization=False): + if __exists_by_name(name=data.name, exclude_id=None): + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"name already exists.") if not skip_authorization: admin = users.get(user_id=user_id, tenant_id=tenant_id) if not admin["admin"] and not admin["superAdmin"]: @@ -169,6 +188,8 @@ def create(tenant_id, user_id, data: schemas.CreateProjectSchema, skip_authoriza def edit(tenant_id, user_id, project_id, data: schemas.CreateProjectSchema): + if __exists_by_name(name=data.name, exclude_id=project_id): + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"name already exists.") admin = users.get(user_id=user_id, tenant_id=tenant_id) if not admin["admin"] and not admin["superAdmin"]: return {"errors": ["unauthorized"]} @@ -182,95 +203,77 @@ def delete(tenant_id, user_id, project_id): if not admin["admin"] and not admin["superAdmin"]: return {"errors": ["unauthorized"]} with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""\ - UPDATE public.projects - SET - deleted_at = timezone('utc'::text, now()), - active = FALSE - WHERE - project_id = %(project_id)s;""", - {"project_id": project_id}) - ) + query = cur.mogrify("""UPDATE public.projects + SET deleted_at = timezone('utc'::text, now()), + active = FALSE + WHERE project_id = %(project_id)s;""", + {"project_id": project_id}) + cur.execute(query=query) return {"data": {"state": "success"}} -def count_by_tenant(tenant_id): - with pg_client.PostgresClient() as cur: - cur.execute("""\ - SELECT - count(s.project_id) - FROM public.projects AS s - where s.deleted_at IS NULL;""") - return cur.fetchone()["count"] - - def get_gdpr(project_id): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""\ - SELECT - gdpr - FROM public.projects AS s - where s.project_id =%(project_id)s - AND s.deleted_at IS NULL;""", - {"project_id": project_id}) - ) - return cur.fetchone()["gdpr"] + query = cur.mogrify("""SELECT gdpr + FROM public.projects AS s + WHERE s.project_id =%(project_id)s + AND s.deleted_at IS NULL;""", + {"project_id": project_id}) + cur.execute(query=query) + row = cur.fetchone()["gdpr"] + row["projectId"] = project_id + return row def edit_gdpr(project_id, gdpr): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""\ - UPDATE public.projects - SET - gdpr = gdpr|| %(gdpr)s - WHERE - project_id = %(project_id)s - AND deleted_at ISNULL - RETURNING gdpr;""", - {"project_id": project_id, "gdpr": json.dumps(gdpr)}) - ) - return cur.fetchone()["gdpr"] + query = cur.mogrify("""UPDATE public.projects + SET gdpr = gdpr|| %(gdpr)s + WHERE project_id = %(project_id)s + AND deleted_at ISNULL + RETURNING gdpr;""", + {"project_id": project_id, "gdpr": json.dumps(gdpr)}) + cur.execute(query=query) + row = cur.fetchone() + if not row: + return {"errors": ["something went wrong"]} + row = row["gdpr"] + row["projectId"] = project_id + return row def get_internal_project_id(project_key): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""\ - SELECT project_id - FROM public.projects - where project_key =%(project_key)s AND deleted_at ISNULL;""", - {"project_key": project_key}) - ) + query = cur.mogrify("""SELECT project_id + FROM public.projects + WHERE project_key =%(project_key)s + AND deleted_at ISNULL;""", + {"project_key": project_key}) + cur.execute(query=query) row = cur.fetchone() return row["project_id"] if row else None def get_project_key(project_id): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""\ - SELECT project_key - FROM public.projects - where project_id =%(project_id)s AND deleted_at ISNULL;""", - {"project_id": project_id}) - ) + query = cur.mogrify("""SELECT project_key + FROM public.projects + WHERE project_id =%(project_id)s + AND deleted_at ISNULL;""", + {"project_id": project_id}) + cur.execute(query=query) project = cur.fetchone() return project["project_key"] if project is not None else None def get_capture_status(project_id): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""\ - SELECT - sample_rate AS rate, sample_rate=100 AS capture_all - FROM public.projects - where project_id =%(project_id)s AND deleted_at ISNULL;""", - {"project_id": project_id}) - ) + query = cur.mogrify("""SELECT sample_rate AS rate, sample_rate=100 AS capture_all + FROM public.projects + WHERE project_id =%(project_id)s + AND deleted_at ISNULL;""", + {"project_id": project_id}) + cur.execute(query=query) return helper.dict_to_camel_case(cur.fetchone()) @@ -285,22 +288,22 @@ def update_capture_status(project_id, changes): if changes.get("captureAll"): sample_rate = 100 with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""\ - UPDATE public.projects - SET sample_rate= %(sample_rate)s - WHERE project_id =%(project_id)s AND deleted_at ISNULL;""", - {"project_id": project_id, "sample_rate": sample_rate}) - ) + query = cur.mogrify("""UPDATE public.projects + SET sample_rate= %(sample_rate)s + WHERE project_id =%(project_id)s + AND deleted_at ISNULL;""", + {"project_id": project_id, "sample_rate": sample_rate}) + cur.execute(query=query) return changes def get_projects_ids(tenant_id): with pg_client.PostgresClient() as cur: - cur.execute(f"""SELECT s.project_id - FROM public.projects AS s - WHERE s.deleted_at IS NULL - ORDER BY s.project_id;""") + query = f"""SELECT s.project_id + FROM public.projects AS s + WHERE s.deleted_at IS NULL + ORDER BY s.project_id;""" + cur.execute(query=query) rows = cur.fetchall() return [r["project_id"] for r in rows] diff --git a/api/chalicelib/core/sessions.py b/api/chalicelib/core/sessions.py index 91efb967f..c95bed903 100644 --- a/api/chalicelib/core/sessions.py +++ b/api/chalicelib/core/sessions.py @@ -2,9 +2,11 @@ from typing import List import schemas from chalicelib.core import events, metadata, events_ios, \ - sessions_mobs, issues, projects, errors, resources, assist, performance_event, sessions_viewed, sessions_favorite, \ + sessions_mobs, issues, projects, resources, assist, performance_event, sessions_favorite, \ sessions_devtool, sessions_notes +from chalicelib.utils import errors_helper from chalicelib.utils import pg_client, helper, metrics_helper +from chalicelib.utils import sql_helper as sh SESSION_PROJECTION_COLS = """s.project_id, s.session_id::text AS session_id, @@ -60,7 +62,7 @@ def get_by_id2_pg(project_id, session_id, context: schemas.CurrentContext, full_ s.session_id::text AS session_id, (SELECT project_key FROM public.projects WHERE project_id = %(project_id)s LIMIT 1) AS project_key {"," if len(extra_query) > 0 else ""}{",".join(extra_query)} - {(",json_build_object(" + ",".join([f"'{m}',p.{m}" for m in metadata._get_column_names()]) + ") AS project_metadata") if group_metadata else ''} + {(",json_build_object(" + ",".join([f"'{m}',p.{m}" for m in metadata.column_names()]) + ") AS project_metadata") if group_metadata else ''} FROM public.sessions AS s {"INNER JOIN public.projects AS p USING (project_id)" if group_metadata else ""} WHERE s.project_id = %(project_id)s AND s.session_id = %(session_id)s;""", @@ -84,16 +86,16 @@ def get_by_id2_pg(project_id, session_id, context: schemas.CurrentContext, full_ session_id=session_id) data['mobsUrl'] = sessions_mobs.get_ios(session_id=session_id) else: - data['events'] = events.get_by_sessionId2_pg(project_id=project_id, session_id=session_id, - group_clickrage=True) + data['events'] = events.get_by_session_id(project_id=project_id, session_id=session_id, + group_clickrage=True) all_errors = events.get_errors_by_session_id(session_id=session_id, project_id=project_id) data['stackEvents'] = [e for e in all_errors if e['source'] != "js_exception"] # to keep only the first stack # limit the number of errors to reduce the response-body size - data['errors'] = [errors.format_first_stack_frame(e) for e in all_errors + data['errors'] = [errors_helper.format_first_stack_frame(e) for e in all_errors if e['source'] == "js_exception"][:500] - data['userEvents'] = events.get_customs_by_sessionId2_pg(project_id=project_id, - session_id=session_id) + data['userEvents'] = events.get_customs_by_session_id(project_id=project_id, + session_id=session_id) data['domURL'] = sessions_mobs.get_urls(session_id=session_id, project_id=project_id) data['mobsUrl'] = sessions_mobs.get_urls_depercated(session_id=session_id) data['devtoolsURL'] = sessions_devtool.get_urls(session_id=session_id, project_id=project_id) @@ -114,67 +116,6 @@ def get_by_id2_pg(project_id, session_id, context: schemas.CurrentContext, full_ return None -def __get_sql_operator(op: schemas.SearchEventOperator): - return { - schemas.SearchEventOperator._is: "=", - schemas.SearchEventOperator._is_any: "IN", - schemas.SearchEventOperator._on: "=", - schemas.SearchEventOperator._on_any: "IN", - schemas.SearchEventOperator._is_not: "!=", - schemas.SearchEventOperator._not_on: "!=", - schemas.SearchEventOperator._contains: "ILIKE", - schemas.SearchEventOperator._not_contains: "NOT ILIKE", - schemas.SearchEventOperator._starts_with: "ILIKE", - schemas.SearchEventOperator._ends_with: "ILIKE", - }.get(op, "=") - - -def __is_negation_operator(op: schemas.SearchEventOperator): - return op in [schemas.SearchEventOperator._is_not, - schemas.SearchEventOperator._not_on, - schemas.SearchEventOperator._not_contains] - - -def __reverse_sql_operator(op): - return "=" if op == "!=" else "!=" if op == "=" else "ILIKE" if op == "NOT ILIKE" else "NOT ILIKE" - - -def __get_sql_operator_multiple(op: schemas.SearchEventOperator): - return " IN " if op not in [schemas.SearchEventOperator._is_not, schemas.SearchEventOperator._not_on, - schemas.SearchEventOperator._not_contains] else " NOT IN " - - -def __get_sql_value_multiple(values): - if isinstance(values, tuple): - return values - return tuple(values) if isinstance(values, list) else (values,) - - -def _multiple_conditions(condition, values, value_key="value", is_not=False): - query = [] - for i in range(len(values)): - k = f"{value_key}_{i}" - query.append(condition.replace(value_key, k)) - return "(" + (" AND " if is_not else " OR ").join(query) + ")" - - -def _multiple_values(values, value_key="value"): - query_values = {} - if values is not None and isinstance(values, list): - for i in range(len(values)): - k = f"{value_key}_{i}" - query_values[k] = values[i] - return query_values - - -def _isAny_opreator(op: schemas.SearchEventOperator): - return op in [schemas.SearchEventOperator._on_any, schemas.SearchEventOperator._is_any] - - -def _isUndefined_operator(op: schemas.SearchEventOperator): - return op in [schemas.SearchEventOperator._is_undefined] - - # This function executes the query and return result def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_id, errors_only=False, error_status=schemas.ErrorStatus.all, count_only=False, issue=None, ids_only=False): @@ -210,9 +151,9 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_ elif data.group_by_user: g_sort = "count(full_sessions)" if data.order is None: - data.order = schemas.SortOrderType.desc + data.order = schemas.SortOrderType.desc.value else: - data.order = data.order.upper() + data.order = data.order.value if data.sort is not None and data.sort != 'sessionsCount': sort = helper.key_to_snake_case(data.sort) g_sort = f"{'MIN' if data.order == schemas.SortOrderType.desc else 'MAX'}({sort})" @@ -245,7 +186,9 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_ full_args) else: if data.order is None: - data.order = schemas.SortOrderType.desc + data.order = schemas.SortOrderType.desc.value + else: + data.order = data.order.value sort = 'session_id' if data.sort is not None and data.sort != "session_id": # sort += " " + data.order + "," + helper.key_to_snake_case(data.sort) @@ -304,13 +247,13 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, density: int, view_type: schemas.MetricTimeseriesViewType, metric_type: schemas.MetricType, - metric_of: schemas.TableMetricOfType, metric_value: List): + metric_of: schemas.MetricOfTable, metric_value: List): step_size = int(metrics_helper.__get_step_size(endTimestamp=data.endDate, startTimestamp=data.startDate, density=density, factor=1, decimal=True)) extra_event = None - if metric_of == schemas.TableMetricOfType.visited_url: + if metric_of == schemas.MetricOfTable.visited_url: extra_event = "events.pages" - elif metric_of == schemas.TableMetricOfType.issues and len(metric_value) > 0: + elif metric_of == schemas.MetricOfTable.issues and len(metric_value) > 0: data.filters.append(schemas.SessionSearchFilterSchema(value=metric_value, type=schemas.FilterType.issue, operator=schemas.SearchEventOperator._is)) full_args, query_part = search_query_parts(data=data, error_status=None, errors_only=False, @@ -353,18 +296,19 @@ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, d else: sessions = cur.fetchone()["count"] elif metric_type == schemas.MetricType.table: - if isinstance(metric_of, schemas.TableMetricOfType): + if isinstance(metric_of, schemas.MetricOfTable): main_col = "user_id" extra_col = "" extra_where = "" pre_query = "" - if metric_of == schemas.TableMetricOfType.user_country: + distinct_on = "s.session_id" + if metric_of == schemas.MetricOfTable.user_country: main_col = "user_country" - elif metric_of == schemas.TableMetricOfType.user_device: + elif metric_of == schemas.MetricOfTable.user_device: main_col = "user_device" - elif metric_of == schemas.TableMetricOfType.user_browser: + elif metric_of == schemas.MetricOfTable.user_browser: main_col = "user_browser" - elif metric_of == schemas.TableMetricOfType.issues: + elif metric_of == schemas.MetricOfTable.issues: main_col = "issue" extra_col = f", UNNEST(s.issue_types) AS {main_col}" if len(metric_value) > 0: @@ -374,16 +318,17 @@ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, d extra_where.append(f"{main_col} = %({arg_name})s") full_args[arg_name] = metric_value[i] extra_where = f"WHERE ({' OR '.join(extra_where)})" - elif metric_of == schemas.TableMetricOfType.visited_url: + elif metric_of == schemas.MetricOfTable.visited_url: main_col = "path" extra_col = ", path" + distinct_on += ",path" main_query = cur.mogrify(f"""{pre_query} SELECT COUNT(*) AS count, COALESCE(JSONB_AGG(users_sessions) FILTER ( WHERE rn <= 200 ), '[]'::JSONB) AS values FROM (SELECT {main_col} AS name, - count(full_sessions) AS session_count, + count(DISTINCT session_id) AS session_count, ROW_NUMBER() OVER (ORDER BY count(full_sessions) DESC) AS rn FROM (SELECT * - FROM (SELECT DISTINCT ON(s.session_id) s.session_id, s.user_uuid, + FROM (SELECT DISTINCT ON({distinct_on}) s.session_id, s.user_uuid, s.user_id, s.user_os, s.user_browser, s.user_device, s.user_device_type, s.user_country, s.issue_types{extra_col} @@ -420,7 +365,8 @@ def __is_valid_event(is_any: bool, event: schemas._SessionSearchEventSchema): # this function generates the query and return the generated-query with the dict of query arguments -def search_query_parts(data, error_status, errors_only, favorite_only, issue, project_id, user_id, extra_event=None): +def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status, errors_only, favorite_only, issue, + project_id, user_id, extra_event=None): ss_constraints = [] full_args = {"project_id": project_id, "startDate": data.startDate, "endDate": data.endDate, "projectId": project_id, "userId": user_id} @@ -438,15 +384,15 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr filter_type = f.type f.value = helper.values_for_operator(value=f.value, op=f.operator) f_k = f"f_value{i}" - full_args = {**full_args, **_multiple_values(f.value, value_key=f_k)} - op = __get_sql_operator(f.operator) \ + full_args = {**full_args, **sh.multi_values(f.value, value_key=f_k)} + op = sh.get_sql_operator(f.operator) \ if filter_type not in [schemas.FilterType.events_count] else f.operator - is_any = _isAny_opreator(f.operator) - is_undefined = _isUndefined_operator(f.operator) + is_any = sh.isAny_opreator(f.operator) + is_undefined = sh.isUndefined_operator(f.operator) if not is_any and not is_undefined and len(f.value) == 0: continue is_not = False - if __is_negation_operator(f.operator): + if sh.is_negation_operator(f.operator): is_not = True if filter_type == schemas.FilterType.user_browser: if is_any: @@ -454,9 +400,10 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.user_browser IS NOT NULL') else: extra_constraints.append( - _multiple_conditions(f's.user_browser {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f's.user_browser {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) ss_constraints.append( - _multiple_conditions(f'ms.user_browser {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f'ms.user_browser {op} %({f_k})s', f.value, is_not=is_not, + value_key=f_k)) elif filter_type in [schemas.FilterType.user_os, schemas.FilterType.user_os_ios]: if is_any: @@ -464,9 +411,9 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.user_os IS NOT NULL') else: extra_constraints.append( - _multiple_conditions(f's.user_os {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f's.user_os {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) ss_constraints.append( - _multiple_conditions(f'ms.user_os {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f'ms.user_os {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) elif filter_type in [schemas.FilterType.user_device, schemas.FilterType.user_device_ios]: if is_any: @@ -474,9 +421,9 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.user_device IS NOT NULL') else: extra_constraints.append( - _multiple_conditions(f's.user_device {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f's.user_device {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) ss_constraints.append( - _multiple_conditions(f'ms.user_device {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f'ms.user_device {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) elif filter_type in [schemas.FilterType.user_country, schemas.FilterType.user_country_ios]: if is_any: @@ -484,9 +431,10 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.user_country IS NOT NULL') else: extra_constraints.append( - _multiple_conditions(f's.user_country {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f's.user_country {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) ss_constraints.append( - _multiple_conditions(f'ms.user_country {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f'ms.user_country {op} %({f_k})s', f.value, is_not=is_not, + value_key=f_k)) elif filter_type in [schemas.FilterType.utm_source]: if is_any: @@ -497,11 +445,11 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.utm_source IS NULL') else: extra_constraints.append( - _multiple_conditions(f's.utm_source {op} %({f_k})s::text', f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f's.utm_source {op} %({f_k})s::text', f.value, is_not=is_not, + value_key=f_k)) ss_constraints.append( - _multiple_conditions(f'ms.utm_source {op} %({f_k})s::text', f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f'ms.utm_source {op} %({f_k})s::text', f.value, is_not=is_not, + value_key=f_k)) elif filter_type in [schemas.FilterType.utm_medium]: if is_any: extra_constraints.append('s.utm_medium IS NOT NULL') @@ -511,11 +459,11 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.utm_medium IS NULL') else: extra_constraints.append( - _multiple_conditions(f's.utm_medium {op} %({f_k})s::text', f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f's.utm_medium {op} %({f_k})s::text', f.value, is_not=is_not, + value_key=f_k)) ss_constraints.append( - _multiple_conditions(f'ms.utm_medium {op} %({f_k})s::text', f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f'ms.utm_medium {op} %({f_k})s::text', f.value, is_not=is_not, + value_key=f_k)) elif filter_type in [schemas.FilterType.utm_campaign]: if is_any: extra_constraints.append('s.utm_campaign IS NOT NULL') @@ -525,11 +473,11 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.utm_campaign IS NULL') else: extra_constraints.append( - _multiple_conditions(f's.utm_campaign {op} %({f_k})s::text', f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f's.utm_campaign {op} %({f_k})s::text', f.value, is_not=is_not, + value_key=f_k)) ss_constraints.append( - _multiple_conditions(f'ms.utm_campaign {op} %({f_k})s::text', f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f'ms.utm_campaign {op} %({f_k})s::text', f.value, is_not=is_not, + value_key=f_k)) elif filter_type == schemas.FilterType.duration: if len(f.value) > 0 and f.value[0] is not None: @@ -546,8 +494,9 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr extra_constraints.append('s.base_referrer IS NOT NULL') else: extra_constraints.append( - _multiple_conditions(f"s.base_referrer {op} %({f_k})s", f.value, is_not=is_not, value_key=f_k)) - elif filter_type == events.event_type.METADATA.ui_type: + sh.multi_conditions(f"s.base_referrer {op} %({f_k})s", f.value, is_not=is_not, + value_key=f_k)) + elif filter_type == events.EventType.METADATA.ui_type: # get metadata list only if you need it if meta_keys is None: meta_keys = metadata.get(project_id=project_id) @@ -561,11 +510,11 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append(f"ms.{metadata.index_to_colname(meta_keys[f.source])} IS NULL") else: extra_constraints.append( - _multiple_conditions( + sh.multi_conditions( f"s.{metadata.index_to_colname(meta_keys[f.source])} {op} %({f_k})s::text", f.value, is_not=is_not, value_key=f_k)) ss_constraints.append( - _multiple_conditions( + sh.multi_conditions( f"ms.{metadata.index_to_colname(meta_keys[f.source])} {op} %({f_k})s::text", f.value, is_not=is_not, value_key=f_k)) elif filter_type in [schemas.FilterType.user_id, schemas.FilterType.user_id_ios]: @@ -577,9 +526,11 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.user_id IS NULL') else: extra_constraints.append( - _multiple_conditions(f"s.user_id {op} %({f_k})s::text", f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f"s.user_id {op} %({f_k})s::text", f.value, is_not=is_not, + value_key=f_k)) ss_constraints.append( - _multiple_conditions(f"ms.user_id {op} %({f_k})s::text", f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f"ms.user_id {op} %({f_k})s::text", f.value, is_not=is_not, + value_key=f_k)) elif filter_type in [schemas.FilterType.user_anonymous_id, schemas.FilterType.user_anonymous_id_ios]: if is_any: @@ -590,11 +541,11 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.user_anonymous_id IS NULL') else: extra_constraints.append( - _multiple_conditions(f"s.user_anonymous_id {op} %({f_k})s::text", f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f"s.user_anonymous_id {op} %({f_k})s::text", f.value, is_not=is_not, + value_key=f_k)) ss_constraints.append( - _multiple_conditions(f"ms.user_anonymous_id {op} %({f_k})s::text", f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f"ms.user_anonymous_id {op} %({f_k})s::text", f.value, is_not=is_not, + value_key=f_k)) elif filter_type in [schemas.FilterType.rev_id, schemas.FilterType.rev_id_ios]: if is_any: extra_constraints.append('s.rev_id IS NOT NULL') @@ -604,40 +555,58 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.rev_id IS NULL') else: extra_constraints.append( - _multiple_conditions(f"s.rev_id {op} %({f_k})s::text", f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f"s.rev_id {op} %({f_k})s::text", f.value, is_not=is_not, value_key=f_k)) ss_constraints.append( - _multiple_conditions(f"ms.rev_id {op} %({f_k})s::text", f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f"ms.rev_id {op} %({f_k})s::text", f.value, is_not=is_not, + value_key=f_k)) elif filter_type == schemas.FilterType.platform: - # op = __get_sql_operator(f.operator) + # op = __ sh.get_sql_operator(f.operator) extra_constraints.append( - _multiple_conditions(f"s.user_device_type {op} %({f_k})s", f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f"s.user_device_type {op} %({f_k})s", f.value, is_not=is_not, + value_key=f_k)) ss_constraints.append( - _multiple_conditions(f"ms.user_device_type {op} %({f_k})s", f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f"ms.user_device_type {op} %({f_k})s", f.value, is_not=is_not, + value_key=f_k)) elif filter_type == schemas.FilterType.issue: if is_any: extra_constraints.append("array_length(s.issue_types, 1) > 0") ss_constraints.append("array_length(ms.issue_types, 1) > 0") else: extra_constraints.append( - _multiple_conditions(f"%({f_k})s {op} ANY (s.issue_types)", f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f"%({f_k})s {op} ANY (s.issue_types)", f.value, is_not=is_not, + value_key=f_k)) ss_constraints.append( - _multiple_conditions(f"%({f_k})s {op} ANY (ms.issue_types)", f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f"%({f_k})s {op} ANY (ms.issue_types)", f.value, is_not=is_not, + value_key=f_k)) + # search sessions with click_rage on a specific selector + if len(f.filters) > 0 and schemas.IssueType.click_rage in f.value: + for j, sf in enumerate(f.filters): + if sf.operator == schemas.IssueFilterOperator._on_selector: + f_k = f"f_value{i}_{j}" + full_args = {**full_args, **sh.multi_values(sf.value, value_key=f_k)} + extra_constraints += ["mc.timestamp>=%(startDate)s", + "mc.timestamp<=%(endDate)s", + "mis.type='click_rage'", + sh.multi_conditions(f"mc.selector=%({f_k})s", + sf.value, is_not=is_not, + value_key=f_k)] + + extra_from += """INNER JOIN events.clicks AS mc USING(session_id) + INNER JOIN events_common.issues USING (session_id,timestamp) + INNER JOIN public.issues AS mis USING (issue_id)\n""" + elif filter_type == schemas.FilterType.events_count: extra_constraints.append( - _multiple_conditions(f"s.events_count {op} %({f_k})s", f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f"s.events_count {op} %({f_k})s", f.value, is_not=is_not, + value_key=f_k)) ss_constraints.append( - _multiple_conditions(f"ms.events_count {op} %({f_k})s", f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f"ms.events_count {op} %({f_k})s", f.value, is_not=is_not, + value_key=f_k)) # --------------------------------------------------------------------------- if len(data.events) > 0: valid_events_count = 0 for event in data.events: - is_any = _isAny_opreator(event.operator) + is_any = sh.isAny_opreator(event.operator) if not isinstance(event.value, list): event.value = [event.value] if __is_valid_event(is_any=is_any, event=event): @@ -649,16 +618,16 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr events_joiner = " UNION " if or_events else " INNER JOIN LATERAL " for i, event in enumerate(data.events): event_type = event.type - is_any = _isAny_opreator(event.operator) + is_any = sh.isAny_opreator(event.operator) if not isinstance(event.value, list): event.value = [event.value] if not __is_valid_event(is_any=is_any, event=event): continue - op = __get_sql_operator(event.operator) + op = sh.get_sql_operator(event.operator) is_not = False - if __is_negation_operator(event.operator): + if sh.is_negation_operator(event.operator): is_not = True - op = __reverse_sql_operator(op) + op = sh.reverse_sql_operator(op) if event_index == 0 or or_events: event_from = "%s INNER JOIN public.sessions AS ms USING (session_id)" event_where = ["ms.project_id = %(projectId)s", "main.timestamp >= %(startDate)s", @@ -678,116 +647,120 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr if event.type != schemas.PerformanceEventType.time_between_events: event.value = helper.values_for_operator(value=event.value, op=event.operator) full_args = {**full_args, - **_multiple_values(event.value, value_key=e_k), - **_multiple_values(event.source, value_key=s_k)} + **sh.multi_values(event.value, value_key=e_k), + **sh.multi_values(event.source, value_key=s_k)} - if event_type == events.event_type.CLICK.ui_type: - event_from = event_from % f"{events.event_type.CLICK.table} AS main " + if event_type == events.EventType.CLICK.ui_type: + event_from = event_from % f"{events.EventType.CLICK.table} AS main " + if not is_any: + if event.operator == schemas.ClickEventExtraOperator._on_selector: + event_where.append( + sh.multi_conditions(f"main.selector = %({e_k})s", event.value, value_key=e_k)) + else: + event_where.append( + sh.multi_conditions(f"main.{events.EventType.CLICK.column} {op} %({e_k})s", event.value, + value_key=e_k)) + + elif event_type == events.EventType.INPUT.ui_type: + event_from = event_from % f"{events.EventType.INPUT.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.CLICK.column} {op} %({e_k})s", event.value, - value_key=e_k)) - - elif event_type == events.event_type.INPUT.ui_type: - event_from = event_from % f"{events.event_type.INPUT.table} AS main " - if not is_any: - event_where.append( - _multiple_conditions(f"main.{events.event_type.INPUT.column} {op} %({e_k})s", event.value, - value_key=e_k)) + sh.multi_conditions(f"main.{events.EventType.INPUT.column} {op} %({e_k})s", event.value, + value_key=e_k)) if event.source is not None and len(event.source) > 0: - event_where.append(_multiple_conditions(f"main.value ILIKE %(custom{i})s", event.source, - value_key=f"custom{i}")) - full_args = {**full_args, **_multiple_values(event.source, value_key=f"custom{i}")} + event_where.append(sh.multi_conditions(f"main.value ILIKE %(custom{i})s", event.source, + value_key=f"custom{i}")) + full_args = {**full_args, **sh.multi_values(event.source, value_key=f"custom{i}")} - elif event_type == events.event_type.LOCATION.ui_type: - event_from = event_from % f"{events.event_type.LOCATION.table} AS main " + elif event_type == events.EventType.LOCATION.ui_type: + event_from = event_from % f"{events.EventType.LOCATION.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.LOCATION.column} {op} %({e_k})s", - event.value, value_key=e_k)) - elif event_type == events.event_type.CUSTOM.ui_type: - event_from = event_from % f"{events.event_type.CUSTOM.table} AS main " + sh.multi_conditions(f"main.{events.EventType.LOCATION.column} {op} %({e_k})s", + event.value, value_key=e_k)) + elif event_type == events.EventType.CUSTOM.ui_type: + event_from = event_from % f"{events.EventType.CUSTOM.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.CUSTOM.column} {op} %({e_k})s", event.value, - value_key=e_k)) - elif event_type == events.event_type.REQUEST.ui_type: - event_from = event_from % f"{events.event_type.REQUEST.table} AS main " + sh.multi_conditions(f"main.{events.EventType.CUSTOM.column} {op} %({e_k})s", event.value, + value_key=e_k)) + elif event_type == events.EventType.REQUEST.ui_type: + event_from = event_from % f"{events.EventType.REQUEST.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.REQUEST.column} {op} %({e_k})s", event.value, - value_key=e_k)) + sh.multi_conditions(f"main.{events.EventType.REQUEST.column} {op} %({e_k})s", event.value, + value_key=e_k)) # elif event_type == events.event_type.GRAPHQL.ui_type: # event_from = event_from % f"{events.event_type.GRAPHQL.table} AS main " # if not is_any: # event_where.append( # _multiple_conditions(f"main.{events.event_type.GRAPHQL.column} {op} %({e_k})s", event.value, # value_key=e_k)) - elif event_type == events.event_type.STATEACTION.ui_type: - event_from = event_from % f"{events.event_type.STATEACTION.table} AS main " + elif event_type == events.EventType.STATEACTION.ui_type: + event_from = event_from % f"{events.EventType.STATEACTION.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.STATEACTION.column} {op} %({e_k})s", - event.value, value_key=e_k)) - elif event_type == events.event_type.ERROR.ui_type: - event_from = event_from % f"{events.event_type.ERROR.table} AS main INNER JOIN public.errors AS main1 USING(error_id)" + sh.multi_conditions(f"main.{events.EventType.STATEACTION.column} {op} %({e_k})s", + event.value, value_key=e_k)) + elif event_type == events.EventType.ERROR.ui_type: + event_from = event_from % f"{events.EventType.ERROR.table} AS main INNER JOIN public.errors AS main1 USING(error_id)" event.source = list(set(event.source)) if not is_any and event.value not in [None, "*", ""]: event_where.append( - _multiple_conditions(f"(main1.message {op} %({e_k})s OR main1.name {op} %({e_k})s)", - event.value, value_key=e_k)) + sh.multi_conditions(f"(main1.message {op} %({e_k})s OR main1.name {op} %({e_k})s)", + event.value, value_key=e_k)) if len(event.source) > 0 and event.source[0] not in [None, "*", ""]: - event_where.append(_multiple_conditions(f"main1.source = %({s_k})s", event.source, value_key=s_k)) + event_where.append(sh.multi_conditions(f"main1.source = %({s_k})s", event.source, value_key=s_k)) # ----- IOS - elif event_type == events.event_type.CLICK_IOS.ui_type: - event_from = event_from % f"{events.event_type.CLICK_IOS.table} AS main " + elif event_type == events.EventType.CLICK_IOS.ui_type: + event_from = event_from % f"{events.EventType.CLICK_IOS.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.CLICK_IOS.column} {op} %({e_k})s", - event.value, value_key=e_k)) + sh.multi_conditions(f"main.{events.EventType.CLICK_IOS.column} {op} %({e_k})s", + event.value, value_key=e_k)) - elif event_type == events.event_type.INPUT_IOS.ui_type: - event_from = event_from % f"{events.event_type.INPUT_IOS.table} AS main " + elif event_type == events.EventType.INPUT_IOS.ui_type: + event_from = event_from % f"{events.EventType.INPUT_IOS.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.INPUT_IOS.column} {op} %({e_k})s", - event.value, value_key=e_k)) + sh.multi_conditions(f"main.{events.EventType.INPUT_IOS.column} {op} %({e_k})s", + event.value, value_key=e_k)) if event.source is not None and len(event.source) > 0: - event_where.append(_multiple_conditions(f"main.value ILIKE %(custom{i})s", event.source, - value_key="custom{i}")) - full_args = {**full_args, **_multiple_values(event.source, f"custom{i}")} - elif event_type == events.event_type.VIEW_IOS.ui_type: - event_from = event_from % f"{events.event_type.VIEW_IOS.table} AS main " + event_where.append(sh.multi_conditions(f"main.value ILIKE %(custom{i})s", event.source, + value_key="custom{i}")) + full_args = {**full_args, **sh.multi_values(event.source, f"custom{i}")} + elif event_type == events.EventType.VIEW_IOS.ui_type: + event_from = event_from % f"{events.EventType.VIEW_IOS.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.VIEW_IOS.column} {op} %({e_k})s", - event.value, value_key=e_k)) - elif event_type == events.event_type.CUSTOM_IOS.ui_type: - event_from = event_from % f"{events.event_type.CUSTOM_IOS.table} AS main " + sh.multi_conditions(f"main.{events.EventType.VIEW_IOS.column} {op} %({e_k})s", + event.value, value_key=e_k)) + elif event_type == events.EventType.CUSTOM_IOS.ui_type: + event_from = event_from % f"{events.EventType.CUSTOM_IOS.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.CUSTOM_IOS.column} {op} %({e_k})s", - event.value, value_key=e_k)) - elif event_type == events.event_type.REQUEST_IOS.ui_type: - event_from = event_from % f"{events.event_type.REQUEST_IOS.table} AS main " + sh.multi_conditions(f"main.{events.EventType.CUSTOM_IOS.column} {op} %({e_k})s", + event.value, value_key=e_k)) + elif event_type == events.EventType.REQUEST_IOS.ui_type: + event_from = event_from % f"{events.EventType.REQUEST_IOS.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.REQUEST_IOS.column} {op} %({e_k})s", - event.value, value_key=e_k)) - elif event_type == events.event_type.ERROR_IOS.ui_type: - event_from = event_from % f"{events.event_type.ERROR_IOS.table} AS main INNER JOIN public.crashes_ios AS main1 USING(crash_id)" + sh.multi_conditions(f"main.{events.EventType.REQUEST_IOS.column} {op} %({e_k})s", + event.value, value_key=e_k)) + elif event_type == events.EventType.ERROR_IOS.ui_type: + event_from = event_from % f"{events.EventType.ERROR_IOS.table} AS main INNER JOIN public.crashes_ios AS main1 USING(crash_id)" if not is_any and event.value not in [None, "*", ""]: event_where.append( - _multiple_conditions(f"(main1.reason {op} %({e_k})s OR main1.name {op} %({e_k})s)", - event.value, value_key=e_k)) + sh.multi_conditions(f"(main1.reason {op} %({e_k})s OR main1.name {op} %({e_k})s)", + event.value, value_key=e_k)) elif event_type == schemas.PerformanceEventType.fetch_failed: - event_from = event_from % f"{events.event_type.REQUEST.table} AS main " + event_from = event_from % f"{events.EventType.REQUEST.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.REQUEST.column} {op} %({e_k})s", - event.value, value_key=e_k)) + sh.multi_conditions(f"main.{events.EventType.REQUEST.column} {op} %({e_k})s", + event.value, value_key=e_k)) col = performance_event.get_col(event_type) colname = col["column"] event_where.append(f"main.{colname} = FALSE") @@ -801,7 +774,7 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr # colname = col["column"] # tname = "main" # e_k += "_custom" - # full_args = {**full_args, **_multiple_values(event.source, value_key=e_k)} + # full_args = {**full_args, **_ sh.multiple_values(event.source, value_key=e_k)} # event_where.append(f"{tname}.{colname} IS NOT NULL AND {tname}.{colname}>0 AND " + # _multiple_conditions(f"{tname}.{colname} {event.sourceOperator} %({e_k})s", # event.source, value_key=e_k)) @@ -811,7 +784,7 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr schemas.PerformanceEventType.location_avg_cpu_load, schemas.PerformanceEventType.location_avg_memory_usage ]: - event_from = event_from % f"{events.event_type.LOCATION.table} AS main " + event_from = event_from % f"{events.EventType.LOCATION.table} AS main " col = performance_event.get_col(event_type) colname = col["column"] tname = "main" @@ -822,16 +795,16 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr f"{tname}.timestamp <= %(endDate)s"] if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.LOCATION.column} {op} %({e_k})s", - event.value, value_key=e_k)) + sh.multi_conditions(f"main.{events.EventType.LOCATION.column} {op} %({e_k})s", + event.value, value_key=e_k)) e_k += "_custom" - full_args = {**full_args, **_multiple_values(event.source, value_key=e_k)} + full_args = {**full_args, **sh.multi_values(event.source, value_key=e_k)} event_where.append(f"{tname}.{colname} IS NOT NULL AND {tname}.{colname}>0 AND " + - _multiple_conditions(f"{tname}.{colname} {event.sourceOperator} %({e_k})s", - event.source, value_key=e_k)) + sh.multi_conditions(f"{tname}.{colname} {event.sourceOperator.value} %({e_k})s", + event.source, value_key=e_k)) elif event_type == schemas.PerformanceEventType.time_between_events: - event_from = event_from % f"{getattr(events.event_type, event.value[0].type).table} AS main INNER JOIN {getattr(events.event_type, event.value[1].type).table} AS main2 USING(session_id) " + event_from = event_from % f"{getattr(events.EventType, event.value[0].type).table} AS main INNER JOIN {getattr(events.EventType, event.value[1].type).table} AS main2 USING(session_id) " if not isinstance(event.value[0].value, list): event.value[0].value = [event.value[0].value] if not isinstance(event.value[1].value, list): @@ -843,98 +816,99 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr e_k1 = e_k + "_e1" e_k2 = e_k + "_e2" full_args = {**full_args, - **_multiple_values(event.value[0].value, value_key=e_k1), - **_multiple_values(event.value[1].value, value_key=e_k2)} - s_op = __get_sql_operator(event.value[0].operator) + **sh.multi_values(event.value[0].value, value_key=e_k1), + **sh.multi_values(event.value[1].value, value_key=e_k2)} + s_op = sh.get_sql_operator(event.value[0].operator) event_where += ["main2.timestamp >= %(startDate)s", "main2.timestamp <= %(endDate)s"] if event_index > 0 and not or_events: event_where.append("main2.session_id=event_0.session_id") - is_any = _isAny_opreator(event.value[0].operator) + is_any = sh.isAny_opreator(event.value[0].operator) if not is_any: event_where.append( - _multiple_conditions( - f"main.{getattr(events.event_type, event.value[0].type).column} {s_op} %({e_k1})s", + sh.multi_conditions( + f"main.{getattr(events.EventType, event.value[0].type).column} {s_op} %({e_k1})s", event.value[0].value, value_key=e_k1)) - s_op = __get_sql_operator(event.value[1].operator) - is_any = _isAny_opreator(event.value[1].operator) + s_op = sh.get_sql_operator(event.value[1].operator) + is_any = sh.isAny_opreator(event.value[1].operator) if not is_any: event_where.append( - _multiple_conditions( - f"main2.{getattr(events.event_type, event.value[1].type).column} {s_op} %({e_k2})s", + sh.multi_conditions( + f"main2.{getattr(events.EventType, event.value[1].type).column} {s_op} %({e_k2})s", event.value[1].value, value_key=e_k2)) e_k += "_custom" - full_args = {**full_args, **_multiple_values(event.source, value_key=e_k)} + full_args = {**full_args, **sh.multi_values(event.source, value_key=e_k)} event_where.append( - _multiple_conditions(f"main2.timestamp - main.timestamp {event.sourceOperator} %({e_k})s", - event.source, value_key=e_k)) + sh.multi_conditions(f"main2.timestamp - main.timestamp {event.sourceOperator.value} %({e_k})s", + event.source, value_key=e_k)) elif event_type == schemas.EventType.request_details: - event_from = event_from % f"{events.event_type.REQUEST.table} AS main " + event_from = event_from % f"{events.EventType.REQUEST.table} AS main " apply = False for j, f in enumerate(event.filters): - is_any = _isAny_opreator(f.operator) + is_any = sh.isAny_opreator(f.operator) if is_any or len(f.value) == 0: continue f.value = helper.values_for_operator(value=f.value, op=f.operator) - op = __get_sql_operator(f.operator) + op = sh.get_sql_operator(f.operator) e_k_f = e_k + f"_fetch{j}" - full_args = {**full_args, **_multiple_values(f.value, value_key=e_k_f)} + full_args = {**full_args, **sh.multi_values(f.value, value_key=e_k_f)} if f.type == schemas.FetchFilterType._url: event_where.append( - _multiple_conditions(f"main.{events.event_type.REQUEST.column} {op} %({e_k_f})s::text", - f.value, value_key=e_k_f)) + sh.multi_conditions(f"main.{events.EventType.REQUEST.column} {op} %({e_k_f})s::text", + f.value, value_key=e_k_f)) apply = True elif f.type == schemas.FetchFilterType._status_code: event_where.append( - _multiple_conditions(f"main.status_code {f.operator} %({e_k_f})s::integer", f.value, - value_key=e_k_f)) + sh.multi_conditions(f"main.status_code {f.operator.value} %({e_k_f})s::integer", f.value, + value_key=e_k_f)) apply = True elif f.type == schemas.FetchFilterType._method: event_where.append( - _multiple_conditions(f"main.method {op} %({e_k_f})s", f.value, value_key=e_k_f)) + sh.multi_conditions(f"main.method {op} %({e_k_f})s", f.value, value_key=e_k_f)) apply = True elif f.type == schemas.FetchFilterType._duration: event_where.append( - _multiple_conditions(f"main.duration {f.operator} %({e_k_f})s::integer", f.value, - value_key=e_k_f)) + sh.multi_conditions(f"main.duration {f.operator.value} %({e_k_f})s::integer", f.value, + value_key=e_k_f)) apply = True elif f.type == schemas.FetchFilterType._request_body: event_where.append( - _multiple_conditions(f"main.request_body {op} %({e_k_f})s::text", f.value, value_key=e_k_f)) + sh.multi_conditions(f"main.request_body {op} %({e_k_f})s::text", f.value, + value_key=e_k_f)) apply = True elif f.type == schemas.FetchFilterType._response_body: event_where.append( - _multiple_conditions(f"main.response_body {op} %({e_k_f})s::text", f.value, - value_key=e_k_f)) + sh.multi_conditions(f"main.response_body {op} %({e_k_f})s::text", f.value, + value_key=e_k_f)) apply = True else: print(f"undefined FETCH filter: {f.type}") if not apply: continue elif event_type == schemas.EventType.graphql: - event_from = event_from % f"{events.event_type.GRAPHQL.table} AS main " + event_from = event_from % f"{events.EventType.GRAPHQL.table} AS main " for j, f in enumerate(event.filters): - is_any = _isAny_opreator(f.operator) + is_any = sh.isAny_opreator(f.operator) if is_any or len(f.value) == 0: continue f.value = helper.values_for_operator(value=f.value, op=f.operator) - op = __get_sql_operator(f.operator) + op = sh.get_sql_operator(f.operator) e_k_f = e_k + f"_graphql{j}" - full_args = {**full_args, **_multiple_values(f.value, value_key=e_k_f)} + full_args = {**full_args, **sh.multi_values(f.value, value_key=e_k_f)} if f.type == schemas.GraphqlFilterType._name: event_where.append( - _multiple_conditions(f"main.{events.event_type.GRAPHQL.column} {op} %({e_k_f})s", f.value, - value_key=e_k_f)) + sh.multi_conditions(f"main.{events.EventType.GRAPHQL.column} {op} %({e_k_f})s", f.value, + value_key=e_k_f)) elif f.type == schemas.GraphqlFilterType._method: event_where.append( - _multiple_conditions(f"main.method {op} %({e_k_f})s", f.value, value_key=e_k_f)) + sh.multi_conditions(f"main.method {op} %({e_k_f})s", f.value, value_key=e_k_f)) elif f.type == schemas.GraphqlFilterType._request_body: event_where.append( - _multiple_conditions(f"main.request_body {op} %({e_k_f})s", f.value, value_key=e_k_f)) + sh.multi_conditions(f"main.request_body {op} %({e_k_f})s", f.value, value_key=e_k_f)) elif f.type == schemas.GraphqlFilterType._response_body: event_where.append( - _multiple_conditions(f"main.response_body {op} %({e_k_f})s", f.value, value_key=e_k_f)) + sh.multi_conditions(f"main.response_body {op} %({e_k_f})s", f.value, value_key=e_k_f)) else: print(f"undefined GRAPHQL filter: {f.type}") else: @@ -1005,7 +979,7 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr # b"s.user_os in ('Chrome OS','Fedora','Firefox OS','Linux','Mac OS X','Ubuntu','Windows')") if errors_only: - extra_from += f" INNER JOIN {events.event_type.ERROR.table} AS er USING (session_id) INNER JOIN public.errors AS ser USING (error_id)" + extra_from += f" INNER JOIN {events.EventType.ERROR.table} AS er USING (session_id) INNER JOIN public.errors AS ser USING (error_id)" extra_constraints.append("ser.source = 'js_exception'") extra_constraints.append("ser.project_id = %(project_id)s") # if error_status != schemas.ErrorStatus.all: @@ -1114,39 +1088,6 @@ def search_by_metadata(tenant_id, user_id, m_key, m_value, project_id=None): return results -def search_by_issue(user_id, issue, project_id, start_date, end_date): - constraints = ["s.project_id = %(projectId)s", - "p_issues.context_string = %(issueContextString)s", - "p_issues.type = %(issueType)s"] - if start_date is not None: - constraints.append("start_ts >= %(startDate)s") - if end_date is not None: - constraints.append("start_ts <= %(endDate)s") - with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - f"""SELECT DISTINCT ON(favorite_sessions.session_id, s.session_id) {SESSION_PROJECTION_COLS} - FROM public.sessions AS s - INNER JOIN events_common.issues USING (session_id) - INNER JOIN public.issues AS p_issues USING (issue_id) - LEFT JOIN (SELECT user_id, session_id - FROM public.user_favorite_sessions - WHERE user_id = %(userId)s) AS favorite_sessions - USING (session_id) - WHERE {" AND ".join(constraints)} - ORDER BY s.session_id DESC;""", - { - "issueContextString": issue["contextString"], - "issueType": issue["type"], "userId": user_id, - "projectId": project_id, - "startDate": start_date, - "endDate": end_date - })) - - rows = cur.fetchall() - return helper.list_to_camel_case(rows) - - def get_user_sessions(project_id, user_id, start_date, end_date): with pg_client.PostgresClient() as cur: constraints = ["s.project_id = %(projectId)s", "s.user_id = %(userId)s"] @@ -1253,8 +1194,9 @@ def delete_sessions_by_user_ids(project_id, user_ids): def count_all(): with pg_client.PostgresClient(unlimited_query=True) as cur: - row = cur.execute(query="SELECT COUNT(session_id) AS count FROM public.sessions") - return row.get("count", 0) + cur.execute(query="SELECT COUNT(session_id) AS count FROM public.sessions") + row = cur.fetchone() + return row.get("count", 0) if row else 0 def session_exists(project_id, session_id): @@ -1262,7 +1204,8 @@ def session_exists(project_id, session_id): query = cur.mogrify("""SELECT 1 FROM public.sessions WHERE session_id=%(session_id)s - AND project_id=%(project_id)s""", + AND project_id=%(project_id)s + LIMIT 1;""", {"project_id": project_id, "session_id": session_id}) cur.execute(query) row = cur.fetchone() diff --git a/api/chalicelib/core/sessions_devtool.py b/api/chalicelib/core/sessions_devtool.py index 2afc3c366..6aab5a5e2 100644 --- a/api/chalicelib/core/sessions_devtool.py +++ b/api/chalicelib/core/sessions_devtool.py @@ -13,9 +13,11 @@ def __get_devtools_keys(project_id, session_id): ] -def get_urls(session_id, project_id): +def get_urls(session_id, project_id, check_existence: bool = True): results = [] for k in __get_devtools_keys(project_id=project_id, session_id=session_id): + if check_existence and not s3.exists(bucket=config("sessions_bucket"), key=k): + continue results.append(s3.client.generate_presigned_url( 'get_object', Params={'Bucket': config("sessions_bucket"), 'Key': k}, diff --git a/api/chalicelib/core/sessions_favorite.py b/api/chalicelib/core/sessions_favorite.py index 4c456d385..00228b31f 100644 --- a/api/chalicelib/core/sessions_favorite.py +++ b/api/chalicelib/core/sessions_favorite.py @@ -36,15 +36,15 @@ def favorite_session(context: schemas.CurrentContext, project_id, session_id): return add_favorite_session(context=context, project_id=project_id, session_id=session_id) -def favorite_session_exists(user_id, session_id): +def favorite_session_exists(session_id, user_id=None): with pg_client.PostgresClient() as cur: cur.execute( cur.mogrify( - """SELECT session_id + f"""SELECT session_id FROM public.user_favorite_sessions WHERE - user_id = %(userId)s - AND session_id = %(session_id)s""", + session_id = %(session_id)s + {'AND user_id = %(userId)s' if user_id else ''};""", {"userId": user_id, "session_id": session_id}) ) r = cur.fetchone() diff --git a/api/chalicelib/core/sessions_mobs.py b/api/chalicelib/core/sessions_mobs.py index 9a9237be8..68c64fd2b 100644 --- a/api/chalicelib/core/sessions_mobs.py +++ b/api/chalicelib/core/sessions_mobs.py @@ -18,9 +18,11 @@ def __get_mob_keys_deprecated(session_id): return [str(session_id), str(session_id) + "e"] -def get_urls(project_id, session_id): +def get_urls(project_id, session_id, check_existence: bool = True): results = [] for k in __get_mob_keys(project_id=project_id, session_id=session_id): + if check_existence and not s3.exists(bucket=config("sessions_bucket"), key=k): + continue results.append(s3.client.generate_presigned_url( 'get_object', Params={'Bucket': config("sessions_bucket"), 'Key': k}, @@ -29,9 +31,11 @@ def get_urls(project_id, session_id): return results -def get_urls_depercated(session_id): +def get_urls_depercated(session_id, check_existence: bool = True): results = [] for k in __get_mob_keys_deprecated(session_id=session_id): + if check_existence and not s3.exists(bucket=config("sessions_bucket"), key=k): + continue results.append(s3.client.generate_presigned_url( 'get_object', Params={'Bucket': config("sessions_bucket"), 'Key': k}, diff --git a/api/chalicelib/core/sessions_notes.py b/api/chalicelib/core/sessions_notes.py index 3ef4ccc21..0ad78fee7 100644 --- a/api/chalicelib/core/sessions_notes.py +++ b/api/chalicelib/core/sessions_notes.py @@ -3,15 +3,16 @@ from urllib.parse import urljoin from decouple import config import schemas -from chalicelib.core import sessions +from chalicelib.core.collaboration_msteams import MSTeams from chalicelib.core.collaboration_slack import Slack from chalicelib.utils import pg_client, helper +from chalicelib.utils import sql_helper as sh from chalicelib.utils.TimeUTC import TimeUTC def get_note(tenant_id, project_id, user_id, note_id, share=None): with pg_client.PostgresClient() as cur: - query = cur.mogrify(f"""SELECT sessions_notes.*, users.name AS creator_name + query = cur.mogrify(f"""SELECT sessions_notes.*, users.name AS user_name {",(SELECT name FROM users WHERE user_id=%(share)s AND deleted_at ISNULL) AS share_name" if share else ""} FROM sessions_notes INNER JOIN users USING (user_id) WHERE sessions_notes.project_id = %(project_id)s @@ -31,8 +32,8 @@ def get_note(tenant_id, project_id, user_id, note_id, share=None): def get_session_notes(tenant_id, project_id, session_id, user_id): with pg_client.PostgresClient() as cur: - query = cur.mogrify(f"""SELECT sessions_notes.* - FROM sessions_notes + query = cur.mogrify(f"""SELECT sessions_notes.*, users.name AS user_name + FROM sessions_notes INNER JOIN users USING (user_id) WHERE sessions_notes.project_id = %(project_id)s AND sessions_notes.deleted_at IS NULL AND sessions_notes.session_id = %(session_id)s @@ -57,18 +58,18 @@ def get_all_notes_by_project_id(tenant_id, project_id, user_id, data: schemas.Se if data.tags and len(data.tags) > 0: k = "tag_value" conditions.append( - sessions._multiple_conditions(f"%({k})s = sessions_notes.tag", data.tags, value_key=k)) - extra_params = sessions._multiple_values(data.tags, value_key=k) + sh.multi_conditions(f"%({k})s = sessions_notes.tag", data.tags, value_key=k)) + extra_params = sh.multi_values(data.tags, value_key=k) if data.shared_only: conditions.append("sessions_notes.is_public") elif data.mine_only: conditions.append("sessions_notes.user_id = %(user_id)s") else: conditions.append("(sessions_notes.user_id = %(user_id)s OR sessions_notes.is_public)") - query = cur.mogrify(f"""SELECT sessions_notes.* - FROM sessions_notes + query = cur.mogrify(f"""SELECT sessions_notes.*, users.name AS user_name + FROM sessions_notes INNER JOIN users USING (user_id) WHERE {" AND ".join(conditions)} - ORDER BY created_at {data.order} + ORDER BY created_at {data.order.value} LIMIT {data.limit} OFFSET {data.limit * (data.page - 1)};""", {"project_id": project_id, "user_id": user_id, "tenant_id": tenant_id, **extra_params}) @@ -84,7 +85,7 @@ def create(tenant_id, user_id, project_id, session_id, data: schemas.SessionNote with pg_client.PostgresClient() as cur: query = cur.mogrify(f"""INSERT INTO public.sessions_notes (message, user_id, tag, session_id, project_id, timestamp, is_public) VALUES (%(message)s, %(user_id)s, %(tag)s, %(session_id)s, %(project_id)s, %(timestamp)s, %(is_public)s) - RETURNING *;""", + RETURNING *,(SELECT name FROM users WHERE users.user_id=%(user_id)s) AS user_name;""", {"user_id": user_id, "project_id": project_id, "session_id": session_id, **data.dict()}) cur.execute(query) result = helper.dict_to_camel_case(cur.fetchone()) @@ -113,7 +114,7 @@ def edit(tenant_id, user_id, project_id, note_id, data: schemas.SessionUpdateNot AND user_id = %(user_id)s AND note_id = %(note_id)s AND deleted_at ISNULL - RETURNING *;""", + RETURNING *,(SELECT name FROM users WHERE users.user_id=%(user_id)s) AS user_name;""", {"project_id": project_id, "user_id": user_id, "note_id": note_id, **data.dict()}) ) row = helper.dict_to_camel_case(cur.fetchone()) @@ -155,7 +156,7 @@ def share_to_slack(tenant_id, user_id, project_id, note_id, webhook_id): blocks.append({"type": "context", "elements": [{"type": "plain_text", "text": f"Tag: *{note['tag']}*"}]}) - bottom = f"Created by {note['creatorName'].capitalize()}" + bottom = f"Created by {note['userName'].capitalize()}" if user_id != note["userId"]: bottom += f"\nSent by {note['shareName']}: " blocks.append({"type": "context", @@ -166,3 +167,60 @@ def share_to_slack(tenant_id, user_id, project_id, note_id, webhook_id): webhook_id=webhook_id, body={"blocks": blocks} ) + + +def share_to_msteams(tenant_id, user_id, project_id, note_id, webhook_id): + note = get_note(tenant_id=tenant_id, project_id=project_id, user_id=user_id, note_id=note_id, share=user_id) + if note is None: + return {"errors": ["Note not found"]} + session_url = urljoin(config('SITE_URL'), f"{note['projectId']}/session/{note['sessionId']}?note={note['noteId']}") + if note["timestamp"] > 0: + session_url += f"&jumpto={note['timestamp']}" + title = f"[Note for session {note['sessionId']}]({session_url})" + + blocks = [{ + "type": "TextBlock", + "text": title, + "style": "heading", + "size": "Large" + }, + { + "type": "TextBlock", + "spacing": "Small", + "text": note["message"] + } + ] + if note["tag"]: + blocks.append({"type": "TextBlock", + "spacing": "Small", + "text": f"Tag: *{note['tag']}*", + "size": "Small"}) + bottom = f"Created by {note['userName'].capitalize()}" + if user_id != note["userId"]: + bottom += f"\nSent by {note['shareName']}: " + blocks.append({"type": "TextBlock", + "spacing": "Default", + "text": bottom, + "size": "Small", + "fontType": "Monospace"}) + return MSTeams.send_raw( + tenant_id=tenant_id, + webhook_id=webhook_id, + body={"type": "message", + "attachments": [ + {"contentType": "application/vnd.microsoft.card.adaptive", + "contentUrl": None, + "content": { + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.5", + "body": [{ + "type": "ColumnSet", + "style": "emphasis", + "separator": True, + "bleed": True, + "columns": [{"width": "stretch", + "items": blocks, + "type": "Column"}] + }]}} + ]}) diff --git a/api/chalicelib/core/significance.py b/api/chalicelib/core/significance.py index c4a4fcaac..8477cc985 100644 --- a/api/chalicelib/core/significance.py +++ b/api/chalicelib/core/significance.py @@ -1,6 +1,7 @@ __author__ = "AZNAUROV David" __maintainer__ = "KRAIEM Taha Yassine" +from chalicelib.utils import sql_helper as sh import schemas from chalicelib.core import events, metadata, sessions @@ -49,33 +50,33 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]: continue f["value"] = helper.values_for_operator(value=f["value"], op=f["operator"]) # filter_args = _multiple_values(f["value"]) - op = sessions.__get_sql_operator(f["operator"]) + op = sh.get_sql_operator(f["operator"]) filter_type = f["type"] # values[f_k] = sessions.__get_sql_value_multiple(f["value"]) f_k = f"f_value{i}" values = {**values, - **sessions._multiple_values(helper.values_for_operator(value=f["value"], op=f["operator"]), - value_key=f_k)} + **sh.multi_values(helper.values_for_operator(value=f["value"], op=f["operator"]), + value_key=f_k)} if filter_type == schemas.FilterType.user_browser: # op = sessions.__get_sql_operator_multiple(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_browser {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_browser {op} %({f_k})s', f["value"], value_key=f_k)) elif filter_type in [schemas.FilterType.user_os, schemas.FilterType.user_os_ios]: # op = sessions.__get_sql_operator_multiple(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_os {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_os {op} %({f_k})s', f["value"], value_key=f_k)) elif filter_type in [schemas.FilterType.user_device, schemas.FilterType.user_device_ios]: # op = sessions.__get_sql_operator_multiple(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_device {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_device {op} %({f_k})s', f["value"], value_key=f_k)) elif filter_type in [schemas.FilterType.user_country, schemas.FilterType.user_country_ios]: # op = sessions.__get_sql_operator_multiple(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_country {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_country {op} %({f_k})s', f["value"], value_key=f_k)) elif filter_type == schemas.FilterType.duration: if len(f["value"]) > 0 and f["value"][0] is not None: first_stage_extra_constraints.append(f's.duration >= %(minDuration)s') @@ -85,36 +86,36 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]: values["maxDuration"] = f["value"][1] elif filter_type == schemas.FilterType.referrer: # events_query_part = events_query_part + f"INNER JOIN events.pages AS p USING(session_id)" - filter_extra_from = [f"INNER JOIN {events.event_type.LOCATION.table} AS p USING(session_id)"] + filter_extra_from = [f"INNER JOIN {events.EventType.LOCATION.table} AS p USING(session_id)"] # op = sessions.__get_sql_operator_multiple(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f"p.base_referrer {op} %({f_k})s", f["value"], value_key=f_k)) - elif filter_type == events.event_type.METADATA.ui_type: + sh.multi_conditions(f"p.base_referrer {op} %({f_k})s", f["value"], value_key=f_k)) + elif filter_type == events.EventType.METADATA.ui_type: if meta_keys is None: meta_keys = metadata.get(project_id=project_id) meta_keys = {m["key"]: m["index"] for m in meta_keys} # op = sessions.__get_sql_operator(f["operator"]) if f.get("key") in meta_keys.keys(): first_stage_extra_constraints.append( - sessions._multiple_conditions( + sh.multi_conditions( f's.{metadata.index_to_colname(meta_keys[f["key"]])} {op} %({f_k})s', f["value"], value_key=f_k)) # values[f_k] = helper.string_to_sql_like_with_op(f["value"][0], op) elif filter_type in [schemas.FilterType.user_id, schemas.FilterType.user_id_ios]: # op = sessions.__get_sql_operator(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_id {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_id {op} %({f_k})s', f["value"], value_key=f_k)) # values[f_k] = helper.string_to_sql_like_with_op(f["value"][0], op) elif filter_type in [schemas.FilterType.user_anonymous_id, schemas.FilterType.user_anonymous_id_ios]: # op = sessions.__get_sql_operator(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_anonymous_id {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_anonymous_id {op} %({f_k})s', f["value"], value_key=f_k)) # values[f_k] = helper.string_to_sql_like_with_op(f["value"][0], op) elif filter_type in [schemas.FilterType.rev_id, schemas.FilterType.rev_id_ios]: # op = sessions.__get_sql_operator(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.rev_id {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.rev_id {op} %({f_k})s', f["value"], value_key=f_k)) # values[f_k] = helper.string_to_sql_like_with_op(f["value"][0], op) i = -1 for s in stages: @@ -124,7 +125,7 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]: if not isinstance(s["value"], list): s["value"] = [s["value"]] - is_any = sessions._isAny_opreator(s["operator"]) + is_any = sh.isAny_opreator(s["operator"]) if not is_any and isinstance(s["value"], list) and len(s["value"]) == 0: continue i += 1 @@ -132,41 +133,42 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]: extra_from = filter_extra_from + ["INNER JOIN public.sessions AS s USING (session_id)"] else: extra_from = [] - op = sessions.__get_sql_operator(s["operator"]) - event_type = s["type"].upper() - if event_type == events.event_type.CLICK.ui_type: - next_table = events.event_type.CLICK.table - next_col_name = events.event_type.CLICK.column - elif event_type == events.event_type.INPUT.ui_type: - next_table = events.event_type.INPUT.table - next_col_name = events.event_type.INPUT.column - elif event_type == events.event_type.LOCATION.ui_type: - next_table = events.event_type.LOCATION.table - next_col_name = events.event_type.LOCATION.column - elif event_type == events.event_type.CUSTOM.ui_type: - next_table = events.event_type.CUSTOM.table - next_col_name = events.event_type.CUSTOM.column + op = sh.get_sql_operator(s["operator"]) + # event_type = s["type"].upper() + event_type = s["type"] + if event_type == events.EventType.CLICK.ui_type: + next_table = events.EventType.CLICK.table + next_col_name = events.EventType.CLICK.column + elif event_type == events.EventType.INPUT.ui_type: + next_table = events.EventType.INPUT.table + next_col_name = events.EventType.INPUT.column + elif event_type == events.EventType.LOCATION.ui_type: + next_table = events.EventType.LOCATION.table + next_col_name = events.EventType.LOCATION.column + elif event_type == events.EventType.CUSTOM.ui_type: + next_table = events.EventType.CUSTOM.table + next_col_name = events.EventType.CUSTOM.column # IOS -------------- - elif event_type == events.event_type.CLICK_IOS.ui_type: - next_table = events.event_type.CLICK_IOS.table - next_col_name = events.event_type.CLICK_IOS.column - elif event_type == events.event_type.INPUT_IOS.ui_type: - next_table = events.event_type.INPUT_IOS.table - next_col_name = events.event_type.INPUT_IOS.column - elif event_type == events.event_type.VIEW_IOS.ui_type: - next_table = events.event_type.VIEW_IOS.table - next_col_name = events.event_type.VIEW_IOS.column - elif event_type == events.event_type.CUSTOM_IOS.ui_type: - next_table = events.event_type.CUSTOM_IOS.table - next_col_name = events.event_type.CUSTOM_IOS.column + elif event_type == events.EventType.CLICK_IOS.ui_type: + next_table = events.EventType.CLICK_IOS.table + next_col_name = events.EventType.CLICK_IOS.column + elif event_type == events.EventType.INPUT_IOS.ui_type: + next_table = events.EventType.INPUT_IOS.table + next_col_name = events.EventType.INPUT_IOS.column + elif event_type == events.EventType.VIEW_IOS.ui_type: + next_table = events.EventType.VIEW_IOS.table + next_col_name = events.EventType.VIEW_IOS.column + elif event_type == events.EventType.CUSTOM_IOS.ui_type: + next_table = events.EventType.CUSTOM_IOS.table + next_col_name = events.EventType.CUSTOM_IOS.column else: - print("=================UNDEFINED") + print(f"=================UNDEFINED:{event_type}") continue - values = {**values, **sessions._multiple_values(helper.values_for_operator(value=s["value"], op=s["operator"]), - value_key=f"value{i + 1}")} - if sessions.__is_negation_operator(op) and i > 0: - op = sessions.__reverse_sql_operator(op) + values = {**values, **sh.multi_values(helper.values_for_operator(value=s["value"], op=s["operator"]), + value_key=f"value{i + 1}")} + if sh.is_negation_operator(op) and i > 0: + op = sh.reverse_sql_operator(op) main_condition = "left_not.session_id ISNULL" extra_from.append(f"""LEFT JOIN LATERAL (SELECT session_id FROM {next_table} AS s_main @@ -177,8 +179,8 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]: if is_any: main_condition = "TRUE" else: - main_condition = sessions._multiple_conditions(f"main.{next_col_name} {op} %(value{i + 1})s", - values=s["value"], value_key=f"value{i + 1}") + main_condition = sh.multi_conditions(f"main.{next_col_name} {op} %(value{i + 1})s", + values=s["value"], value_key=f"value{i + 1}") n_stages_query.append(f""" (SELECT main.session_id, {"MIN(main.timestamp)" if i + 1 < len(stages) else "MAX(main.timestamp)"} AS stage{i + 1}_timestamp @@ -319,7 +321,7 @@ def get_transitions_and_issues_of_each_type(rows: List[RealDictRow], all_issues, transitions ::: if transited from the first stage to the last - 1 else - 0 - errors ::: a dictionary where the keys are all unique issues (currently context-wise) + errors ::: a dictionary WHERE the keys are all unique issues (currently context-wise) the values are lists if an issue happened between the first stage to the last - 1 else - 0 diff --git a/api/chalicelib/core/slack.py b/api/chalicelib/core/slack.py deleted file mode 100644 index 76bf40163..000000000 --- a/api/chalicelib/core/slack.py +++ /dev/null @@ -1,21 +0,0 @@ -from datetime import datetime -from decouple import config - -from chalicelib.core.collaboration_slack import Slack - - -def send_batch(notifications_list): - if notifications_list is None or len(notifications_list) == 0: - return - webhookId_map = {} - for n in notifications_list: - if n.get("destination") not in webhookId_map: - webhookId_map[n.get("destination")] = {"tenantId": n["notification"]["tenantId"], "batch": []} - webhookId_map[n.get("destination")]["batch"].append({"text": n["notification"]["description"] \ - + f"\n<{config('SITE_URL')}{n['notification']['buttonUrl']}|{n['notification']['buttonText']}>", - "title": n["notification"]["title"], - "title_link": n["notification"]["buttonUrl"], - "ts": datetime.now().timestamp()}) - for batch in webhookId_map.keys(): - Slack.send_batch(tenant_id=webhookId_map[batch]["tenantId"], webhook_id=batch, - attachments=webhookId_map[batch]["batch"]) diff --git a/api/chalicelib/core/sourcemaps.py b/api/chalicelib/core/sourcemaps.py index 89df77926..844926ffa 100644 --- a/api/chalicelib/core/sourcemaps.py +++ b/api/chalicelib/core/sourcemaps.py @@ -1,4 +1,3 @@ -import hashlib from urllib.parse import urlparse import requests @@ -8,17 +7,11 @@ from chalicelib.core import sourcemaps_parser from chalicelib.utils import s3 -def __get_key(project_id, url): - u = urlparse(url) - new_url = u.scheme + "://" + u.netloc + u.path - return f"{project_id}/{hashlib.md5(new_url.encode()).hexdigest()}" - - def presign_share_urls(project_id, urls): results = [] for u in urls: results.append(s3.get_presigned_url_for_sharing(bucket=config('sourcemaps_bucket'), expires_in=120, - key=__get_key(project_id, u), + key=s3.generate_file_key_from_url(project_id, u), check_exists=True)) return results @@ -28,7 +21,7 @@ def presign_upload_urls(project_id, urls): for u in urls: results.append(s3.get_presigned_url_for_upload(bucket=config('sourcemaps_bucket'), expires_in=1800, - key=__get_key(project_id, u))) + key=s3.generate_file_key_from_url(project_id, u))) return results @@ -94,7 +87,7 @@ def get_traces_group(project_id, payload): file_exists_in_bucket = False file_exists_in_server = False file_url = u["absPath"] - key = __get_key(project_id, file_url) # use filename instead? + key = s3.generate_file_key_from_url(project_id, file_url) # use filename instead? params_idx = file_url.find("?") if file_url and len(file_url) > 0 \ and not (file_url[:params_idx] if params_idx > -1 else file_url).endswith(".js"): @@ -185,7 +178,7 @@ def fetch_missed_contexts(frames): line = lines[l] offset = c - MAX_COLUMN_OFFSET - if offset < 0: # if the line is shirt + if offset < 0: # if the line is short offset = 0 frames[i]["context"].append([frames[i]["lineNo"], line[offset: c + MAX_COLUMN_OFFSET + 1]]) return frames diff --git a/api/chalicelib/core/tenants.py b/api/chalicelib/core/tenants.py index f61456de5..5479178d8 100644 --- a/api/chalicelib/core/tenants.py +++ b/api/chalicelib/core/tenants.py @@ -6,60 +6,50 @@ from chalicelib.core import users, license def get_by_tenant_id(tenant_id): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - f"""SELECT - tenant_id, - name, - api_key, - created_at, - '{license.EDITION}' AS edition, - openreplay_version() AS version_number, - opt_out - FROM public.tenants - LIMIT 1;""", - {"tenantId": tenant_id}) - ) + query = cur.mogrify(f"""SELECT tenants.tenant_id, + tenants.name, + tenants.api_key, + tenants.created_at, + '{license.EDITION}' AS edition, + openreplay_version() AS version_number, + tenants.opt_out + FROM public.tenants + LIMIT 1;""", + {"tenantId": tenant_id}) + cur.execute(query=query) return helper.dict_to_camel_case(cur.fetchone()) def get_by_api_key(api_key): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - f"""SELECT - 1 AS tenant_id, - name, - created_at - FROM public.tenants - WHERE api_key = %(api_key)s - LIMIT 1;""", - {"api_key": api_key}) - ) + query = cur.mogrify(f"""SELECT 1 AS tenant_id, + tenants.name, + tenants.created_at + FROM public.tenants + WHERE tenants.api_key = %(api_key)s + LIMIT 1;""", + {"api_key": api_key}) + cur.execute(query=query) return helper.dict_to_camel_case(cur.fetchone()) def generate_new_api_key(tenant_id): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - f"""UPDATE public.tenants - SET api_key=generate_api_key(20) - RETURNING api_key;""", - {"tenant_id": tenant_id}) - ) + query = cur.mogrify(f"""UPDATE public.tenants + SET api_key=generate_api_key(20) + RETURNING api_key;""", + {"tenant_id": tenant_id}) + cur.execute(query=query) return helper.dict_to_camel_case(cur.fetchone()) def edit_client(tenant_id, changes): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify(f"""\ - UPDATE public.tenants - SET {", ".join([f"{helper.key_to_snake_case(k)} = %({k})s" for k in changes.keys()])} - RETURNING name, opt_out;""", - {"tenantId": tenant_id, **changes}) - ) + query = cur.mogrify(f"""UPDATE public.tenants + SET {", ".join([f"{helper.key_to_snake_case(k)} = %({k})s" for k in changes.keys()])} + RETURNING name, opt_out;""", + {"tenant_id": tenant_id, **changes}) + cur.execute(query=query) return helper.dict_to_camel_case(cur.fetchone()) diff --git a/api/chalicelib/core/users.py b/api/chalicelib/core/users.py index 94c9261ea..c4933f92c 100644 --- a/api/chalicelib/core/users.py +++ b/api/chalicelib/core/users.py @@ -22,7 +22,7 @@ def create_new_member(email, invitation_token, admin, name, owner=False): query = cur.mogrify(f"""\ WITH u AS (INSERT INTO public.users (email, role, name, data) VALUES (%(email)s, %(role)s, %(name)s, %(data)s) - RETURNING user_id,email,role,name + RETURNING user_id,email,role,name,created_at ), au AS (INSERT INTO public.basic_authentication (user_id, invitation_token, invited_at) VALUES ((SELECT user_id FROM u), %(invitation_token)s, timezone('utc'::text, now())) @@ -33,6 +33,7 @@ def create_new_member(email, invitation_token, admin, name, owner=False): u.email, u.role, u.name, + u.created_at, (CASE WHEN u.role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin, (CASE WHEN u.role = 'admin' THEN TRUE ELSE FALSE END) AS admin, (CASE WHEN u.role = 'member' THEN TRUE ELSE FALSE END) AS member, @@ -41,10 +42,11 @@ def create_new_member(email, invitation_token, admin, name, owner=False): {"email": email, "role": "owner" if owner else "admin" if admin else "member", "name": name, "data": json.dumps({"lastAnnouncementView": TimeUTC.now()}), "invitation_token": invitation_token}) - cur.execute( - query - ) - return helper.dict_to_camel_case(cur.fetchone()) + cur.execute(query) + row = helper.dict_to_camel_case(cur.fetchone()) + if row: + row["createdAt"] = TimeUTC.datetime_to_timestamp(row["createdAt"]) + return row def restore_member(user_id, email, invitation_token, admin, name, owner=False): @@ -63,12 +65,11 @@ def restore_member(user_id, email, invitation_token, admin, name, owner=False): name, (CASE WHEN role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin, (CASE WHEN role = 'admin' THEN TRUE ELSE FALSE END) AS admin, - (CASE WHEN role = 'member' THEN TRUE ELSE FALSE END) AS member;""", + (CASE WHEN role = 'member' THEN TRUE ELSE FALSE END) AS member, + created_at;""", {"user_id": user_id, "email": email, "role": "owner" if owner else "admin" if admin else "member", "name": name}) - cur.execute( - query - ) + cur.execute(query) result = cur.fetchone() query = cur.mogrify("""\ UPDATE public.basic_authentication @@ -79,10 +80,9 @@ def restore_member(user_id, email, invitation_token, admin, name, owner=False): WHERE user_id=%(user_id)s RETURNING invitation_token;""", {"user_id": user_id, "invitation_token": invitation_token}) - cur.execute( - query - ) + cur.execute(query) result["invitation_token"] = cur.fetchone()["invitation_token"] + result["created_at"] = TimeUTC.datetime_to_timestamp(result["created_at"]) return helper.dict_to_camel_case(result) @@ -181,9 +181,7 @@ def create_member(tenant_id, user_id, data, background_tasks: BackgroundTasks): if user: return {"errors": ["user already exists"]} name = data.get("name", None) - if name is not None and len(name) == 0: - return {"errors": ["invalid user name"]} - if name is None: + if name is None or len(name) == 0: name = data["email"] invitation_token = __generate_invitation_token() user = get_deleted_user_by_email(email=data["email"]) @@ -483,24 +481,8 @@ def change_password(tenant_id, user_id, email, old_password, new_password): user = update(tenant_id=tenant_id, user_id=user_id, changes=changes) r = authenticate(user['email'], new_password) - tenant_id = r.pop("tenantId") - r["limits"] = { - "teamMember": -1, - "projects": -1, - "metadata": metadata.get_remaining_metadata_with_count(tenant_id)} - - c = tenants.get_by_tenant_id(tenant_id) - c.pop("createdAt") - c["projects"] = projects.get_projects(tenant_id=tenant_id, recording_state=True, recorded=True, - stack_integrations=True) - c["smtp"] = helper.has_smtp() - c["iceServers"] = assist.get_ice_servers() return { - 'jwt': r.pop('jwt'), - 'data': { - "user": r, - "client": c - } + 'jwt': r.pop('jwt') } @@ -532,14 +514,6 @@ def set_password_invitation(user_id, new_password): } -def count_members(): - with pg_client.PostgresClient() as cur: - cur.execute("""SELECT COUNT(user_id) - FROM public.users WHERE deleted_at IS NULL;""") - r = cur.fetchone() - return r["count"] - - def email_exists(email): with pg_client.PostgresClient() as cur: cur.execute( @@ -602,12 +576,12 @@ def auth_exists(user_id, tenant_id, jwt_iat, jwt_aud): ) r = cur.fetchone() return r is not None \ - and r.get("jwt_iat") is not None \ - and (abs(jwt_iat - TimeUTC.datetime_to_timestamp(r["jwt_iat"]) // 1000) <= 1 \ - or (jwt_aud.startswith("plugin") \ - and (r["changed_at"] is None \ - or jwt_iat >= (TimeUTC.datetime_to_timestamp(r["changed_at"]) // 1000))) - ) + and r.get("jwt_iat") is not None \ + and (abs(jwt_iat - TimeUTC.datetime_to_timestamp(r["jwt_iat"]) // 1000) <= 1 \ + or (jwt_aud.startswith("plugin") \ + and (r["changed_at"] is None \ + or jwt_iat >= (TimeUTC.datetime_to_timestamp(r["changed_at"]) // 1000))) + ) def change_jwt_iat(user_id): @@ -648,9 +622,9 @@ def authenticate(email, password, for_change_password=False): return True r = helper.dict_to_camel_case(r) jwt_iat = change_jwt_iat(r['userId']) + iat = TimeUTC.datetime_to_timestamp(jwt_iat) return { - "jwt": authorizers.generate_jwt(r['userId'], r['tenantId'], - TimeUTC.datetime_to_timestamp(jwt_iat), + "jwt": authorizers.generate_jwt(r['userId'], r['tenantId'], iat=iat, aud=f"front:{helper.get_stage_name()}"), "email": email, **r diff --git a/api/chalicelib/core/webhook.py b/api/chalicelib/core/webhook.py index d74271d58..d0ed97d08 100644 --- a/api/chalicelib/core/webhook.py +++ b/api/chalicelib/core/webhook.py @@ -1,7 +1,11 @@ import logging +from typing import Optional import requests +from fastapi import HTTPException +from starlette import status +import schemas from chalicelib.utils import pg_client, helper from chalicelib.utils.TimeUTC import TimeUTC @@ -12,7 +16,7 @@ def get_by_id(webhook_id): cur.mogrify("""\ SELECT w.* FROM public.webhooks AS w - where w.webhook_id =%(webhook_id)s AND deleted_at ISNULL;""", + WHERE w.webhook_id =%(webhook_id)s AND deleted_at ISNULL;""", {"webhook_id": webhook_id}) ) w = helper.dict_to_camel_case(cur.fetchone()) @@ -21,15 +25,14 @@ def get_by_id(webhook_id): return w -def get(tenant_id, webhook_id): +def get_webhook(tenant_id, webhook_id, webhook_type='webhook'): with pg_client.PostgresClient() as cur: cur.execute( - cur.mogrify("""\ - SELECT - webhook_id AS integration_id, webhook_id AS id, w.* - FROM public.webhooks AS w - where w.webhook_id =%(webhook_id)s AND deleted_at ISNULL;""", - {"webhook_id": webhook_id}) + cur.mogrify("""SELECT w.* + FROM public.webhooks AS w + WHERE w.webhook_id =%(webhook_id)s + AND deleted_at ISNULL AND type=%(webhook_type)s;""", + {"webhook_id": webhook_id, "webhook_type": webhook_type}) ) w = helper.dict_to_camel_case(cur.fetchone()) if w: @@ -40,11 +43,9 @@ def get(tenant_id, webhook_id): def get_by_type(tenant_id, webhook_type): with pg_client.PostgresClient() as cur: cur.execute( - cur.mogrify("""\ - SELECT - w.webhook_id AS integration_id, w.webhook_id AS id,w.webhook_id,w.endpoint,w.auth_header,w.type,w.index,w.name,w.created_at - FROM public.webhooks AS w - WHERE w.type =%(type)s AND deleted_at ISNULL;""", + cur.mogrify("""SELECT w.webhook_id,w.endpoint,w.auth_header,w.type,w.index,w.name,w.created_at + FROM public.webhooks AS w + WHERE w.type =%(type)s AND deleted_at ISNULL;""", {"type": webhook_type}) ) webhooks = helper.list_to_camel_case(cur.fetchall()) @@ -55,22 +56,12 @@ def get_by_type(tenant_id, webhook_type): def get_by_tenant(tenant_id, replace_none=False): with pg_client.PostgresClient() as cur: - cur.execute("""\ - SELECT - webhook_id AS integration_id, webhook_id AS id, w.* - FROM public.webhooks AS w - WHERE deleted_at ISNULL;""" - ) + cur.execute("""SELECT w.* + FROM public.webhooks AS w + WHERE deleted_at ISNULL;""") all = helper.list_to_camel_case(cur.fetchall()) - if replace_none: - for w in all: - w["createdAt"] = TimeUTC.datetime_to_timestamp(w["createdAt"]) - for k in w.keys(): - if w[k] is None: - w[k] = '' - else: - for w in all: - w["createdAt"] = TimeUTC.datetime_to_timestamp(w["createdAt"]) + for w in all: + w["createdAt"] = TimeUTC.datetime_to_timestamp(w["createdAt"]) return all @@ -83,7 +74,7 @@ def update(tenant_id, webhook_id, changes, replace_none=False): UPDATE public.webhooks SET {','.join(sub_query)} WHERE webhook_id =%(id)s AND deleted_at ISNULL - RETURNING webhook_id AS integration_id, webhook_id AS id,*;""", + RETURNING *;""", {"id": webhook_id, **changes}) ) w = helper.dict_to_camel_case(cur.fetchone()) @@ -100,7 +91,7 @@ def add(tenant_id, endpoint, auth_header=None, webhook_type='webhook', name="", query = cur.mogrify("""\ INSERT INTO public.webhooks(endpoint,auth_header,type,name) VALUES (%(endpoint)s, %(auth_header)s, %(type)s,%(name)s) - RETURNING webhook_id AS integration_id, webhook_id AS id,*;""", + RETURNING *;""", {"endpoint": endpoint, "auth_header": auth_header, "type": webhook_type, "name": name}) cur.execute( @@ -115,7 +106,25 @@ def add(tenant_id, endpoint, auth_header=None, webhook_type='webhook', name="", return w +def exists_by_name(name: str, exclude_id: Optional[int], webhook_type: str = schemas.WebhookType.webhook, + tenant_id: Optional[int] = None) -> bool: + with pg_client.PostgresClient() as cur: + query = cur.mogrify(f"""SELECT EXISTS(SELECT 1 + FROM public.webhooks + WHERE name ILIKE %(name)s + AND deleted_at ISNULL + AND type=%(webhook_type)s + {"AND webhook_id!=%(exclude_id)s" if exclude_id else ""}) AS exists;""", + {"name": name, "exclude_id": exclude_id, "webhook_type": webhook_type}) + cur.execute(query) + row = cur.fetchone() + return row["exists"] + + def add_edit(tenant_id, data, replace_none=None): + if "name" in data and len(data["name"]) > 0 \ + and exists_by_name(name=data["name"], exclude_id=data.get("webhookId")): + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"name already exists.") if data.get("webhookId") is not None: return update(tenant_id=tenant_id, webhook_id=data["webhookId"], changes={"endpoint": data["endpoint"], diff --git a/api/chalicelib/utils/email_handler.py b/api/chalicelib/utils/email_handler.py index b3c7d9984..5b5fb49cb 100644 --- a/api/chalicelib/utils/email_handler.py +++ b/api/chalicelib/utils/email_handler.py @@ -10,6 +10,10 @@ from decouple import config from chalicelib.utils import smtp +loglevel = config("LOGLEVEL", default=logging.INFO) +print(f">Loglevel set to: {loglevel}") +logging.basicConfig(level=loglevel) + def __get_subject(subject): return subject diff --git a/api/chalicelib/utils/errors_helper.py b/api/chalicelib/utils/errors_helper.py new file mode 100644 index 000000000..73a430110 --- /dev/null +++ b/api/chalicelib/utils/errors_helper.py @@ -0,0 +1,14 @@ +from chalicelib.core import sourcemaps + + +def format_first_stack_frame(error): + error["stack"] = sourcemaps.format_payload(error.pop("payload"), truncate_to_first=True) + for s in error["stack"]: + for c in s.get("context", []): + for sci, sc in enumerate(c): + if isinstance(sc, str) and len(sc) > 1000: + c[sci] = sc[:1000] + # convert bytes to string: + if isinstance(s["filename"], bytes): + s["filename"] = s["filename"].decode("utf-8") + return error diff --git a/api/chalicelib/utils/helper.py b/api/chalicelib/utils/helper.py index 7639f1950..369aff40a 100644 --- a/api/chalicelib/utils/helper.py +++ b/api/chalicelib/utils/helper.py @@ -3,6 +3,7 @@ import random import re import string from typing import Union +from urllib.parse import urlparse from decouple import config @@ -98,7 +99,7 @@ TRACK_TIME = True def allow_captcha(): return config("captcha_server", default=None) is not None and config("captcha_key", default=None) is not None \ - and len(config("captcha_server")) > 0 and len(config("captcha_key")) > 0 + and len(config("captcha_server")) > 0 and len(config("captcha_key")) > 0 def string_to_sql_like(value): @@ -282,6 +283,7 @@ def custom_alert_to_front(values): # to support frontend format for payload if values.get("seriesId") is not None and values["query"]["left"] == schemas.AlertColumn.custom: values["query"]["left"] = values["seriesId"] + values["seriesId"] = None return values @@ -304,3 +306,16 @@ def __time_value(row): def is_saml2_available(): return config("hastSAML2", default=False, cast=bool) + + +def get_domain(): + _url = config("SITE_URL") + if not _url.startswith("http"): + _url = "http://" + _url + return '.'.join(urlparse(_url).netloc.split(".")[-2:]) + + +def obfuscate(text, keep_last: int = 4): + if text is None or not isinstance(text, str): + return text + return "*" * (len(text) - keep_last) + text[-keep_last:] diff --git a/api/chalicelib/utils/html/alert_notification.html b/api/chalicelib/utils/html/alert_notification.html index 2d63341f3..0f475dae1 100644 --- a/api/chalicelib/utils/html/alert_notification.html +++ b/api/chalicelib/utils/html/alert_notification.html @@ -1,60 +1,72 @@ - - - - + + + +
-
- - - - - - + +
-
- OpenReplay -
-
+ + + - - - -
+
+ + + + + - - - - - - - - + - -
+
+ OpenReplay +
+
-

- New alert!

-

- %(message)s

-

- See metrics for more details.

+ - -
-
-
-

- Sent with ♡ from OpenReplay © 2022 - All rights reserved.

- https://openreplay.com/ +

+

+ New alert!

+

+ %(message)s

+

+ See metrics for more details.

- -
+
+ + +
+
+
+
+

+ Sent with ♡ from OpenReplay © + - All rights + reserved.

+ https://openreplay.com/ +

+ +
+
+ + + + + - + \ No newline at end of file diff --git a/api/chalicelib/utils/jira_client.py b/api/chalicelib/utils/jira_client.py index ee8196a46..4cd6fe566 100644 --- a/api/chalicelib/utils/jira_client.py +++ b/api/chalicelib/utils/jira_client.py @@ -6,7 +6,7 @@ from jira import JIRA from jira.exceptions import JIRAError from requests.auth import HTTPBasicAuth from starlette import status -from starlette.exceptions import HTTPException +from fastapi import HTTPException fields = "id, summary, description, creator, reporter, created, assignee, status, updated, comment, issuetype, labels" diff --git a/api/chalicelib/utils/s3.py b/api/chalicelib/utils/s3.py index 5458c8f14..366a5d181 100644 --- a/api/chalicelib/utils/s3.py +++ b/api/chalicelib/utils/s3.py @@ -1,9 +1,13 @@ -from botocore.exceptions import ClientError -from decouple import config +import hashlib from datetime import datetime, timedelta +from urllib.parse import urlparse + import boto3 import botocore from botocore.client import Config +from botocore.exceptions import ClientError +from decouple import config +from requests.models import PreparedRequest if not config("S3_HOST", default=False): client = boto3.client('s3') @@ -51,7 +55,7 @@ def get_presigned_url_for_sharing(bucket, expires_in, key, check_exists=False): ) -def get_presigned_url_for_upload(bucket, expires_in, key): +def get_presigned_url_for_upload(bucket, expires_in, key, **args): return client.generate_presigned_url( 'put_object', Params={ @@ -62,6 +66,25 @@ def get_presigned_url_for_upload(bucket, expires_in, key): ) +def get_presigned_url_for_upload_secure(bucket, expires_in, key, conditions=None, public=False, content_type=None): + acl = 'private' + if public: + acl = 'public-read' + fields = {"acl": acl} + if content_type: + fields["Content-Type"] = content_type + url_parts = client.generate_presigned_post( + Bucket=bucket, + Key=key, + ExpiresIn=expires_in, + Fields=fields, + Conditions=conditions, + ) + req = PreparedRequest() + req.prepare_url(f"{url_parts['url']}/{url_parts['fields']['key']}", url_parts['fields']) + return req.url + + def get_file(source_bucket, source_key): try: result = client.get_object( @@ -88,3 +111,13 @@ def schedule_for_deletion(bucket, key): s3_object.copy_from(CopySource={'Bucket': bucket, 'Key': key}, Expires=datetime.now() + timedelta(days=7), MetadataDirective='REPLACE') + + +def generate_file_key(project_id, key): + return f"{project_id}/{hashlib.md5(key.encode()).hexdigest()}" + + +def generate_file_key_from_url(project_id, url): + u = urlparse(url) + new_url = u.scheme + "://" + u.netloc + u.path + return generate_file_key(project_id=project_id, key=new_url) diff --git a/api/chalicelib/utils/smtp.py b/api/chalicelib/utils/smtp.py index 63e1621fb..a4710b42f 100644 --- a/api/chalicelib/utils/smtp.py +++ b/api/chalicelib/utils/smtp.py @@ -3,7 +3,7 @@ import smtplib from smtplib import SMTPAuthenticationError from decouple import config -from starlette.exceptions import HTTPException +from fastapi import HTTPException class EmptySMTP: @@ -17,20 +17,20 @@ class SMTPClient: def __init__(self): if config("EMAIL_HOST") is None or len(config("EMAIL_HOST")) == 0: return - elif config("EMAIL_USE_SSL").lower() == "false": - self.server = smtplib.SMTP(host=config("EMAIL_HOST"), port=int(config("EMAIL_PORT"))) + elif not config("EMAIL_USE_SSL", cast=bool): + self.server = smtplib.SMTP(host=config("EMAIL_HOST"), port=config("EMAIL_PORT", cast=int)) else: if len(config("EMAIL_SSL_KEY")) == 0 or len(config("EMAIL_SSL_CERT")) == 0: - self.server = smtplib.SMTP_SSL(host=config("EMAIL_HOST"), port=int(config("EMAIL_PORT"))) + self.server = smtplib.SMTP_SSL(host=config("EMAIL_HOST"), port=config("EMAIL_PORT", cast=int)) else: - self.server = smtplib.SMTP_SSL(host=config("EMAIL_HOST"), port=int(config("EMAIL_PORT")), + self.server = smtplib.SMTP_SSL(host=config("EMAIL_HOST"), port=config("EMAIL_PORT", cast=int), keyfile=config("EMAIL_SSL_KEY"), certfile=config("EMAIL_SSL_CERT")) def __enter__(self): if self.server is None: return EmptySMTP() self.server.ehlo() - if config("EMAIL_USE_SSL").lower() == "false" and config("EMAIL_USE_TLS").lower() == "true": + if not config("EMAIL_USE_SSL", cast=bool) and config("EMAIL_USE_TLS", cast=bool): self.server.starttls() # stmplib docs recommend calling ehlo() before & after starttls() self.server.ehlo() diff --git a/api/chalicelib/utils/sql_helper.py b/api/chalicelib/utils/sql_helper.py new file mode 100644 index 000000000..02744595a --- /dev/null +++ b/api/chalicelib/utils/sql_helper.py @@ -0,0 +1,53 @@ +from typing import Union + +import schemas + + +def get_sql_operator(op: Union[schemas.SearchEventOperator, schemas.ClickEventExtraOperator]): + return { + schemas.SearchEventOperator._is: "=", + schemas.SearchEventOperator._is_any: "IN", + schemas.SearchEventOperator._on: "=", + schemas.SearchEventOperator._on_any: "IN", + schemas.SearchEventOperator._is_not: "!=", + schemas.SearchEventOperator._not_on: "!=", + schemas.SearchEventOperator._contains: "ILIKE", + schemas.SearchEventOperator._not_contains: "NOT ILIKE", + schemas.SearchEventOperator._starts_with: "ILIKE", + schemas.SearchEventOperator._ends_with: "ILIKE", + }.get(op, "=") + + +def is_negation_operator(op: schemas.SearchEventOperator): + return op in [schemas.SearchEventOperator._is_not, + schemas.SearchEventOperator._not_on, + schemas.SearchEventOperator._not_contains] + + +def reverse_sql_operator(op): + return "=" if op == "!=" else "!=" if op == "=" else "ILIKE" if op == "NOT ILIKE" else "NOT ILIKE" + + +def multi_conditions(condition, values, value_key="value", is_not=False): + query = [] + for i in range(len(values)): + k = f"{value_key}_{i}" + query.append(condition.replace(value_key, k)) + return "(" + (" AND " if is_not else " OR ").join(query) + ")" + + +def multi_values(values, value_key="value"): + query_values = {} + if values is not None and isinstance(values, list): + for i in range(len(values)): + k = f"{value_key}_{i}" + query_values[k] = values[i] + return query_values + + +def isAny_opreator(op: schemas.SearchEventOperator): + return op in [schemas.SearchEventOperator._on_any, schemas.SearchEventOperator._is_any] + + +def isUndefined_operator(op: schemas.SearchEventOperator): + return op in [schemas.SearchEventOperator._is_undefined] diff --git a/api/entrypoint.bundle.sh b/api/entrypoint.bundle.sh deleted file mode 100755 index 8478d9d14..000000000 --- a/api/entrypoint.bundle.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -cd utilities -nohup npm start &> /tmp/utilities.log & -cd .. -python env_handler.py -chalice local --no-autoreload --host 0.0.0.0 --stage ${ENTERPRISE_BUILD} diff --git a/api/env.default b/api/env.default index 3ee65e89c..78acd001c 100644 --- a/api/env.default +++ b/api/env.default @@ -22,6 +22,7 @@ JWT_EXPIRATION=2592000 JWT_ISSUER=openreplay-oss jwt_secret="SET A RANDOM STRING HERE" ASSIST_URL=http://assist-openreplay.app.svc.cluster.local:9001/assist/%s +ASSIST_KEY= assist=/sockets-live assistList=/sockets-list pg_dbname=postgres @@ -50,4 +51,5 @@ DEVTOOLS_MOB_PATTERN=%(sessionId)s/devtools.mob PRESIGNED_URL_EXPIRATION=3600 ASSIST_JWT_EXPIRATION=144000 ASSIST_JWT_SECRET= -PYTHONUNBUFFERED=1 \ No newline at end of file +PYTHONUNBUFFERED=1 +THUMBNAILS_BUCKET=thumbnails \ No newline at end of file diff --git a/api/requirements-alerts.txt b/api/requirements-alerts.txt index ff36f3099..b208d28c2 100644 --- a/api/requirements-alerts.txt +++ b/api/requirements-alerts.txt @@ -1,15 +1,15 @@ -requests==2.28.1 -urllib3==1.26.12 -boto3==1.26.14 +requests==2.28.2 +urllib3==1.26.14 +boto3==1.26.70 pyjwt==2.6.0 psycopg2-binary==2.9.5 -elasticsearch==8.5.1 +elasticsearch==8.6.1 jira==3.4.1 -fastapi==0.87.0 +fastapi==0.92.0 uvicorn[standard]==0.20.0 -python-decouple==3.6 -pydantic[email]==1.10.2 -apscheduler==3.9.1.post1 \ No newline at end of file +python-decouple==3.7 +pydantic[email]==1.10.4 +apscheduler==3.10.0 \ No newline at end of file diff --git a/api/requirements.txt b/api/requirements.txt index ff36f3099..0a058a94f 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -1,15 +1,15 @@ -requests==2.28.1 -urllib3==1.26.12 -boto3==1.26.14 +requests==2.28.2 +urllib3==1.26.14 +boto3==1.26.70 pyjwt==2.6.0 psycopg2-binary==2.9.5 -elasticsearch==8.5.1 +elasticsearch==8.6.1 jira==3.4.1 -fastapi==0.87.0 +fastapi==0.92.0 uvicorn[standard]==0.20.0 -python-decouple==3.6 -pydantic[email]==1.10.2 -apscheduler==3.9.1.post1 \ No newline at end of file +python-decouple==3.7 +pydantic[email]==1.10.4 +apscheduler==3.10.0 diff --git a/api/routers/core.py b/api/routers/core.py index 7ee8364e7..67e33a50a 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -1,17 +1,19 @@ from typing import Union from decouple import config -from fastapi import Depends, Body, HTTPException +from fastapi import Depends, Body, HTTPException, Response +from fastapi.responses import JSONResponse from starlette import status import schemas from chalicelib.core import log_tool_rollbar, sourcemaps, events, sessions_assignments, projects, \ - alerts, funnels, issues, integrations_manager, metadata, \ + alerts, issues, integrations_manager, metadata, \ log_tool_elasticsearch, log_tool_datadog, \ log_tool_stackdriver, reset_password, log_tool_cloudwatch, log_tool_sentry, log_tool_sumologic, log_tools, sessions, \ log_tool_newrelic, announcements, log_tool_bugsnag, weekly_report, integration_jira_cloud, integration_github, \ assist, mobile, signup, tenants, boarding, notifications, webhook, users, \ custom_metrics, saved_search, integrations_global +from chalicelib.core.collaboration_msteams import MSTeams from chalicelib.core.collaboration_slack import Slack from chalicelib.utils import helper, captcha from or_dependencies import OR_context @@ -21,7 +23,7 @@ public_app, app, app_apikey = get_routers() @public_app.post('/login', tags=["authentication"]) -def login(data: schemas.UserLoginSchema = Body(...)): +async def login(data: schemas.UserLoginSchema = Body(...)): if helper.allow_captcha() and not captcha.is_valid(data.g_recaptcha_response): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -39,36 +41,45 @@ def login(data: schemas.UserLoginSchema = Body(...)): status_code=status.HTTP_401_UNAUTHORIZED, detail=r["errors"][0] ) + r["smtp"] = helper.has_smtp() - return { + content = { 'jwt': r.pop('jwt'), 'data': { "user": r } } + response = JSONResponse(content=content) + response.set_cookie(key="jwt", value=content['jwt'], domain=helper.get_domain(), + expires=config("JWT_EXPIRATION", cast=int)) + return response + + +@app.get('/logout', tags=["login", "logout"]) +async def logout_user(response: Response, context: schemas.CurrentContext = Depends(OR_context)): + response.delete_cookie("jwt") + return {"data": "success"} @app.post('/{projectId}/sessions/search', tags=["sessions"]) -@app.post('/{projectId}/sessions/search2', tags=["sessions"]) -def sessions_search(projectId: int, data: schemas.FlatSessionsSearchPayloadSchema = Body(...), +async def sessions_search(projectId: int, data: schemas.FlatSessionsSearchPayloadSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): data = sessions.search_sessions(data=data, project_id=projectId, user_id=context.user_id) return {'data': data} @app.post('/{projectId}/sessions/search/ids', tags=["sessions"]) -@app.post('/{projectId}/sessions/search2/ids', tags=["sessions"]) -def session_ids_search(projectId: int, data: schemas.FlatSessionsSearchPayloadSchema = Body(...), +async def session_ids_search(projectId: int, data: schemas.FlatSessionsSearchPayloadSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): data = sessions.search_sessions(data=data, project_id=projectId, user_id=context.user_id, ids_only=True) return {'data': data} @app.get('/{projectId}/events/search', tags=["events"]) -def events_search(projectId: int, q: str, +async def events_search(projectId: int, q: str, type: Union[schemas.FilterType, schemas.EventType, - schemas.PerformanceEventType, schemas.FetchFilterType, - schemas.GraphqlFilterType, str] = None, + schemas.PerformanceEventType, schemas.FetchFilterType, + schemas.GraphqlFilterType, str] = None, key: str = None, source: str = None, live: bool = False, context: schemas.CurrentContext = Depends(OR_context)): if len(q) == 0: @@ -97,170 +108,176 @@ def events_search(projectId: int, q: str, @app.get('/{projectId}/integrations', tags=["integrations"]) -def get_integrations_status(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_integrations_status(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): data = integrations_global.get_global_integrations_status(tenant_id=context.tenant_id, user_id=context.user_id, project_id=projectId) return {"data": data} -@app.post('/{projectId}/integrations/{integration}/notify/{integrationId}/{source}/{sourceId}', tags=["integrations"]) -def integration_notify(projectId: int, integration: str, integrationId: int, source: str, sourceId: str, +@app.post('/{projectId}/integrations/{integration}/notify/{webhookId}/{source}/{sourceId}', tags=["integrations"]) +async def integration_notify(projectId: int, integration: str, webhookId: int, source: str, sourceId: str, data: schemas.IntegrationNotificationSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): comment = None if data.comment: comment = data.comment - if integration == "slack": - args = {"tenant_id": context.tenant_id, - "user": context.email, "comment": comment, "project_id": projectId, - "integration_id": integrationId} + + args = {"tenant_id": context.tenant_id, + "user": context.email, "comment": comment, "project_id": projectId, + "integration_id": webhookId} + if integration == schemas.WebhookType.slack: if source == "sessions": return Slack.share_session(session_id=sourceId, **args) elif source == "errors": return Slack.share_error(error_id=sourceId, **args) + elif integration == schemas.WebhookType.msteams: + if source == "sessions": + return MSTeams.share_session(session_id=sourceId, **args) + elif source == "errors": + return MSTeams.share_error(error_id=sourceId, **args) return {"data": None} @app.get('/integrations/sentry', tags=["integrations"]) -def get_all_sentry(context: schemas.CurrentContext = Depends(OR_context)): +async def get_all_sentry(context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_sentry.get_all(tenant_id=context.tenant_id)} @app.get('/{projectId}/integrations/sentry', tags=["integrations"]) -def get_sentry(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_sentry(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_sentry.get(project_id=projectId)} @app.post('/{projectId}/integrations/sentry', tags=["integrations"]) -def add_edit_sentry(projectId: int, data: schemas.SentrySchema = Body(...), +async def add_edit_sentry(projectId: int, data: schemas.SentrySchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_sentry.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())} @app.delete('/{projectId}/integrations/sentry', tags=["integrations"]) -def delete_sentry(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_sentry(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_sentry.delete(tenant_id=context.tenant_id, project_id=projectId)} @app.get('/{projectId}/integrations/sentry/events/{eventId}', tags=["integrations"]) -def proxy_sentry(projectId: int, eventId: str, context: schemas.CurrentContext = Depends(OR_context)): +async def proxy_sentry(projectId: int, eventId: str, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_sentry.proxy_get(tenant_id=context.tenant_id, project_id=projectId, event_id=eventId)} @app.get('/integrations/datadog', tags=["integrations"]) -def get_all_datadog(context: schemas.CurrentContext = Depends(OR_context)): +async def get_all_datadog(context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_datadog.get_all(tenant_id=context.tenant_id)} @app.get('/{projectId}/integrations/datadog', tags=["integrations"]) -def get_datadog(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_datadog(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_datadog.get(project_id=projectId)} @app.post('/{projectId}/integrations/datadog', tags=["integrations"]) -def add_edit_datadog(projectId: int, data: schemas.DatadogSchema = Body(...), +async def add_edit_datadog(projectId: int, data: schemas.DatadogSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_datadog.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())} @app.delete('/{projectId}/integrations/datadog', tags=["integrations"]) -def delete_datadog(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_datadog(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_datadog.delete(tenant_id=context.tenant_id, project_id=projectId)} @app.get('/integrations/stackdriver', tags=["integrations"]) -def get_all_stackdriver(context: schemas.CurrentContext = Depends(OR_context)): +async def get_all_stackdriver(context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_stackdriver.get_all(tenant_id=context.tenant_id)} @app.get('/{projectId}/integrations/stackdriver', tags=["integrations"]) -def get_stackdriver(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_stackdriver(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_stackdriver.get(project_id=projectId)} @app.post('/{projectId}/integrations/stackdriver', tags=["integrations"]) -def add_edit_stackdriver(projectId: int, data: schemas.StackdriverSchema = Body(...), +async def add_edit_stackdriver(projectId: int, data: schemas.StackdriverSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_stackdriver.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())} @app.delete('/{projectId}/integrations/stackdriver', tags=["integrations"]) -def delete_stackdriver(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_stackdriver(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_stackdriver.delete(tenant_id=context.tenant_id, project_id=projectId)} @app.get('/integrations/newrelic', tags=["integrations"]) -def get_all_newrelic(context: schemas.CurrentContext = Depends(OR_context)): +async def get_all_newrelic(context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_newrelic.get_all(tenant_id=context.tenant_id)} @app.get('/{projectId}/integrations/newrelic', tags=["integrations"]) -def get_newrelic(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_newrelic(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_newrelic.get(project_id=projectId)} @app.post('/{projectId}/integrations/newrelic', tags=["integrations"]) -def add_edit_newrelic(projectId: int, data: schemas.NewrelicSchema = Body(...), +async def add_edit_newrelic(projectId: int, data: schemas.NewrelicSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_newrelic.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())} @app.delete('/{projectId}/integrations/newrelic', tags=["integrations"]) -def delete_newrelic(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_newrelic(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_newrelic.delete(tenant_id=context.tenant_id, project_id=projectId)} @app.get('/integrations/rollbar', tags=["integrations"]) -def get_all_rollbar(context: schemas.CurrentContext = Depends(OR_context)): +async def get_all_rollbar(context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_rollbar.get_all(tenant_id=context.tenant_id)} @app.get('/{projectId}/integrations/rollbar', tags=["integrations"]) -def get_rollbar(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_rollbar(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_rollbar.get(project_id=projectId)} @app.post('/{projectId}/integrations/rollbar', tags=["integrations"]) -def add_edit_rollbar(projectId: int, data: schemas.RollbarSchema = Body(...), +async def add_edit_rollbar(projectId: int, data: schemas.RollbarSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_rollbar.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())} @app.delete('/{projectId}/integrations/rollbar', tags=["integrations"]) -def delete_datadog(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_datadog(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_rollbar.delete(tenant_id=context.tenant_id, project_id=projectId)} @app.post('/integrations/bugsnag/list_projects', tags=["integrations"]) -def list_projects_bugsnag(data: schemas.BugsnagBasicSchema = Body(...), +async def list_projects_bugsnag(data: schemas.BugsnagBasicSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_bugsnag.list_projects(auth_token=data.authorizationToken)} @app.get('/integrations/bugsnag', tags=["integrations"]) -def get_all_bugsnag(context: schemas.CurrentContext = Depends(OR_context)): +async def get_all_bugsnag(context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_bugsnag.get_all(tenant_id=context.tenant_id)} @app.get('/{projectId}/integrations/bugsnag', tags=["integrations"]) -def get_bugsnag(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_bugsnag(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_bugsnag.get(project_id=projectId)} @app.post('/{projectId}/integrations/bugsnag', tags=["integrations"]) -def add_edit_bugsnag(projectId: int, data: schemas.BugsnagSchema = Body(...), +async def add_edit_bugsnag(projectId: int, data: schemas.BugsnagSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_bugsnag.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())} @app.delete('/{projectId}/integrations/bugsnag', tags=["integrations"]) -def delete_bugsnag(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_bugsnag(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_bugsnag.delete(tenant_id=context.tenant_id, project_id=projectId)} @app.post('/integrations/cloudwatch/list_groups', tags=["integrations"]) -def list_groups_cloudwatch(data: schemas.CloudwatchBasicSchema = Body(...), +async def list_groups_cloudwatch(data: schemas.CloudwatchBasicSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_cloudwatch.list_log_groups(aws_access_key_id=data.awsAccessKeyId, aws_secret_access_key=data.awsSecretAccessKey, @@ -268,77 +285,77 @@ def list_groups_cloudwatch(data: schemas.CloudwatchBasicSchema = Body(...), @app.get('/integrations/cloudwatch', tags=["integrations"]) -def get_all_cloudwatch(context: schemas.CurrentContext = Depends(OR_context)): +async def get_all_cloudwatch(context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_cloudwatch.get_all(tenant_id=context.tenant_id)} @app.get('/{projectId}/integrations/cloudwatch', tags=["integrations"]) -def get_cloudwatch(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_cloudwatch(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_cloudwatch.get(project_id=projectId)} @app.post('/{projectId}/integrations/cloudwatch', tags=["integrations"]) -def add_edit_cloudwatch(projectId: int, data: schemas.CloudwatchSchema = Body(...), +async def add_edit_cloudwatch(projectId: int, data: schemas.CloudwatchSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_cloudwatch.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())} @app.delete('/{projectId}/integrations/cloudwatch', tags=["integrations"]) -def delete_cloudwatch(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_cloudwatch(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_cloudwatch.delete(tenant_id=context.tenant_id, project_id=projectId)} @app.get('/integrations/elasticsearch', tags=["integrations"]) -def get_all_elasticsearch(context: schemas.CurrentContext = Depends(OR_context)): +async def get_all_elasticsearch(context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_elasticsearch.get_all(tenant_id=context.tenant_id)} @app.get('/{projectId}/integrations/elasticsearch', tags=["integrations"]) -def get_elasticsearch(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_elasticsearch(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_elasticsearch.get(project_id=projectId)} @app.post('/integrations/elasticsearch/test', tags=["integrations"]) -def test_elasticsearch_connection(data: schemas.ElasticsearchBasicSchema = Body(...), +async def test_elasticsearch_connection(data: schemas.ElasticsearchBasicSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_elasticsearch.ping(tenant_id=context.tenant_id, **data.dict())} @app.post('/{projectId}/integrations/elasticsearch', tags=["integrations"]) -def add_edit_elasticsearch(projectId: int, data: schemas.ElasticsearchSchema = Body(...), +async def add_edit_elasticsearch(projectId: int, data: schemas.ElasticsearchSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return { "data": log_tool_elasticsearch.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())} @app.delete('/{projectId}/integrations/elasticsearch', tags=["integrations"]) -def delete_elasticsearch(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_elasticsearch(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_elasticsearch.delete(tenant_id=context.tenant_id, project_id=projectId)} @app.get('/integrations/sumologic', tags=["integrations"]) -def get_all_sumologic(context: schemas.CurrentContext = Depends(OR_context)): +async def get_all_sumologic(context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_sumologic.get_all(tenant_id=context.tenant_id)} @app.get('/{projectId}/integrations/sumologic', tags=["integrations"]) -def get_sumologic(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_sumologic(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_sumologic.get(project_id=projectId)} @app.post('/{projectId}/integrations/sumologic', tags=["integrations"]) -def add_edit_sumologic(projectId: int, data: schemas.SumologicSchema = Body(...), +async def add_edit_sumologic(projectId: int, data: schemas.SumologicSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_sumologic.add_edit(tenant_id=context.tenant_id, project_id=projectId, data=data.dict())} @app.delete('/{projectId}/integrations/sumologic', tags=["integrations"]) -def delete_sumologic(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_sumologic(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": log_tool_sumologic.delete(tenant_id=context.tenant_id, project_id=projectId)} @app.get('/integrations/issues', tags=["integrations"]) -def get_integration_status(context: schemas.CurrentContext = Depends(OR_context)): +async def get_integration_status(context: schemas.CurrentContext = Depends(OR_context)): error, integration = integrations_manager.get_integration(tenant_id=context.tenant_id, user_id=context.user_id) if error is not None and integration is None: @@ -347,7 +364,7 @@ def get_integration_status(context: schemas.CurrentContext = Depends(OR_context) @app.get('/integrations/jira', tags=["integrations"]) -def get_integration_status_jira(context: schemas.CurrentContext = Depends(OR_context)): +async def get_integration_status_jira(context: schemas.CurrentContext = Depends(OR_context)): error, integration = integrations_manager.get_integration(tenant_id=context.tenant_id, user_id=context.user_id, tool=integration_jira_cloud.PROVIDER) @@ -357,7 +374,7 @@ def get_integration_status_jira(context: schemas.CurrentContext = Depends(OR_con @app.get('/integrations/github', tags=["integrations"]) -def get_integration_status_github(context: schemas.CurrentContext = Depends(OR_context)): +async def get_integration_status_github(context: schemas.CurrentContext = Depends(OR_context)): error, integration = integrations_manager.get_integration(tenant_id=context.tenant_id, user_id=context.user_id, tool=integration_github.PROVIDER) @@ -367,7 +384,7 @@ def get_integration_status_github(context: schemas.CurrentContext = Depends(OR_c @app.post('/integrations/jira', tags=["integrations"]) -def add_edit_jira_cloud(data: schemas.JiraSchema = Body(...), +async def add_edit_jira_cloud(data: schemas.JiraSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): if not data.url.endswith('atlassian.net'): return {"errors": ["url must be a valid JIRA URL (example.atlassian.net)"]} @@ -380,7 +397,7 @@ def add_edit_jira_cloud(data: schemas.JiraSchema = Body(...), @app.post('/integrations/github', tags=["integrations"]) -def add_edit_github(data: schemas.GithubSchema = Body(...), +async def add_edit_github(data: schemas.GithubSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): error, integration = integrations_manager.get_integration(tool=integration_github.PROVIDER, tenant_id=context.tenant_id, @@ -391,7 +408,7 @@ def add_edit_github(data: schemas.GithubSchema = Body(...), @app.delete('/integrations/issues', tags=["integrations"]) -def delete_default_issue_tracking_tool(context: schemas.CurrentContext = Depends(OR_context)): +async def delete_default_issue_tracking_tool(context: schemas.CurrentContext = Depends(OR_context)): error, integration = integrations_manager.get_integration(tenant_id=context.tenant_id, user_id=context.user_id) if error is not None and integration is None: @@ -400,7 +417,7 @@ def delete_default_issue_tracking_tool(context: schemas.CurrentContext = Depends @app.delete('/integrations/jira', tags=["integrations"]) -def delete_jira_cloud(context: schemas.CurrentContext = Depends(OR_context)): +async def delete_jira_cloud(context: schemas.CurrentContext = Depends(OR_context)): error, integration = integrations_manager.get_integration(tool=integration_jira_cloud.PROVIDER, tenant_id=context.tenant_id, user_id=context.user_id, @@ -411,7 +428,7 @@ def delete_jira_cloud(context: schemas.CurrentContext = Depends(OR_context)): @app.delete('/integrations/github', tags=["integrations"]) -def delete_github(context: schemas.CurrentContext = Depends(OR_context)): +async def delete_github(context: schemas.CurrentContext = Depends(OR_context)): error, integration = integrations_manager.get_integration(tool=integration_github.PROVIDER, tenant_id=context.tenant_id, user_id=context.user_id, @@ -422,7 +439,7 @@ def delete_github(context: schemas.CurrentContext = Depends(OR_context)): @app.get('/integrations/issues/list_projects', tags=["integrations"]) -def get_all_issue_tracking_projects(context: schemas.CurrentContext = Depends(OR_context)): +async def get_all_issue_tracking_projects(context: schemas.CurrentContext = Depends(OR_context)): error, integration = integrations_manager.get_integration(tenant_id=context.tenant_id, user_id=context.user_id) if error is not None: @@ -434,7 +451,7 @@ def get_all_issue_tracking_projects(context: schemas.CurrentContext = Depends(OR @app.get('/integrations/issues/{integrationProjectId}', tags=["integrations"]) -def get_integration_metadata(integrationProjectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_integration_metadata(integrationProjectId: int, context: schemas.CurrentContext = Depends(OR_context)): error, integration = integrations_manager.get_integration(tenant_id=context.tenant_id, user_id=context.user_id) if error is not None: @@ -446,7 +463,7 @@ def get_integration_metadata(integrationProjectId: int, context: schemas.Current @app.get('/{projectId}/assignments', tags=["assignment"]) -def get_all_assignments(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_all_assignments(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): data = sessions_assignments.get_all(project_id=projectId, user_id=context.user_id) return { 'data': data @@ -454,7 +471,7 @@ def get_all_assignments(projectId: int, context: schemas.CurrentContext = Depend @app.post('/{projectId}/sessions/{sessionId}/assign/projects/{integrationProjectId}', tags=["assignment"]) -def create_issue_assignment(projectId: int, sessionId: int, integrationProjectId, +async def create_issue_assignment(projectId: int, sessionId: int, integrationProjectId, data: schemas.AssignmentSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): data = sessions_assignments.create_new_assignment(tenant_id=context.tenant_id, project_id=projectId, @@ -471,54 +488,57 @@ def create_issue_assignment(projectId: int, sessionId: int, integrationProjectId @app.get('/{projectId}/gdpr', tags=["projects", "gdpr"]) -def get_gdpr(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_gdpr(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": projects.get_gdpr(project_id=projectId)} @app.post('/{projectId}/gdpr', tags=["projects", "gdpr"]) -def edit_gdpr(projectId: int, data: schemas.GdprSchema = Body(...), +async def edit_gdpr(projectId: int, data: schemas.GdprSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): - return {"data": projects.edit_gdpr(project_id=projectId, gdpr=data.dict())} + result = projects.edit_gdpr(project_id=projectId, gdpr=data.dict()) + if "errors" in result: + return result + return {"data": result} @public_app.post('/password/reset-link', tags=["reset password"]) -def reset_password_handler(data: schemas.ForgetPasswordPayloadSchema = Body(...)): +async def reset_password_handler(data: schemas.ForgetPasswordPayloadSchema = Body(...)): if len(data.email) < 5: return {"errors": ["please provide a valid email address"]} - return reset_password.reset(data) + return reset_password.reset(data=data) @app.get('/{projectId}/metadata', tags=["metadata"]) -def get_metadata(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_metadata(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": metadata.get(project_id=projectId)} @app.post('/{projectId}/metadata/list', tags=["metadata"]) -def add_edit_delete_metadata(projectId: int, data: schemas.MetadataListSchema = Body(...), +async def add_edit_delete_metadata(projectId: int, data: schemas.MetadataListSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return metadata.add_edit_delete(tenant_id=context.tenant_id, project_id=projectId, new_metas=data.list) @app.post('/{projectId}/metadata', tags=["metadata"]) -def add_metadata(projectId: int, data: schemas.MetadataBasicSchema = Body(...), +async def add_metadata(projectId: int, data: schemas.MetadataBasicSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return metadata.add(tenant_id=context.tenant_id, project_id=projectId, new_name=data.key) @app.post('/{projectId}/metadata/{index}', tags=["metadata"]) -def edit_metadata(projectId: int, index: int, data: schemas.MetadataBasicSchema = Body(...), +async def edit_metadata(projectId: int, index: int, data: schemas.MetadataBasicSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return metadata.edit(tenant_id=context.tenant_id, project_id=projectId, index=index, new_name=data.key) @app.delete('/{projectId}/metadata/{index}', tags=["metadata"]) -def delete_metadata(projectId: int, index: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_metadata(projectId: int, index: int, context: schemas.CurrentContext = Depends(OR_context)): return metadata.delete(tenant_id=context.tenant_id, project_id=projectId, index=index) @app.get('/{projectId}/metadata/search', tags=["metadata"]) -def search_metadata(projectId: int, q: str, key: str, context: schemas.CurrentContext = Depends(OR_context)): +async def search_metadata(projectId: int, q: str, key: str, context: schemas.CurrentContext = Depends(OR_context)): if len(q) == 0 and len(key) == 0: return {"data": []} if len(q) == 0: @@ -529,203 +549,72 @@ def search_metadata(projectId: int, q: str, key: str, context: schemas.CurrentCo @app.get('/{projectId}/integration/sources', tags=["integrations"]) -def search_integrations(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def search_integrations(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return log_tools.search(project_id=projectId) @app.get('/{projectId}/sample_rate', tags=["projects"]) -def get_capture_status(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_capture_status(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": projects.get_capture_status(project_id=projectId)} @app.post('/{projectId}/sample_rate', tags=["projects"]) -def update_capture_status(projectId: int, data: schemas.SampleRateSchema = Body(...), +async def update_capture_status(projectId: int, data: schemas.SampleRateSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": projects.update_capture_status(project_id=projectId, changes=data.dict())} @app.get('/announcements', tags=["announcements"]) -def get_all_announcements(context: schemas.CurrentContext = Depends(OR_context)): - return {"data": announcements.get_all(context.user_id)} +async def get_all_announcements(context: schemas.CurrentContext = Depends(OR_context)): + return {"data": announcements.get_all(user_id=context.user_id)} @app.get('/announcements/view', tags=["announcements"]) -def get_all_announcements(context: schemas.CurrentContext = Depends(OR_context)): +async def get_all_announcements(context: schemas.CurrentContext = Depends(OR_context)): return {"data": announcements.view(user_id=context.user_id)} @app.get('/show_banner', tags=["banner"]) -def errors_merge(context: schemas.CurrentContext = Depends(OR_context)): +async def errors_merge(context: schemas.CurrentContext = Depends(OR_context)): return {"data": False} @app.post('/{projectId}/alerts', tags=["alerts"]) -def create_alert(projectId: int, data: schemas.AlertSchema = Body(...), +async def create_alert(projectId: int, data: schemas.AlertSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): - return alerts.create(projectId, data) + return alerts.create(project_id=projectId, data=data) @app.get('/{projectId}/alerts', tags=["alerts"]) -def get_all_alerts(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): - return {"data": alerts.get_all(projectId)} +async def get_all_alerts(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): + return {"data": alerts.get_all(project_id=projectId)} @app.get('/{projectId}/alerts/triggers', tags=["alerts", "customMetrics"]) -def get_alerts_triggers(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_alerts_triggers(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": alerts.get_predefined_values() \ + custom_metrics.get_series_for_alert(project_id=projectId, user_id=context.user_id)} @app.get('/{projectId}/alerts/{alertId}', tags=["alerts"]) -def get_alert(projectId: int, alertId: int, context: schemas.CurrentContext = Depends(OR_context)): - return {"data": alerts.get(alertId)} +async def get_alert(projectId: int, alertId: int, context: schemas.CurrentContext = Depends(OR_context)): + return {"data": alerts.get(id=alertId)} @app.post('/{projectId}/alerts/{alertId}', tags=["alerts"]) -def update_alert(projectId: int, alertId: int, data: schemas.AlertSchema = Body(...), +async def update_alert(projectId: int, alertId: int, data: schemas.AlertSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): - return alerts.update(alertId, data) + return alerts.update(id=alertId, data=data) @app.delete('/{projectId}/alerts/{alertId}', tags=["alerts"]) -def delete_alert(projectId: int, alertId: int, context: schemas.CurrentContext = Depends(OR_context)): - return alerts.delete(projectId, alertId) - - -@app.post('/{projectId}/funnels', tags=["funnels"]) -def add_funnel(projectId: int, data: schemas.FunnelSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): - return funnels.create(project_id=projectId, - user_id=context.user_id, - name=data.name, - filter=data.filter, - is_public=data.is_public) - - -@app.get('/{projectId}/funnels', tags=["funnels"]) -def get_funnels(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): - return {"data": funnels.get_by_user(project_id=projectId, - user_id=context.user_id, - range_value=None, - start_date=None, - end_date=None, - details=False)} - - -@app.get('/{projectId}/funnels/details', tags=["funnels"]) -def get_funnels_with_details(projectId: int, rangeValue: str = None, startDate: int = None, endDate: int = None, - context: schemas.CurrentContext = Depends(OR_context)): - return {"data": funnels.get_by_user(project_id=projectId, - user_id=context.user_id, - range_value=rangeValue, - start_date=startDate, - end_date=endDate, - details=True)} - - -@app.get('/{projectId}/funnels/issue_types', tags=["funnels"]) -def get_possible_issue_types(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): - return {"data": funnels.get_possible_issue_types(project_id=projectId)} - - -@app.get('/{projectId}/funnels/{funnelId}/insights', tags=["funnels"]) -def get_funnel_insights(projectId: int, funnelId: int, rangeValue: str = None, startDate: int = None, - endDate: int = None, context: schemas.CurrentContext = Depends(OR_context)): - return funnels.get_top_insights(funnel_id=funnelId, user_id=context.user_id, project_id=projectId, - range_value=rangeValue, start_date=startDate, end_date=endDate) - - -@app.post('/{projectId}/funnels/{funnelId}/insights', tags=["funnels"]) -def get_funnel_insights_on_the_fly(projectId: int, funnelId: int, data: schemas.FunnelInsightsPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): - return funnels.get_top_insights_on_the_fly(funnel_id=funnelId, user_id=context.user_id, project_id=projectId, - data=data) - - -@app.get('/{projectId}/funnels/{funnelId}/issues', tags=["funnels"]) -def get_funnel_issues(projectId: int, funnelId, rangeValue: str = None, startDate: int = None, endDate: int = None, - context: schemas.CurrentContext = Depends(OR_context)): - return funnels.get_issues(funnel_id=funnelId, user_id=context.user_id, project_id=projectId, - range_value=rangeValue, start_date=startDate, end_date=endDate) - - -@app.post('/{projectId}/funnels/{funnelId}/issues', tags=["funnels"]) -def get_funnel_issues_on_the_fly(projectId: int, funnelId: int, data: schemas.FunnelSearchPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): - return {"data": funnels.get_issues_on_the_fly(funnel_id=funnelId, user_id=context.user_id, project_id=projectId, - data=data)} - - -@app.get('/{projectId}/funnels/{funnelId}/sessions', tags=["funnels"]) -def get_funnel_sessions(projectId: int, funnelId: int, rangeValue: str = None, startDate: int = None, - endDate: int = None, context: schemas.CurrentContext = Depends(OR_context)): - return {"data": funnels.get_sessions(funnel_id=funnelId, user_id=context.user_id, project_id=projectId, - range_value=rangeValue, - start_date=startDate, - end_date=endDate)} - - -@app.post('/{projectId}/funnels/{funnelId}/sessions', tags=["funnels"]) -def get_funnel_sessions_on_the_fly(projectId: int, funnelId: int, data: schemas.FunnelSearchPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): - return {"data": funnels.get_sessions_on_the_fly(funnel_id=funnelId, user_id=context.user_id, project_id=projectId, - data=data)} - - -@app.get('/{projectId}/funnels/issues/{issueId}/sessions', tags=["funnels"]) -def get_funnel_issue_sessions(projectId: int, issueId: str, startDate: int = None, endDate: int = None, - context: schemas.CurrentContext = Depends(OR_context)): - issue = issues.get(project_id=projectId, issue_id=issueId) - if issue is None: - return {"errors": ["issue not found"]} - return { - "data": {"sessions": sessions.search_by_issue(user_id=context.user_id, project_id=projectId, issue=issue, - start_date=startDate, end_date=endDate), - "issue": issue}} - - -@app.post('/{projectId}/funnels/{funnelId}/issues/{issueId}/sessions', tags=["funnels"]) -def get_funnel_issue_sessions(projectId: int, funnelId: int, issueId: str, - data: schemas.FunnelSearchPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): - data = funnels.search_by_issue(project_id=projectId, user_id=context.user_id, issue_id=issueId, - funnel_id=funnelId, data=data) - if "errors" in data: - return data - if data.get("issue") is None: - data["issue"] = issues.get(project_id=projectId, issue_id=issueId) - return { - "data": data - } - - -@app.get('/{projectId}/funnels/{funnelId}', tags=["funnels"]) -def get_funnel(projectId: int, funnelId: int, context: schemas.CurrentContext = Depends(OR_context)): - data = funnels.get(funnel_id=funnelId, project_id=projectId, user_id=context.user_id) - if data is None: - return {"errors": ["funnel not found"]} - return {"data": data} - - -@app.post('/{projectId}/funnels/{funnelId}', tags=["funnels"]) -def edit_funnel(projectId: int, funnelId: int, data: schemas.UpdateFunnelSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): - return funnels.update(funnel_id=funnelId, - user_id=context.user_id, - name=data.name, - filter=data.filter.dict(), - is_public=data.is_public, - project_id=projectId) - - -@app.delete('/{projectId}/funnels/{funnelId}', tags=["funnels"]) -def delete_filter(projectId: int, funnelId: int, context: schemas.CurrentContext = Depends(OR_context)): - return funnels.delete(user_id=context.user_id, funnel_id=funnelId, project_id=projectId) +async def delete_alert(projectId: int, alertId: int, context: schemas.CurrentContext = Depends(OR_context)): + return alerts.delete(project_id=projectId, alert_id=alertId) @app_apikey.put('/{projectKey}/sourcemaps/', tags=["sourcemaps"]) @app_apikey.put('/{projectKey}/sourcemaps', tags=["sourcemaps"]) -def sign_sourcemap_for_upload(projectKey: str, data: schemas.SourcemapUploadPayloadSchema = Body(...), +async def sign_sourcemap_for_upload(projectKey: str, data: schemas.SourcemapUploadPayloadSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): project_id = projects.get_internal_project_id(projectKey) if project_id is None: @@ -735,70 +624,79 @@ def sign_sourcemap_for_upload(projectKey: str, data: schemas.SourcemapUploadPayl @app.get('/config/weekly_report', tags=["weekly report config"]) -def get_weekly_report_config(context: schemas.CurrentContext = Depends(OR_context)): +async def get_weekly_report_config(context: schemas.CurrentContext = Depends(OR_context)): return {"data": weekly_report.get_config(user_id=context.user_id)} @app.post('/config/weekly_report', tags=["weekly report config"]) -def edit_weekly_report_config(data: schemas.WeeklyReportConfigSchema = Body(...), +async def edit_weekly_report_config(data: schemas.WeeklyReportConfigSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": weekly_report.edit_config(user_id=context.user_id, weekly_report=data.weekly_report)} @app.get('/{projectId}/issue_types', tags=["issues"]) -def issue_types(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def issue_types(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": issues.get_all_types()} @app.get('/issue_types', tags=["issues"]) -def all_issue_types(context: schemas.CurrentContext = Depends(OR_context)): +async def all_issue_types(context: schemas.CurrentContext = Depends(OR_context)): return {"data": issues.get_all_types()} @app.get('/{projectId}/assist/sessions', tags=["assist"]) -def get_sessions_live(projectId: int, userId: str = None, context: schemas.CurrentContext = Depends(OR_context)): +async def get_sessions_live(projectId: int, userId: str = None, context: schemas.CurrentContext = Depends(OR_context)): data = assist.get_live_sessions_ws_user_id(projectId, user_id=userId) return {'data': data} @app.post('/{projectId}/assist/sessions', tags=["assist"]) -def sessions_live(projectId: int, data: schemas.LiveSessionsSearchPayloadSchema = Body(...), +async def sessions_live(projectId: int, data: schemas.LiveSessionsSearchPayloadSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): data = assist.get_live_sessions_ws(projectId, body=data) return {'data': data} @app.post('/{projectId}/mobile/{sessionId}/urls', tags=['mobile']) -def mobile_signe(projectId: int, sessionId: int, data: schemas.MobileSignPayloadSchema = Body(...), +async def mobile_signe(projectId: int, sessionId: int, data: schemas.MobileSignPayloadSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": mobile.sign_keys(project_id=projectId, session_id=sessionId, keys=data.keys)} @public_app.post('/signup', tags=['signup']) @public_app.put('/signup', tags=['signup']) -def signup_handler(data: schemas.UserSignupSchema = Body(...)): +async def signup_handler(data: schemas.UserSignupSchema = Body(...)): return signup.create_step1(data) @app.post('/projects', tags=['projects']) -def create_project(data: schemas.CreateProjectSchema = Body(...), +async def create_project(data: schemas.CreateProjectSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return projects.create(tenant_id=context.tenant_id, user_id=context.user_id, data=data) +@app.get('/projects/{projectId}', tags=['projects']) +async def get_project(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): + data = projects.get_project(tenant_id=context.tenant_id, project_id=projectId, include_last_session=True, + include_gdpr=True) + if data is None: + return {"errors": ["project not found"]} + return {"data": data} + + @app.put('/projects/{projectId}', tags=['projects']) -def edit_project(projectId: int, data: schemas.CreateProjectSchema = Body(...), +async def edit_project(projectId: int, data: schemas.CreateProjectSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return projects.edit(tenant_id=context.tenant_id, user_id=context.user_id, data=data, project_id=projectId) @app.delete('/projects/{projectId}', tags=['projects']) -def delete_project(projectId, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_project(projectId, context: schemas.CurrentContext = Depends(OR_context)): return projects.delete(tenant_id=context.tenant_id, user_id=context.user_id, project_id=projectId) @app.get('/client/new_api_key', tags=['client']) -def generate_new_tenant_token(context: schemas.CurrentContext = Depends(OR_context)): +async def generate_new_tenant_token(context: schemas.CurrentContext = Depends(OR_context)): return { 'data': tenants.generate_new_api_key(context.tenant_id) } @@ -806,28 +704,28 @@ def generate_new_tenant_token(context: schemas.CurrentContext = Depends(OR_conte @app.post('/client', tags=['client']) @app.put('/client', tags=['client']) -def edit_client(data: schemas.UpdateTenantSchema = Body(...), +async def edit_client(data: schemas.UpdateTenantSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return tenants.update(tenant_id=context.tenant_id, user_id=context.user_id, data=data) @app.get('/notifications', tags=['notifications']) -def get_notifications(context: schemas.CurrentContext = Depends(OR_context)): +async def get_notifications(context: schemas.CurrentContext = Depends(OR_context)): return {"data": notifications.get_all(tenant_id=context.tenant_id, user_id=context.user_id)} @app.get('/notifications/count', tags=['notifications']) -def get_notifications_count(context: schemas.CurrentContext = Depends(OR_context)): +async def get_notifications_count(context: schemas.CurrentContext = Depends(OR_context)): return {"data": notifications.get_all_count(tenant_id=context.tenant_id, user_id=context.user_id)} @app.get('/notifications/{notificationId}/view', tags=['notifications']) -def view_notifications(notificationId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def view_notifications(notificationId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": notifications.view_notification(notification_ids=[notificationId], user_id=context.user_id)} @app.post('/notifications/view', tags=['notifications']) -def batch_view_notifications(data: schemas.NotificationsViewSchema, +async def batch_view_notifications(data: schemas.NotificationsViewSchema, context: schemas.CurrentContext = Depends(OR_context)): return {"data": notifications.view_notification(notification_ids=data.ids, startTimestamp=data.startTimestamp, @@ -837,83 +735,83 @@ def batch_view_notifications(data: schemas.NotificationsViewSchema, @app.get('/boarding', tags=['boarding']) -def get_boarding_state(context: schemas.CurrentContext = Depends(OR_context)): +async def get_boarding_state(context: schemas.CurrentContext = Depends(OR_context)): return {"data": boarding.get_state(tenant_id=context.tenant_id)} @app.get('/boarding/installing', tags=['boarding']) -def get_boarding_state_installing(context: schemas.CurrentContext = Depends(OR_context)): +async def get_boarding_state_installing(context: schemas.CurrentContext = Depends(OR_context)): return {"data": boarding.get_state_installing(tenant_id=context.tenant_id)} @app.get('/boarding/identify-users', tags=["boarding"]) -def get_boarding_state_identify_users(context: schemas.CurrentContext = Depends(OR_context)): +async def get_boarding_state_identify_users(context: schemas.CurrentContext = Depends(OR_context)): return {"data": boarding.get_state_identify_users(tenant_id=context.tenant_id)} @app.get('/boarding/manage-users', tags=["boarding"]) -def get_boarding_state_manage_users(context: schemas.CurrentContext = Depends(OR_context)): +async def get_boarding_state_manage_users(context: schemas.CurrentContext = Depends(OR_context)): return {"data": boarding.get_state_manage_users(tenant_id=context.tenant_id)} @app.get('/boarding/integrations', tags=["boarding"]) -def get_boarding_state_integrations(context: schemas.CurrentContext = Depends(OR_context)): +async def get_boarding_state_integrations(context: schemas.CurrentContext = Depends(OR_context)): return {"data": boarding.get_state_integrations(tenant_id=context.tenant_id)} @app.get('/integrations/slack/channels', tags=["integrations"]) -def get_slack_channels(context: schemas.CurrentContext = Depends(OR_context)): - return {"data": webhook.get_by_type(tenant_id=context.tenant_id, webhook_type='slack')} +async def get_slack_channels(context: schemas.CurrentContext = Depends(OR_context)): + return {"data": webhook.get_by_type(tenant_id=context.tenant_id, webhook_type=schemas.WebhookType.slack)} -@app.get('/integrations/slack/{integrationId}', tags=["integrations"]) -def get_slack_webhook(integrationId: int, context: schemas.CurrentContext = Depends(OR_context)): - return {"data": webhook.get(tenant_id=context.tenant_id, webhook_id=integrationId)} +@app.get('/integrations/slack/{webhookId}', tags=["integrations"]) +async def get_slack_webhook(webhookId: int, context: schemas.CurrentContext = Depends(OR_context)): + return {"data": Slack.get_integration(tenant_id=context.tenant_id, integration_id=webhookId)} -@app.delete('/integrations/slack/{integrationId}', tags=["integrations"]) -def delete_slack_integration(integrationId: int, context: schemas.CurrentContext = Depends(OR_context)): - return webhook.delete(context.tenant_id, integrationId) +@app.delete('/integrations/slack/{webhookId}', tags=["integrations"]) +async def delete_slack_integration(webhookId: int, context: schemas.CurrentContext = Depends(OR_context)): + return webhook.delete(tenant_id=context.tenant_id, webhook_id=webhookId) @app.put('/webhooks', tags=["webhooks"]) -def add_edit_webhook(data: schemas.CreateEditWebhookSchema = Body(...), +async def add_edit_webhook(data: schemas.CreateEditWebhookSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": webhook.add_edit(tenant_id=context.tenant_id, data=data.dict(), replace_none=True)} @app.get('/webhooks', tags=["webhooks"]) -def get_webhooks(context: schemas.CurrentContext = Depends(OR_context)): +async def get_webhooks(context: schemas.CurrentContext = Depends(OR_context)): return {"data": webhook.get_by_tenant(tenant_id=context.tenant_id, replace_none=True)} @app.delete('/webhooks/{webhookId}', tags=["webhooks"]) -def delete_webhook(webhookId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_webhook(webhookId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": webhook.delete(tenant_id=context.tenant_id, webhook_id=webhookId)} @app.get('/client/members', tags=["client"]) -def get_members(context: schemas.CurrentContext = Depends(OR_context)): +async def get_members(context: schemas.CurrentContext = Depends(OR_context)): return {"data": users.get_members(tenant_id=context.tenant_id)} @app.get('/client/members/{memberId}/reset', tags=["client"]) -def reset_reinvite_member(memberId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def reset_reinvite_member(memberId: int, context: schemas.CurrentContext = Depends(OR_context)): return users.reset_member(tenant_id=context.tenant_id, editor_id=context.user_id, user_id_to_update=memberId) @app.delete('/client/members/{memberId}', tags=["client"]) -def delete_member(memberId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_member(memberId: int, context: schemas.CurrentContext = Depends(OR_context)): return users.delete_member(tenant_id=context.tenant_id, user_id=context.user_id, id_to_delete=memberId) @app.get('/account/new_api_key', tags=["account"]) -def generate_new_user_token(context: schemas.CurrentContext = Depends(OR_context)): +async def generate_new_user_token(context: schemas.CurrentContext = Depends(OR_context)): return {"data": users.generate_new_api_key(user_id=context.user_id)} @app.post('/account/password', tags=["account"]) -def change_client_password(data: schemas.EditUserPasswordSchema = Body(...), +async def change_client_password(data: schemas.EditUserPasswordSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return users.change_password(email=context.email, old_password=data.old_password, new_password=data.new_password, tenant_id=context.tenant_id, @@ -921,34 +819,34 @@ def change_client_password(data: schemas.EditUserPasswordSchema = Body(...), @app.post('/{projectId}/saved_search', tags=["savedSearch"]) -def add_saved_search(projectId: int, data: schemas.SavedSearchSchema = Body(...), +async def add_saved_search(projectId: int, data: schemas.SavedSearchSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return saved_search.create(project_id=projectId, user_id=context.user_id, data=data) @app.get('/{projectId}/saved_search', tags=["savedSearch"]) -def get_saved_searches(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_saved_searches(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": saved_search.get_all(project_id=projectId, user_id=context.user_id, details=True)} @app.get('/{projectId}/saved_search/{search_id}', tags=["savedSearch"]) -def get_saved_search(projectId: int, search_id: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_saved_search(projectId: int, search_id: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": saved_search.get(project_id=projectId, search_id=search_id, user_id=context.user_id)} @app.post('/{projectId}/saved_search/{search_id}', tags=["savedSearch"]) -def update_saved_search(projectId: int, search_id: int, data: schemas.SavedSearchSchema = Body(...), +async def update_saved_search(projectId: int, search_id: int, data: schemas.SavedSearchSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": saved_search.update(user_id=context.user_id, search_id=search_id, data=data, project_id=projectId)} @app.delete('/{projectId}/saved_search/{search_id}', tags=["savedSearch"]) -def delete_saved_search(projectId: int, search_id: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_saved_search(projectId: int, search_id: int, context: schemas.CurrentContext = Depends(OR_context)): return {"data": saved_search.delete(project_id=projectId, user_id=context.user_id, search_id=search_id)} @app.get('/limits', tags=['accounts']) -def get_limits(context: schemas.CurrentContext = Depends(OR_context)): +async def get_limits(context: schemas.CurrentContext = Depends(OR_context)): return { 'data': { "teamMember": -1, @@ -957,10 +855,54 @@ def get_limits(context: schemas.CurrentContext = Depends(OR_context)): } +@app.get('/integrations/msteams/channels', tags=["integrations"]) +async def get_msteams_channels(context: schemas.CurrentContext = Depends(OR_context)): + return {"data": webhook.get_by_type(tenant_id=context.tenant_id, webhook_type=schemas.WebhookType.msteams)} + + +@app.post('/integrations/msteams', tags=['integrations']) +async def add_msteams_integration(data: schemas.AddCollaborationSchema, + context: schemas.CurrentContext = Depends(OR_context)): + n = MSTeams.add(tenant_id=context.tenant_id, data=data) + if n is None: + return { + "errors": [ + "We couldn't send you a test message on your Microsoft Teams channel. Please verify your webhook url."] + } + return {"data": n} + + +@app.post('/integrations/msteams/{webhookId}', tags=['integrations']) +async def edit_msteams_integration(webhookId: int, data: schemas.EditCollaborationSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + if len(data.url) > 0: + old = MSTeams.get_integration(tenant_id=context.tenant_id, integration_id=webhookId) + if not old: + return {"errors": ["MsTeams integration not found."]} + if old["endpoint"] != data.url: + if not MSTeams.say_hello(data.url): + return { + "errors": [ + "We couldn't send you a test message on your Microsoft Teams channel. Please verify your webhook url."] + } + return {"data": webhook.update(tenant_id=context.tenant_id, webhook_id=webhookId, + changes={"name": data.name, "endpoint": data.url})} + + +@app.delete('/integrations/msteams/{webhookId}', tags=["integrations"]) +async def delete_msteams_integration(webhookId: int, context: schemas.CurrentContext = Depends(OR_context)): + return webhook.delete(tenant_id=context.tenant_id, webhook_id=webhookId) + + +@public_app.get('/general_stats', tags=["private"], include_in_schema=False) +async def get_general_stats(): + return {"data": {"sessions:": sessions.count_all()}} + + @public_app.get('/', tags=["health"]) @public_app.post('/', tags=["health"]) @public_app.put('/', tags=["health"]) @public_app.delete('/', tags=["health"]) -def health_check(): +async def health_check(): return {"data": {"stage": f"live {config('version_number', default='')}", "internalCrons": config("LOCAL_CRONS", default=False, cast=bool)}} diff --git a/api/routers/core_dynamic.py b/api/routers/core_dynamic.py index c9c9c40c8..3389074bf 100644 --- a/api/routers/core_dynamic.py +++ b/api/routers/core_dynamic.py @@ -6,7 +6,7 @@ from starlette.responses import RedirectResponse, FileResponse import schemas from chalicelib.core import sessions, errors, errors_viewed, errors_favorite, sessions_assignments, heatmaps, \ - sessions_favorite, assist, sessions_notes + sessions_favorite, assist, sessions_notes, click_maps from chalicelib.core import sessions_viewed from chalicelib.core import tenants, users, projects, license from chalicelib.core import webhook @@ -20,7 +20,7 @@ public_app, app, app_apikey = get_routers() @public_app.get('/signup', tags=['signup']) -def get_all_signup(): +async def get_all_signup(): return {"data": {"tenants": tenants.tenants_exists(), "sso": None, "ssoProvider": None, @@ -28,7 +28,7 @@ def get_all_signup(): @app.get('/account', tags=['accounts']) -def get_account(context: schemas.CurrentContext = Depends(OR_context)): +async def get_account(context: schemas.CurrentContext = Depends(OR_context)): r = users.get(tenant_id=context.tenant_id, user_id=context.user_id) t = tenants.get_by_tenant_id(context.tenant_id) if t is not None: @@ -46,33 +46,17 @@ def get_account(context: schemas.CurrentContext = Depends(OR_context)): @app.post('/account', tags=["account"]) -def edit_account(data: schemas.EditUserSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def edit_account(data: schemas.EditUserSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return users.edit(tenant_id=context.tenant_id, user_id_to_update=context.user_id, changes=data, editor_id=context.user_id) -@app.get('/projects/limit', tags=['projects']) -def get_projects_limit(context: schemas.CurrentContext = Depends(OR_context)): - return {"data": { - "current": projects.count_by_tenant(tenant_id=context.tenant_id), - "remaining": -1 - }} - - -@app.get('/projects/{projectId}', tags=['projects']) -def get_project(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): - data = projects.get_project(tenant_id=context.tenant_id, project_id=projectId, include_last_session=True, - include_gdpr=True) - if data is None: - return {"errors": ["project not found"]} - return {"data": data} - - @app.post('/integrations/slack', tags=['integrations']) @app.put('/integrations/slack', tags=['integrations']) -def add_slack_client(data: schemas.AddSlackSchema, context: schemas.CurrentContext = Depends(OR_context)): - n = Slack.add_channel(tenant_id=context.tenant_id, url=data.url, name=data.name) +async def add_slack_integration(data: schemas.AddCollaborationSchema, + context: schemas.CurrentContext = Depends(OR_context)): + n = Slack.add(tenant_id=context.tenant_id, data=data) if n is None: return { "errors": ["We couldn't send you a test message on your Slack channel. Please verify your webhook url."] @@ -81,10 +65,12 @@ def add_slack_client(data: schemas.AddSlackSchema, context: schemas.CurrentConte @app.post('/integrations/slack/{integrationId}', tags=['integrations']) -def edit_slack_integration(integrationId: int, data: schemas.EditSlackSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def edit_slack_integration(integrationId: int, data: schemas.EditCollaborationSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): if len(data.url) > 0: - old = webhook.get(tenant_id=context.tenant_id, webhook_id=integrationId) + old = Slack.get_integration(tenant_id=context.tenant_id, integration_id=integrationId) + if not old: + return {"errors": ["Slack integration not found."]} if old["endpoint"] != data.url: if not Slack.say_hello(data.url): return { @@ -96,14 +82,14 @@ def edit_slack_integration(integrationId: int, data: schemas.EditSlackSchema = B @app.post('/client/members', tags=["client"]) -def add_member(background_tasks: BackgroundTasks, data: schemas.CreateMemberSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def add_member(background_tasks: BackgroundTasks, data: schemas.CreateMemberSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return users.create_member(tenant_id=context.tenant_id, user_id=context.user_id, data=data.dict(), background_tasks=background_tasks) @public_app.get('/users/invitation', tags=['users']) -def process_invitation_link(token: str): +async def process_invitation_link(token: str): if token is None or len(token) < 64: return {"errors": ["please provide a valid invitation"]} user = users.get_by_invitation_token(token) @@ -120,7 +106,7 @@ def process_invitation_link(token: str): @public_app.post('/password/reset', tags=["users"]) -def change_password_by_invitation(data: schemas.EditPasswordByInvitationSchema = Body(...)): +async def change_password_by_invitation(data: schemas.EditPasswordByInvitationSchema = Body(...)): if data is None or len(data.invitation) < 64 or len(data.passphrase) < 8: return {"errors": ["please provide a valid invitation & pass"]} user = users.get_by_invitation_token(token=data.invitation, pass_token=data.passphrase) @@ -133,15 +119,15 @@ def change_password_by_invitation(data: schemas.EditPasswordByInvitationSchema = @app.put('/client/members/{memberId}', tags=["client"]) -def edit_member(memberId: int, data: schemas.EditMemberSchema, - context: schemas.CurrentContext = Depends(OR_context)): +async def edit_member(memberId: int, data: schemas.EditMemberSchema, + context: schemas.CurrentContext = Depends(OR_context)): return users.edit_member(tenant_id=context.tenant_id, editor_id=context.user_id, changes=data, user_id_to_update=memberId) @app.get('/metadata/session_search', tags=["metadata"]) -def search_sessions_by_metadata(key: str, value: str, projectId: Optional[int] = None, - context: schemas.CurrentContext = Depends(OR_context)): +async def search_sessions_by_metadata(key: str, value: str, projectId: Optional[int] = None, + context: schemas.CurrentContext = Depends(OR_context)): if key is None or value is None or len(value) == 0 and len(key) == 0: return {"errors": ["please provide a key&value for search"]} if len(value) == 0: @@ -153,20 +139,15 @@ def search_sessions_by_metadata(key: str, value: str, projectId: Optional[int] = m_key=key, project_id=projectId)} -@public_app.get('/general_stats', tags=["private"], include_in_schema=False) -def get_general_stats(): - return {"data": {"sessions:": sessions.count_all()}} - - @app.get('/projects', tags=['projects']) -def get_projects(context: schemas.CurrentContext = Depends(OR_context)): +async def get_projects(context: schemas.CurrentContext = Depends(OR_context)): return {"data": projects.get_projects(tenant_id=context.tenant_id, recording_state=True, gdpr=True, recorded=True, stack_integrations=True)} @app.get('/{projectId}/sessions/{sessionId}', tags=["sessions"]) -def get_session(projectId: int, sessionId: Union[int, str], background_tasks: BackgroundTasks, - context: schemas.CurrentContext = Depends(OR_context)): +async def get_session(projectId: int, sessionId: Union[int, str], background_tasks: BackgroundTasks, + context: schemas.CurrentContext = Depends(OR_context)): if isinstance(sessionId, str): return {"errors": ["session not found"]} data = sessions.get_by_id2_pg(project_id=projectId, session_id=sessionId, full_data=True, @@ -182,8 +163,8 @@ def get_session(projectId: int, sessionId: Union[int, str], background_tasks: Ba @app.get('/{projectId}/sessions/{sessionId}/errors/{errorId}/sourcemaps', tags=["sessions", "sourcemaps"]) -def get_error_trace(projectId: int, sessionId: int, errorId: str, - context: schemas.CurrentContext = Depends(OR_context)): +async def get_error_trace(projectId: int, sessionId: int, errorId: str, + context: schemas.CurrentContext = Depends(OR_context)): data = errors.get_trace(project_id=projectId, error_id=errorId) if "errors" in data: return data @@ -193,20 +174,20 @@ def get_error_trace(projectId: int, sessionId: int, errorId: str, @app.post('/{projectId}/errors/search', tags=['errors']) -def errors_search(projectId: int, data: schemas.SearchErrorsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def errors_search(projectId: int, data: schemas.SearchErrorsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return {"data": errors.search(data, projectId, user_id=context.user_id)} @app.get('/{projectId}/errors/stats', tags=['errors']) -def errors_stats(projectId: int, startTimestamp: int, endTimestamp: int, - context: schemas.CurrentContext = Depends(OR_context)): +async def errors_stats(projectId: int, startTimestamp: int, endTimestamp: int, + context: schemas.CurrentContext = Depends(OR_context)): return errors.stats(projectId, user_id=context.user_id, startTimestamp=startTimestamp, endTimestamp=endTimestamp) @app.get('/{projectId}/errors/{errorId}', tags=['errors']) -def errors_get_details(projectId: int, errorId: str, background_tasks: BackgroundTasks, density24: int = 24, - density30: int = 30, context: schemas.CurrentContext = Depends(OR_context)): +async def errors_get_details(projectId: int, errorId: str, background_tasks: BackgroundTasks, density24: int = 24, + density30: int = 30, context: schemas.CurrentContext = Depends(OR_context)): data = errors.get_details(project_id=projectId, user_id=context.user_id, error_id=errorId, **{"density24": density24, "density30": density30}) if data.get("data") is not None: @@ -216,17 +197,17 @@ def errors_get_details(projectId: int, errorId: str, background_tasks: Backgroun @app.get('/{projectId}/errors/{errorId}/stats', tags=['errors']) -def errors_get_details_right_column(projectId: int, errorId: str, startDate: int = TimeUTC.now(-7), - endDate: int = TimeUTC.now(), density: int = 7, - context: schemas.CurrentContext = Depends(OR_context)): +async def errors_get_details_right_column(projectId: int, errorId: str, startDate: int = TimeUTC.now(-7), + endDate: int = TimeUTC.now(), density: int = 7, + context: schemas.CurrentContext = Depends(OR_context)): data = errors.get_details_chart(project_id=projectId, user_id=context.user_id, error_id=errorId, **{"startDate": startDate, "endDate": endDate, "density": density}) return data @app.get('/{projectId}/errors/{errorId}/sourcemaps', tags=['errors']) -def errors_get_details_sourcemaps(projectId: int, errorId: str, - context: schemas.CurrentContext = Depends(OR_context)): +async def errors_get_details_sourcemaps(projectId: int, errorId: str, + context: schemas.CurrentContext = Depends(OR_context)): data = errors.get_trace(project_id=projectId, error_id=errorId) if "errors" in data: return data @@ -236,8 +217,9 @@ def errors_get_details_sourcemaps(projectId: int, errorId: str, @app.get('/{projectId}/errors/{errorId}/{action}', tags=["errors"]) -def add_remove_favorite_error(projectId: int, errorId: str, action: str, startDate: int = TimeUTC.now(-7), - endDate: int = TimeUTC.now(), context: schemas.CurrentContext = Depends(OR_context)): +async def add_remove_favorite_error(projectId: int, errorId: str, action: str, startDate: int = TimeUTC.now(-7), + endDate: int = TimeUTC.now(), + context: schemas.CurrentContext = Depends(OR_context)): if action == "favorite": return errors_favorite.favorite_error(project_id=projectId, user_id=context.user_id, error_id=errorId) elif action == "sessions": @@ -253,8 +235,8 @@ def add_remove_favorite_error(projectId: int, errorId: str, action: str, startDa @app.get('/{projectId}/assist/sessions/{sessionId}', tags=["assist"]) -def get_live_session(projectId: int, sessionId: str, background_tasks: BackgroundTasks, - context: schemas.CurrentContext = Depends(OR_context)): +async def get_live_session(projectId: int, sessionId: str, background_tasks: BackgroundTasks, + context: schemas.CurrentContext = Depends(OR_context)): data = assist.get_live_session_by_id(project_id=projectId, session_id=sessionId) if data is None: data = sessions.get_by_id2_pg(context=context, project_id=projectId, session_id=sessionId, @@ -268,8 +250,8 @@ def get_live_session(projectId: int, sessionId: str, background_tasks: Backgroun @app.get('/{projectId}/unprocessed/{sessionId}/dom.mob', tags=["assist"]) -def get_live_session_replay_file(projectId: int, sessionId: Union[int, str], - context: schemas.CurrentContext = Depends(OR_context)): +async def get_live_session_replay_file(projectId: int, sessionId: Union[int, str], + context: schemas.CurrentContext = Depends(OR_context)): not_found = {"errors": ["Replay file not found"]} if isinstance(sessionId, str): print(f"{sessionId} not a valid number.") @@ -288,8 +270,8 @@ def get_live_session_replay_file(projectId: int, sessionId: Union[int, str], @app.get('/{projectId}/unprocessed/{sessionId}/devtools.mob', tags=["assist"]) -def get_live_session_devtools_file(projectId: int, sessionId: Union[int, str], - context: schemas.CurrentContext = Depends(OR_context)): +async def get_live_session_devtools_file(projectId: int, sessionId: Union[int, str], + context: schemas.CurrentContext = Depends(OR_context)): not_found = {"errors": ["Devtools file not found"]} if isinstance(sessionId, str): print(f"{sessionId} not a valid number.") @@ -308,20 +290,20 @@ def get_live_session_devtools_file(projectId: int, sessionId: Union[int, str], @app.post('/{projectId}/heatmaps/url', tags=["heatmaps"]) -def get_heatmaps_by_url(projectId: int, data: schemas.GetHeatmapPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): - return {"data": heatmaps.get_by_url(project_id=projectId, data=data.dict())} +async def get_heatmaps_by_url(projectId: int, data: schemas.GetHeatmapPayloadSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + return {"data": heatmaps.get_by_url(project_id=projectId, data=data)} @app.get('/{projectId}/sessions/{sessionId}/favorite', tags=["sessions"]) -def add_remove_favorite_session2(projectId: int, sessionId: int, - context: schemas.CurrentContext = Depends(OR_context)): +async def add_remove_favorite_session2(projectId: int, sessionId: int, + context: schemas.CurrentContext = Depends(OR_context)): return { "data": sessions_favorite.favorite_session(context=context, project_id=projectId, session_id=sessionId)} @app.get('/{projectId}/sessions/{sessionId}/assign', tags=["sessions"]) -def assign_session(projectId: int, sessionId, context: schemas.CurrentContext = Depends(OR_context)): +async def assign_session(projectId: int, sessionId, context: schemas.CurrentContext = Depends(OR_context)): data = sessions_assignments.get_by_session(project_id=projectId, session_id=sessionId, tenant_id=context.tenant_id, user_id=context.user_id) @@ -333,8 +315,8 @@ def assign_session(projectId: int, sessionId, context: schemas.CurrentContext = @app.get('/{projectId}/sessions/{sessionId}/assign/{issueId}', tags=["sessions", "issueTracking"]) -def assign_session(projectId: int, sessionId: int, issueId: str, - context: schemas.CurrentContext = Depends(OR_context)): +async def assign_session(projectId: int, sessionId: int, issueId: str, + context: schemas.CurrentContext = Depends(OR_context)): data = sessions_assignments.get(project_id=projectId, session_id=sessionId, assignment_id=issueId, tenant_id=context.tenant_id, user_id=context.user_id) if "errors" in data: @@ -345,8 +327,9 @@ def assign_session(projectId: int, sessionId: int, issueId: str, @app.post('/{projectId}/sessions/{sessionId}/assign/{issueId}/comment', tags=["sessions", "issueTracking"]) -def comment_assignment(projectId: int, sessionId: int, issueId: str, data: schemas.CommentAssignmentSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def comment_assignment(projectId: int, sessionId: int, issueId: str, + data: schemas.CommentAssignmentSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): data = sessions_assignments.comment(tenant_id=context.tenant_id, project_id=projectId, session_id=sessionId, assignment_id=issueId, user_id=context.user_id, message=data.message) @@ -358,8 +341,8 @@ def comment_assignment(projectId: int, sessionId: int, issueId: str, data: schem @app.post('/{projectId}/sessions/{sessionId}/notes', tags=["sessions", "notes"]) -def create_note(projectId: int, sessionId: int, data: schemas.SessionNoteSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def create_note(projectId: int, sessionId: int, data: schemas.SessionNoteSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): if not sessions.session_exists(project_id=projectId, session_id=sessionId): return {"errors": ["Session not found"]} data = sessions_notes.create(tenant_id=context.tenant_id, project_id=projectId, @@ -372,7 +355,7 @@ def create_note(projectId: int, sessionId: int, data: schemas.SessionNoteSchema @app.get('/{projectId}/sessions/{sessionId}/notes', tags=["sessions", "notes"]) -def get_session_notes(projectId: int, sessionId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_session_notes(projectId: int, sessionId: int, context: schemas.CurrentContext = Depends(OR_context)): data = sessions_notes.get_session_notes(tenant_id=context.tenant_id, project_id=projectId, session_id=sessionId, user_id=context.user_id) if "errors" in data: @@ -383,8 +366,8 @@ def get_session_notes(projectId: int, sessionId: int, context: schemas.CurrentCo @app.post('/{projectId}/notes/{noteId}', tags=["sessions", "notes"]) -def edit_note(projectId: int, noteId: int, data: schemas.SessionUpdateNoteSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def edit_note(projectId: int, noteId: int, data: schemas.SessionUpdateNoteSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): data = sessions_notes.edit(tenant_id=context.tenant_id, project_id=projectId, user_id=context.user_id, note_id=noteId, data=data) if "errors" in data.keys(): @@ -395,24 +378,37 @@ def edit_note(projectId: int, noteId: int, data: schemas.SessionUpdateNoteSchema @app.delete('/{projectId}/notes/{noteId}', tags=["sessions", "notes"]) -def delete_note(projectId: int, noteId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_note(projectId: int, noteId: int, context: schemas.CurrentContext = Depends(OR_context)): data = sessions_notes.delete(tenant_id=context.tenant_id, project_id=projectId, user_id=context.user_id, note_id=noteId) return data @app.get('/{projectId}/notes/{noteId}/slack/{webhookId}', tags=["sessions", "notes"]) -def share_note_to_slack(projectId: int, noteId: int, webhookId: int, - context: schemas.CurrentContext = Depends(OR_context)): +async def share_note_to_slack(projectId: int, noteId: int, webhookId: int, + context: schemas.CurrentContext = Depends(OR_context)): return sessions_notes.share_to_slack(tenant_id=context.tenant_id, project_id=projectId, user_id=context.user_id, note_id=noteId, webhook_id=webhookId) +@app.get('/{projectId}/notes/{noteId}/msteams/{webhookId}', tags=["sessions", "notes"]) +async def share_note_to_msteams(projectId: int, noteId: int, webhookId: int, + context: schemas.CurrentContext = Depends(OR_context)): + return sessions_notes.share_to_msteams(tenant_id=context.tenant_id, project_id=projectId, user_id=context.user_id, + note_id=noteId, webhook_id=webhookId) + + @app.post('/{projectId}/notes', tags=["sessions", "notes"]) -def get_all_notes(projectId: int, data: schemas.SearchNoteSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def get_all_notes(projectId: int, data: schemas.SearchNoteSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): data = sessions_notes.get_all_notes_by_project_id(tenant_id=context.tenant_id, project_id=projectId, user_id=context.user_id, data=data) if "errors" in data: return data return {'data': data} + + +@app.post('/{projectId}/click_maps/search', tags=["click maps"]) +async def click_map_search(projectId: int, data: schemas.FlatClickMapSessionsSearch = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + return {"data": click_maps.search_short_session(user_id=context.user_id, data=data, project_id=projectId)} diff --git a/api/routers/crons/core_dynamic_crons.py b/api/routers/crons/core_dynamic_crons.py index 3e4df3825..a79ce705d 100644 --- a/api/routers/crons/core_dynamic_crons.py +++ b/api/routers/crons/core_dynamic_crons.py @@ -1,3 +1,6 @@ +from apscheduler.triggers.cron import CronTrigger +from apscheduler.triggers.interval import IntervalTrigger + from chalicelib.core import telemetry from chalicelib.core import weekly_report, jobs @@ -15,7 +18,10 @@ async def telemetry_cron() -> None: cron_jobs = [ - {"func": telemetry_cron, "trigger": "cron", "day_of_week": "*"}, - {"func": run_scheduled_jobs, "trigger": "interval", "seconds": 60, "misfire_grace_time": 20}, - {"func": weekly_report2, "trigger": "cron", "day_of_week": "mon", "hour": 5, "misfire_grace_time": 60 * 60} + {"func": telemetry_cron, "trigger": CronTrigger(day_of_week="*"), + "misfire_grace_time": 60 * 60, "max_instances": 1}, + {"func": run_scheduled_jobs, "trigger": IntervalTrigger(minutes=1), + "misfire_grace_time": 20, "max_instances": 1}, + {"func": weekly_report2, "trigger": CronTrigger(day_of_week="mon", hour=5), + "misfire_grace_time": 60 * 60, "max_instances": 1} ] diff --git a/api/routers/subs/dashboard.py b/api/routers/subs/dashboard.py deleted file mode 100644 index b167c4231..000000000 --- a/api/routers/subs/dashboard.py +++ /dev/null @@ -1,398 +0,0 @@ -from fastapi import Body - -import schemas -from chalicelib.core import metrics -from chalicelib.core import metadata -from chalicelib.utils import helper -from routers.base import get_routers - -public_app, app, app_apikey = get_routers() - - -@app.get('/{projectId}/dashboard/metadata', tags=["dashboard", "metrics"]) -def get_metadata_map(projectId: int): - metamap = [] - for m in metadata.get(project_id=projectId): - metamap.append({"name": m["key"], "key": f"metadata{m['index']}"}) - return {"data": metamap} - - -@app.post('/{projectId}/dashboard/sessions', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/sessions', tags=["dashboard", "metrics"]) -def get_dashboard_processed_sessions(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_processed_sessions(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/errors', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/errors', tags=["dashboard", "metrics"]) -def get_dashboard_errors(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_errors(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/errors_trend', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/errors_trend', tags=["dashboard", "metrics"]) -def get_dashboard_errors_trend(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_errors_trend(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/application_activity', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/application_activity', tags=["dashboard", "metrics"]) -def get_dashboard_application_activity(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_application_activity(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/page_metrics', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/page_metrics', tags=["dashboard", "metrics"]) -def get_dashboard_page_metrics(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_page_metrics(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/user_activity', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/user_activity', tags=["dashboard", "metrics"]) -def get_dashboard_user_activity(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_user_activity(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/performance', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/performance', tags=["dashboard", "metrics"]) -def get_dashboard_performance(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_performance(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/slowest_images', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/slowest_images', tags=["dashboard", "metrics"]) -def get_dashboard_slowest_images(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_slowest_images(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/missing_resources', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/missing_resources', tags=["dashboard", "metrics"]) -def get_performance_sessions(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_missing_resources_trend(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/network', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/network', tags=["dashboard", "metrics"]) -def get_network_widget(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_network(project_id=projectId, **data.dict())} - - -@app.get('/{projectId}/dashboard/{widget}/search', tags=["dashboard", "metrics"]) -def get_dashboard_autocomplete(projectId: int, widget: str, q: str, type: str = "", platform: str = None, - key: str = ""): - if q is None or len(q) == 0: - return {"data": []} - q = '^' + q - - if widget in ['performance']: - data = metrics.search(q, type, project_id=projectId, - platform=platform, performance=True) - elif widget in ['pages', 'pages_dom_buildtime', 'top_metrics', 'time_to_render', - 'impacted_sessions_by_slow_pages', 'pages_response_time']: - data = metrics.search(q, type, project_id=projectId, - platform=platform, pages_only=True) - elif widget in ['resources_loading_time']: - data = metrics.search(q, type, project_id=projectId, - platform=platform, performance=False) - elif widget in ['time_between_events', 'events']: - data = metrics.search(q, type, project_id=projectId, - platform=platform, performance=False, events_only=True) - elif widget in ['metadata']: - data = metrics.search(q, None, project_id=projectId, - platform=platform, metadata=True, key=key) - else: - return {"errors": [f"unsupported widget: {widget}"]} - return {'data': data} - - -# 1 -@app.post('/{projectId}/dashboard/slowest_resources', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/slowest_resources', tags=["dashboard", "metrics"]) -def get_dashboard_slowest_resources(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_slowest_resources(project_id=projectId, **data.dict())} - - -# 2 -@app.post('/{projectId}/dashboard/resources_loading_time', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/resources_loading_time', tags=["dashboard", "metrics"]) -def get_dashboard_resources(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_resources_loading_time(project_id=projectId, **data.dict())} - - -# 3 -@app.post('/{projectId}/dashboard/pages_dom_buildtime', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/pages_dom_buildtime', tags=["dashboard", "metrics"]) -def get_dashboard_pages_dom(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_pages_dom_build_time(project_id=projectId, **data.dict())} - - -# 4 -@app.post('/{projectId}/dashboard/busiest_time_of_day', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/busiest_time_of_day', tags=["dashboard", "metrics"]) -def get_dashboard_busiest_time_of_day(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_busiest_time_of_day(project_id=projectId, **data.dict())} - - -# 5 -@app.post('/{projectId}/dashboard/sessions_location', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/sessions_location', tags=["dashboard", "metrics"]) -def get_dashboard_sessions_location(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_sessions_location(project_id=projectId, **data.dict())} - - -# 6 -@app.post('/{projectId}/dashboard/speed_location', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/speed_location', tags=["dashboard", "metrics"]) -def get_dashboard_speed_location(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_speed_index_location(project_id=projectId, **data.dict())} - - -# 7 -@app.post('/{projectId}/dashboard/pages_response_time', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/pages_response_time', tags=["dashboard", "metrics"]) -def get_dashboard_pages_response_time(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_pages_response_time(project_id=projectId, **data.dict())} - - -# 8 -@app.post('/{projectId}/dashboard/pages_response_time_distribution', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/pages_response_time_distribution', tags=["dashboard", "metrics"]) -def get_dashboard_pages_response_time_distribution(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_pages_response_time_distribution(project_id=projectId, **data.dict())} - - -# 9 -@app.post('/{projectId}/dashboard/top_metrics', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/top_metrics', tags=["dashboard", "metrics"]) -def get_dashboard_top_metrics(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_top_metrics(project_id=projectId, **data.dict())} - - -# 10 -@app.post('/{projectId}/dashboard/time_to_render', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/time_to_render', tags=["dashboard", "metrics"]) -def get_dashboard_time_to_render(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_time_to_render(project_id=projectId, **data.dict())} - - -# 11 -@app.post('/{projectId}/dashboard/impacted_sessions_by_slow_pages', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/impacted_sessions_by_slow_pages', tags=["dashboard", "metrics"]) -def get_dashboard_impacted_sessions_by_slow_pages(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_impacted_sessions_by_slow_pages(project_id=projectId, **data.dict())} - - -# 12 -@app.post('/{projectId}/dashboard/memory_consumption', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/memory_consumption', tags=["dashboard", "metrics"]) -def get_dashboard_memory_consumption(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_memory_consumption(project_id=projectId, **data.dict())} - - -# 12.1 -@app.post('/{projectId}/dashboard/fps', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/fps', tags=["dashboard", "metrics"]) -def get_dashboard_avg_fps(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_avg_fps(project_id=projectId, **data.dict())} - - -# 12.2 -@app.post('/{projectId}/dashboard/cpu', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/cpu', tags=["dashboard", "metrics"]) -def get_dashboard_avg_cpu(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_avg_cpu(project_id=projectId, **data.dict())} - - -# 13 -@app.post('/{projectId}/dashboard/crashes', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/crashes', tags=["dashboard", "metrics"]) -def get_dashboard_impacted_sessions_by_slow_pages(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_crashes(project_id=projectId, **data.dict())} - - -# 14 -@app.post('/{projectId}/dashboard/domains_errors', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/domains_errors', tags=["dashboard", "metrics"]) -def get_dashboard_domains_errors(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_domains_errors(project_id=projectId, **data.dict())} - - -# 14.1 -@app.post('/{projectId}/dashboard/domains_errors_4xx', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/domains_errors_4xx', tags=["dashboard", "metrics"]) -def get_dashboard_domains_errors_4xx(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_domains_errors_4xx(project_id=projectId, **data.dict())} - - -# 14.2 -@app.post('/{projectId}/dashboard/domains_errors_5xx', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/domains_errors_5xx', tags=["dashboard", "metrics"]) -def get_dashboard_domains_errors_5xx(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_domains_errors_5xx(project_id=projectId, **data.dict())} - - -# 15 -@app.post('/{projectId}/dashboard/slowest_domains', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/slowest_domains', tags=["dashboard", "metrics"]) -def get_dashboard_slowest_domains(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_slowest_domains(project_id=projectId, **data.dict())} - - -# 16 -@app.post('/{projectId}/dashboard/errors_per_domains', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/errors_per_domains', tags=["dashboard", "metrics"]) -def get_dashboard_errors_per_domains(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_errors_per_domains(project_id=projectId, **data.dict())} - - -# 17 -@app.post('/{projectId}/dashboard/sessions_per_browser', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/sessions_per_browser', tags=["dashboard", "metrics"]) -def get_dashboard_sessions_per_browser(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_sessions_per_browser(project_id=projectId, **data.dict())} - - -# 18 -@app.post('/{projectId}/dashboard/calls_errors', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/calls_errors', tags=["dashboard", "metrics"]) -def get_dashboard_calls_errors(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_calls_errors(project_id=projectId, **data.dict())} - - -# 18.1 -@app.post('/{projectId}/dashboard/calls_errors_4xx', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/calls_errors_4xx', tags=["dashboard", "metrics"]) -def get_dashboard_calls_errors_4xx(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_calls_errors_4xx(project_id=projectId, **data.dict())} - - -# 18.2 -@app.post('/{projectId}/dashboard/calls_errors_5xx', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/calls_errors_5xx', tags=["dashboard", "metrics"]) -def get_dashboard_calls_errors_5xx(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_calls_errors_5xx(project_id=projectId, **data.dict())} - - -# 19 -@app.post('/{projectId}/dashboard/errors_per_type', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/errors_per_type', tags=["dashboard", "metrics"]) -def get_dashboard_errors_per_type(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_errors_per_type(project_id=projectId, **data.dict())} - - -# 20 -@app.post('/{projectId}/dashboard/resources_by_party', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/resources_by_party', tags=["dashboard", "metrics"]) -def get_dashboard_resources_by_party(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_resources_by_party(project_id=projectId, **data.dict())} - - -# 21 -@app.post('/{projectId}/dashboard/resource_type_vs_response_end', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/resource_type_vs_response_end', tags=["dashboard", "metrics"]) -def get_dashboard_errors_per_resource_type(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.resource_type_vs_response_end(project_id=projectId, **data.dict())} - - -# 22 -@app.post('/{projectId}/dashboard/resources_vs_visually_complete', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/resources_vs_visually_complete', tags=["dashboard", "metrics"]) -def get_dashboard_resources_vs_visually_complete(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_resources_vs_visually_complete(project_id=projectId, **data.dict())} - - -# 23 -@app.post('/{projectId}/dashboard/impacted_sessions_by_js_errors', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/impacted_sessions_by_js_errors', tags=["dashboard", "metrics"]) -def get_dashboard_impacted_sessions_by_js_errors(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_impacted_sessions_by_js_errors(project_id=projectId, **data.dict())} - - -# 24 -@app.post('/{projectId}/dashboard/resources_count_by_type', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/resources_count_by_type', tags=["dashboard", "metrics"]) -def get_dashboard_resources_count_by_type(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_resources_count_by_type(project_id=projectId, **data.dict())} - - -# # 25 -# @app.post('/{projectId}/dashboard/time_between_events', tags=["dashboard", "metrics"]) -# @app.get('/{projectId}/dashboard/time_between_events', tags=["dashboard", "metrics"]) -# def get_dashboard_resources_count_by_type(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): -# return {"errors": ["please choose 2 events"]} - - -@app.post('/{projectId}/dashboard/overview', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/overview', tags=["dashboard", "metrics"]) -def get_dashboard_group(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - results = [ - {"key": "count_sessions", - "data": metrics.get_processed_sessions(project_id=projectId, **data.dict())}, - *helper.explode_widget(data={**metrics.get_application_activity(project_id=projectId, **data.dict()), - "chart": metrics.get_performance(project_id=projectId, **data.dict()) - .get("chart", [])}), - *helper.explode_widget(data=metrics.get_page_metrics(project_id=projectId, **data.dict())), - *helper.explode_widget(data=metrics.get_user_activity(project_id=projectId, **data.dict())), - {"key": "avg_pages_dom_buildtime", - "data": metrics.get_pages_dom_build_time(project_id=projectId, **data.dict())}, - {"key": "avg_pages_response_time", - "data": metrics.get_pages_response_time(project_id=projectId, **data.dict()) - }, - *helper.explode_widget(metrics.get_top_metrics(project_id=projectId, **data.dict())), - {"key": "avg_time_to_render", "data": metrics.get_time_to_render(project_id=projectId, **data.dict())}, - {"key": "avg_used_js_heap_size", "data": metrics.get_memory_consumption(project_id=projectId, **data.dict())}, - {"key": "avg_cpu", "data": metrics.get_avg_cpu(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_fps, - "data": metrics.get_avg_fps(project_id=projectId, **data.dict())} - ] - results = sorted(results, key=lambda r: r["key"]) - return {"data": results} - - -@app.post('/{projectId}/dashboard/overview2', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/overview2', tags=["dashboard", "metrics"]) -def get_dashboard_group(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - results = [ - {"key": schemas.TemplatePredefinedKeys.count_sessions, - "data": metrics.get_processed_sessions(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_image_load_time, - "data": metrics.get_application_activity_avg_image_load_time(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_page_load_time, - "data": metrics.get_application_activity_avg_page_load_time(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_request_load_time, - "data": metrics.get_application_activity_avg_request_load_time(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_dom_content_load_start, - "data": metrics.get_page_metrics_avg_dom_content_load_start(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_first_contentful_pixel, - "data": metrics.get_page_metrics_avg_first_contentful_pixel(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_visited_pages, - "data": metrics.get_user_activity_avg_visited_pages(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_session_duration, - "data": metrics.get_user_activity_avg_session_duration(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_pages_dom_buildtime, - "data": metrics.get_pages_dom_build_time(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_pages_response_time, - "data": metrics.get_pages_response_time(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_response_time, - "data": metrics.get_top_metrics_avg_response_time(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_first_paint, - "data": metrics.get_top_metrics_avg_first_paint(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_dom_content_loaded, - "data": metrics.get_top_metrics_avg_dom_content_loaded(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_till_first_bit, - "data": metrics.get_top_metrics_avg_till_first_bit(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_time_to_interactive, - "data": metrics.get_top_metrics_avg_time_to_interactive(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.count_requests, - "data": metrics.get_top_metrics_count_requests(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_time_to_render, - "data": metrics.get_time_to_render(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_used_js_heap_size, - "data": metrics.get_memory_consumption(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_cpu, - "data": metrics.get_avg_cpu(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_fps, - "data": metrics.get_avg_fps(project_id=projectId, **data.dict())} - ] - results = sorted(results, key=lambda r: r["key"]) - return {"data": results} diff --git a/api/routers/subs/metrics.py b/api/routers/subs/metrics.py index b8332aa85..ac54842da 100644 --- a/api/routers/subs/metrics.py +++ b/api/routers/subs/metrics.py @@ -1,4 +1,6 @@ -from fastapi import Body, Depends +from typing import Union + +from fastapi import Body, Depends, Request import schemas from chalicelib.core import dashboards, custom_metrics, funnels @@ -46,11 +48,12 @@ def pin_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentCont return {"data": dashboards.pin_dashboard(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId)} +@app.post('/{projectId}/dashboards/{dashboardId}/cards', tags=["cards"]) @app.post('/{projectId}/dashboards/{dashboardId}/widgets', tags=["dashboard"]) @app.put('/{projectId}/dashboards/{dashboardId}/widgets', tags=["dashboard"]) -def add_widget_to_dashboard(projectId: int, dashboardId: int, - data: schemas.AddWidgetToDashboardPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def add_card_to_dashboard(projectId: int, dashboardId: int, + data: schemas.AddWidgetToDashboardPayloadSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return {"data": dashboards.add_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, data=data)} @@ -58,7 +61,7 @@ def add_widget_to_dashboard(projectId: int, dashboardId: int, @app.post('/{projectId}/dashboards/{dashboardId}/metrics', tags=["dashboard"]) @app.put('/{projectId}/dashboards/{dashboardId}/metrics', tags=["dashboard"]) def create_metric_and_add_to_dashboard(projectId: int, dashboardId: int, - data: schemas.CreateCustomMetricsSchema = Body(...), + data: schemas.CreateCardSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": dashboards.create_metric_add_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, data=data)} @@ -80,43 +83,41 @@ def remove_widget_from_dashboard(projectId: int, dashboardId: int, widgetId: int widget_id=widgetId) -@app.post('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}/chart', tags=["dashboard"]) -def get_widget_chart(projectId: int, dashboardId: int, widgetId: int, - data: schemas.CustomMetricChartPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): - data = dashboards.make_chart_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, - widget_id=widgetId, data=data) - if data is None: - return {"errors": ["widget not found"]} - return {"data": data} - - -@app.get('/{projectId}/metrics/templates', tags=["dashboard"]) -def get_templates(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): - return {"data": dashboards.get_templates(project_id=projectId, user_id=context.user_id)} +# @app.post('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}/chart', tags=["dashboard"]) +# def get_widget_chart(projectId: int, dashboardId: int, widgetId: int, +# data: schemas.CardChartSchema = Body(...), +# context: schemas.CurrentContext = Depends(OR_context)): +# data = dashboards.make_chart_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, +# widget_id=widgetId, data=data) +# if data is None: +# return {"errors": ["widget not found"]} +# return {"data": data} +@app.post('/{projectId}/cards/try', tags=["cards"]) @app.post('/{projectId}/metrics/try', tags=["dashboard"]) @app.put('/{projectId}/metrics/try', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/try', tags=["customMetrics"]) @app.put('/{projectId}/custom_metrics/try', tags=["customMetrics"]) -def try_custom_metric(projectId: int, data: schemas.TryCustomMetricsPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def try_card(projectId: int, data: schemas.CreateCardSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return {"data": custom_metrics.merged_live(project_id=projectId, data=data, user_id=context.user_id)} +@app.post('/{projectId}/cards/try/sessions', tags=["cards"]) @app.post('/{projectId}/metrics/try/sessions', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/try/sessions', tags=["customMetrics"]) -def try_custom_metric_sessions(projectId: int, data: schemas.CustomMetricSessionsPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def try_card_sessions(projectId: int, data: schemas.CardSessionsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): data = custom_metrics.try_sessions(project_id=projectId, user_id=context.user_id, data=data) return {"data": data} +@app.post('/{projectId}/cards/try/issues', tags=["cards"]) @app.post('/{projectId}/metrics/try/issues', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/try/issues', tags=["customMetrics"]) -def try_custom_metric_funnel_issues(projectId: int, data: schemas.CustomMetricSessionsPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def try_card_funnel_issues(projectId: int, data: schemas.CardSessionsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): if len(data.series) == 0: return {"data": []} data.series[0].filter.startDate = data.startTimestamp @@ -125,46 +126,72 @@ def try_custom_metric_funnel_issues(projectId: int, data: schemas.CustomMetricSe return {"data": data} +@app.get('/{projectId}/cards', tags=["cards"]) +@app.get('/{projectId}/metrics', tags=["dashboard"]) +@app.get('/{projectId}/custom_metrics', tags=["customMetrics"]) +def get_cards(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): + return {"data": custom_metrics.get_all(project_id=projectId, user_id=context.user_id)} + + +@app.post('/{projectId}/cards', tags=["cards"]) @app.post('/{projectId}/metrics', tags=["dashboard"]) @app.put('/{projectId}/metrics', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics', tags=["customMetrics"]) @app.put('/{projectId}/custom_metrics', tags=["customMetrics"]) -def add_custom_metric(projectId: int, data: schemas.CreateCustomMetricsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def create_card(projectId: int, data: schemas.CreateCardSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return custom_metrics.create(project_id=projectId, user_id=context.user_id, data=data) -@app.get('/{projectId}/metrics', tags=["dashboard"]) -@app.get('/{projectId}/custom_metrics', tags=["customMetrics"]) -def get_custom_metrics(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): - return {"data": custom_metrics.get_all(project_id=projectId, user_id=context.user_id)} +@app.post('/{projectId}/cards/search', tags=["cards"]) +@app.post('/{projectId}/metrics/search', tags=["dashboard"]) +@app.post('/{projectId}/custom_metrics/search', tags=["customMetrics"]) +def search_cards(projectId: int, data: schemas.SearchCardsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + return {"data": custom_metrics.search_all(project_id=projectId, user_id=context.user_id, data=data)} +@app.get('/{projectId}/cards/{metric_id}', tags=["cards"]) @app.get('/{projectId}/metrics/{metric_id}', tags=["dashboard"]) @app.get('/{projectId}/custom_metrics/{metric_id}', tags=["customMetrics"]) -def get_custom_metric(projectId: int, metric_id: str, context: schemas.CurrentContext = Depends(OR_context)): - data = custom_metrics.get(project_id=projectId, user_id=context.user_id, metric_id=metric_id) +def get_card(projectId: int, metric_id: Union[int, str], context: schemas.CurrentContext = Depends(OR_context)): + if not isinstance(metric_id, int): + return {"errors": ["invalid card_id"]} + data = custom_metrics.get_card(project_id=projectId, user_id=context.user_id, metric_id=metric_id) if data is None: - return {"errors": ["custom metric not found"]} + return {"errors": ["card not found"]} return {"data": data} +# @app.get('/{projectId}/cards/{metric_id}/thumbnail', tags=["cards"]) +# def sign_thumbnail_for_upload(projectId: int, metric_id: Union[int, str], +# context: schemas.CurrentContext = Depends(OR_context)): +# if not isinstance(metric_id, int): +# return {"errors": ["invalid card_id"]} +# return custom_metrics.add_thumbnail(metric_id=metric_id, user_id=context.user_id, project_id=projectId) + + +@app.post('/{projectId}/cards/{metric_id}/sessions', tags=["cards"]) @app.post('/{projectId}/metrics/{metric_id}/sessions', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/sessions', tags=["customMetrics"]) -def get_custom_metric_sessions(projectId: int, metric_id: int, - data: schemas.CustomMetricSessionsPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def get_card_sessions(projectId: int, metric_id: int, + data: schemas.CardSessionsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): data = custom_metrics.get_sessions(project_id=projectId, user_id=context.user_id, metric_id=metric_id, data=data) if data is None: return {"errors": ["custom metric not found"]} return {"data": data} +@app.post('/{projectId}/cards/{metric_id}/issues', tags=["cards"]) @app.post('/{projectId}/metrics/{metric_id}/issues', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/issues', tags=["customMetrics"]) -def get_custom_metric_funnel_issues(projectId: int, metric_id: int, - data: schemas.CustomMetricSessionsPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def get_card_funnel_issues(projectId: int, metric_id: Union[int, str], + data: schemas.CardSessionsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + if not isinstance(metric_id, int): + return {"errors": [f"invalid card_id: {metric_id}"]} + data = custom_metrics.get_funnel_issues(project_id=projectId, user_id=context.user_id, metric_id=metric_id, data=data) if data is None: @@ -172,10 +199,11 @@ def get_custom_metric_funnel_issues(projectId: int, metric_id: int, return {"data": data} +@app.post('/{projectId}/cards/{metric_id}/issues/{issueId}/sessions', tags=["dashboard"]) @app.post('/{projectId}/metrics/{metric_id}/issues/{issueId}/sessions', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/issues/{issueId}/sessions', tags=["customMetrics"]) def get_metric_funnel_issue_sessions(projectId: int, metric_id: int, issueId: str, - data: schemas.CustomMetricSessionsPayloadSchema = Body(...), + data: schemas.CardSessionsSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): data = custom_metrics.get_funnel_sessions_by_issue(project_id=projectId, user_id=context.user_id, metric_id=metric_id, issue_id=issueId, data=data) @@ -184,10 +212,11 @@ def get_metric_funnel_issue_sessions(projectId: int, metric_id: int, issueId: st return {"data": data} +@app.post('/{projectId}/cards/{metric_id}/errors', tags=["dashboard"]) @app.post('/{projectId}/metrics/{metric_id}/errors', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/errors', tags=["customMetrics"]) def get_custom_metric_errors_list(projectId: int, metric_id: int, - data: schemas.CustomMetricSessionsPayloadSchema = Body(...), + data: schemas.CardSessionsSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): data = custom_metrics.get_errors_list(project_id=projectId, user_id=context.user_id, metric_id=metric_id, data=data) @@ -196,22 +225,22 @@ def get_custom_metric_errors_list(projectId: int, metric_id: int, return {"data": data} +@app.post('/{projectId}/cards/{metric_id}/chart', tags=["card"]) @app.post('/{projectId}/metrics/{metric_id}/chart', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/chart', tags=["customMetrics"]) -def get_custom_metric_chart(projectId: int, metric_id: int, data: schemas.CustomMetricChartPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): - data = dashboards.make_chart_metrics(project_id=projectId, user_id=context.user_id, metric_id=metric_id, - data=data) - if data is None: - return {"errors": ["custom metric not found"]} +def get_card_chart(projectId: int, metric_id: int, request: Request, data: schemas.CardChartSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + data = custom_metrics.make_chart_from_card(project_id=projectId, user_id=context.user_id, metric_id=metric_id, + data=data) return {"data": data} +@app.post('/{projectId}/cards/{metric_id}', tags=["dashboard"]) @app.post('/{projectId}/metrics/{metric_id}', tags=["dashboard"]) @app.put('/{projectId}/metrics/{metric_id}', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}', tags=["customMetrics"]) @app.put('/{projectId}/custom_metrics/{metric_id}', tags=["customMetrics"]) -def update_custom_metric(projectId: int, metric_id: int, data: schemas.UpdateCustomMetricsSchema = Body(...), +def update_custom_metric(projectId: int, metric_id: int, data: schemas.UpdateCardSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): data = custom_metrics.update(project_id=projectId, user_id=context.user_id, metric_id=metric_id, data=data) if data is None: @@ -219,6 +248,7 @@ def update_custom_metric(projectId: int, metric_id: int, data: schemas.UpdateCus return {"data": data} +@app.post('/{projectId}/cards/{metric_id}/status', tags=["dashboard"]) @app.post('/{projectId}/metrics/{metric_id}/status', tags=["dashboard"]) @app.put('/{projectId}/metrics/{metric_id}/status', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/status', tags=["customMetrics"]) @@ -231,6 +261,7 @@ def update_custom_metric_state(projectId: int, metric_id: int, status=data.active)} +@app.delete('/{projectId}/cards/{metric_id}', tags=["dashboard"]) @app.delete('/{projectId}/metrics/{metric_id}', tags=["dashboard"]) @app.delete('/{projectId}/custom_metrics/{metric_id}', tags=["customMetrics"]) def delete_custom_metric(projectId: int, metric_id: int, context: schemas.CurrentContext = Depends(OR_context)): diff --git a/api/routers/subs/v1_api.py b/api/routers/subs/v1_api.py index 3892bfef2..2af0b398a 100644 --- a/api/routers/subs/v1_api.py +++ b/api/routers/subs/v1_api.py @@ -30,7 +30,7 @@ def get_session_events(projectKey: str, sessionId: int): if projectId is None: return {"errors": ["invalid projectKey"]} return { - 'data': events.get_by_sessionId2_pg( + 'data': events.get_by_session_id( project_id=projectId, session_id=sessionId ) diff --git a/api/run-alerts-dev.sh b/api/run-alerts-dev.sh new file mode 100755 index 000000000..54db30171 --- /dev/null +++ b/api/run-alerts-dev.sh @@ -0,0 +1,3 @@ +#!/bin/zsh + +uvicorn app_alerts:app --reload \ No newline at end of file diff --git a/api/schemas.py b/api/schemas.py index 7e990bcb8..5cae3a31a 100644 --- a/api/schemas.py +++ b/api/schemas.py @@ -15,6 +15,10 @@ def transform_email(email: str) -> str: return email.lower().strip() if isinstance(email, str) else email +def remove_whitespace(value: str) -> str: + return " ".join(value.split()) if isinstance(value, str) else value + + class _Grecaptcha(BaseModel): g_recaptcha_response: Optional[str] = Field(None, alias='g-recaptcha-response') @@ -64,7 +68,8 @@ class UpdateTenantSchema(BaseModel): class CreateProjectSchema(BaseModel): - name: str = Field("my first project") + name: str = Field(default="my first project") + _transform_name = validator('name', pre=True, allow_reuse=True)(remove_whitespace) class CurrentAPIContext(BaseModel): @@ -78,14 +83,15 @@ class CurrentContext(CurrentAPIContext): _transform_email = validator('email', pre=True, allow_reuse=True)(transform_email) -class AddSlackSchema(BaseModel): +class AddCollaborationSchema(BaseModel): name: str = Field(...) url: HttpUrl = Field(...) + _transform_name = validator('name', pre=True, allow_reuse=True)(remove_whitespace) + _transform_url = validator('url', pre=True, allow_reuse=True)(remove_whitespace) -class EditSlackSchema(BaseModel): +class EditCollaborationSchema(AddCollaborationSchema): name: Optional[str] = Field(None) - url: HttpUrl = Field(...) class CreateNotificationSchema(BaseModel): @@ -93,7 +99,19 @@ class CreateNotificationSchema(BaseModel): notifications: List = Field(...) -class NotificationsViewSchema(BaseModel): +class _TimedSchema(BaseModel): + startTimestamp: int = Field(default=None) + endTimestamp: int = Field(default=None) + + @root_validator + def time_validator(cls, values): + if values.get("startTimestamp") is not None and values.get("endTimestamp") is not None: + assert values.get("startTimestamp") < values.get("endTimestamp"), \ + "endTimestamp must be greater than startTimestamp" + return values + + +class NotificationsViewSchema(_TimedSchema): ids: Optional[List] = Field(default=[]) startTimestamp: Optional[int] = Field(default=None) endTimestamp: Optional[int] = Field(default=None) @@ -117,6 +135,7 @@ class CreateEditWebhookSchema(BaseModel): endpoint: str = Field(...) authHeader: Optional[str] = Field(None) name: Optional[str] = Field(...) + _transform_name = validator('name', pre=True, allow_reuse=True)(remove_whitespace) class CreateMemberSchema(BaseModel): @@ -126,12 +145,15 @@ class CreateMemberSchema(BaseModel): admin: bool = Field(False) _transform_email = validator('email', pre=True, allow_reuse=True)(transform_email) + _transform_name = validator('name', pre=True, allow_reuse=True)(remove_whitespace) class EditMemberSchema(EditUserSchema): name: str = Field(...) email: EmailStr = Field(...) admin: bool = Field(False) + _transform_name = validator('name', pre=True, allow_reuse=True)(remove_whitespace) + _transform_email = validator('email', pre=True, allow_reuse=True)(transform_email) class EditPasswordByInvitationSchema(BaseModel): @@ -145,6 +167,7 @@ class AssignmentSchema(BaseModel): description: str = Field(...) title: str = Field(...) issue_type: str = Field(...) + _transform_title = validator('title', pre=True, allow_reuse=True)(remove_whitespace) class Config: alias_generator = attribute_to_camel_case @@ -177,12 +200,6 @@ class WeeklyReportConfigSchema(BaseModel): alias_generator = attribute_to_camel_case -class GetHeatmapPayloadSchema(BaseModel): - startDate: int = Field(TimeUTC.now(delta_days=-30)) - endDate: int = Field(TimeUTC.now()) - url: str = Field(...) - - class DatadogSchema(BaseModel): apiKey: str = Field(...) applicationKey: str = Field(...) @@ -241,6 +258,7 @@ class SumologicSchema(BaseModel): class MetadataBasicSchema(BaseModel): index: Optional[int] = Field(None) key: str = Field(...) + _transform_key = validator('key', pre=True, allow_reuse=True)(remove_whitespace) class MetadataListSchema(BaseModel): @@ -345,7 +363,8 @@ class AlertSchema(BaseModel): @root_validator(pre=True) def transform_alert(cls, values): - if values.get("seriesId") is None and isinstance(values["query"]["left"], int): + values["seriesId"] = None + if isinstance(values["query"]["left"], int): values["seriesId"] = values["query"]["left"] values["query"]["left"] = AlertColumn.custom @@ -378,59 +397,59 @@ class ErrorSource(str, Enum): class EventType(str, Enum): - click = "CLICK" - input = "INPUT" - location = "LOCATION" - custom = "CUSTOM" - request = "REQUEST" - request_details = "FETCH" - graphql = "GRAPHQL" - state_action = "STATEACTION" - error = "ERROR" - click_ios = "CLICK_IOS" - input_ios = "INPUT_IOS" - view_ios = "VIEW_IOS" - custom_ios = "CUSTOM_IOS" - request_ios = "REQUEST_IOS" - error_ios = "ERROR_IOS" + click = "click" + input = "input" + location = "location" + custom = "custom" + request = "request" + request_details = "fetch" + graphql = "graphql" + state_action = "stateAction" + error = "error" + click_ios = "clickIos" + input_ios = "inputIos" + view_ios = "viewIos" + custom_ios = "customIos" + request_ios = "requestIos" + error_ios = "errorIos" class PerformanceEventType(str, Enum): - location_dom_complete = "DOM_COMPLETE" - location_largest_contentful_paint_time = "LARGEST_CONTENTFUL_PAINT_TIME" - time_between_events = "TIME_BETWEEN_EVENTS" - location_ttfb = "TTFB" - location_avg_cpu_load = "AVG_CPU_LOAD" - location_avg_memory_usage = "AVG_MEMORY_USAGE" - fetch_failed = "FETCH_FAILED" + location_dom_complete = "domComplete" + location_largest_contentful_paint_time = "largestContentfulPaintTime" + time_between_events = "timeBetweenEvents" + location_ttfb = "ttfb" + location_avg_cpu_load = "avgCpuLoad" + location_avg_memory_usage = "avgMemoryUsage" + fetch_failed = "fetchFailed" # fetch_duration = "FETCH_DURATION" class FilterType(str, Enum): - user_os = "USEROS" - user_browser = "USERBROWSER" - user_device = "USERDEVICE" - user_country = "USERCOUNTRY" - user_id = "USERID" - user_anonymous_id = "USERANONYMOUSID" - referrer = "REFERRER" - rev_id = "REVID" + user_os = "userOs" + user_browser = "userBrowser" + user_device = "userDevice" + user_country = "userCountry" + user_id = "userId" + user_anonymous_id = "userAnonymousId" + referrer = "referrer" + rev_id = "revId" # IOS - user_os_ios = "USEROS_IOS" - user_device_ios = "USERDEVICE_IOS" - user_country_ios = "USERCOUNTRY_IOS" - user_id_ios = "USERID_IOS" - user_anonymous_id_ios = "USERANONYMOUSID_IOS" - rev_id_ios = "REVID_IOS" + user_os_ios = "userOsIos" + user_device_ios = "userDeviceIos" + user_country_ios = "userCountryIos" + user_id_ios = "userIdIos" + user_anonymous_id_ios = "userAnonymousIdIos" + rev_id_ios = "revIdIos" # - duration = "DURATION" - platform = "PLATFORM" - metadata = "METADATA" - issue = "ISSUE" - events_count = "EVENTS_COUNT" - utm_source = "UTM_SOURCE" - utm_medium = "UTM_MEDIUM" - utm_campaign = "UTM_CAMPAIGN" + duration = "duration" + platform = "platform" + metadata = "metadata" + issue = "issue" + events_count = "eventsCount" + utm_source = "utmSource" + utm_medium = "utmMedium" + utm_campaign = "utmCampaign" class SearchEventOperator(str, Enum): @@ -447,6 +466,15 @@ class SearchEventOperator(str, Enum): _ends_with = "endsWith" +class ClickEventExtraOperator(str, Enum): + _on_selector = "onSelector" + _on_text = "onText" + + +class IssueFilterOperator(str, Enum): + _on_selector = ClickEventExtraOperator._on_selector.value + + class PlatformType(str, Enum): mobile = "mobile" desktop = "desktop" @@ -507,19 +535,23 @@ class HttpMethod(str, Enum): class FetchFilterType(str, Enum): - _url = "FETCH_URL" - _status_code = "FETCH_STATUS_CODE" - _method = "FETCH_METHOD" - _duration = "FETCH_DURATION" - _request_body = "FETCH_REQUEST_BODY" - _response_body = "FETCH_RESPONSE_BODY" + _url = "fetchUrl" # FETCH_URL + _status_code = "fetchStatusCode" # FETCH_STATUS_CODE + _method = "fetchMethod" # FETCH_METHOD + _duration = "fetchDuration" # FETCH_DURATION + _request_body = "fetchRequestBody" # FETCH_REQUEST_BODY + _response_body = "fetchResponseBody" # FETCH_RESPONSE_BODY class GraphqlFilterType(str, Enum): - _name = "GRAPHQL_NAME" - _method = "GRAPHQL_METHOD" - _request_body = "GRAPHQL_REQUEST_BODY" - _response_body = "GRAPHQL_RESPONSE_BODY" + _name = "graphqlName" # GRAPHQL_NAME + _method = "graphqlMethod" # GRAPHQL_METHOD + _request_body = "graphqlRequestBody" # GRAPHQL_REQUEST_BODY + _response_body = "graphqlResponseBody" # GRAPHQL_RESPONSE_BODY + + +class IssueFilterType(str, Enum): + _selector = "CLICK_SELECTOR" class RequestGraphqlFilterSchema(BaseModel): @@ -528,14 +560,50 @@ class RequestGraphqlFilterSchema(BaseModel): operator: Union[SearchEventOperator, MathOperator] = Field(...) +class IssueFilterSchema(BaseModel): + type: IssueFilterType = Field(...) + value: List[str] = Field(...) + operator: IssueFilterOperator = Field(...) + + class _SessionSearchEventRaw(__MixedSearchFilter): is_event: bool = Field(default=True, const=True) value: List[str] = Field(...) type: Union[EventType, PerformanceEventType] = Field(...) - operator: SearchEventOperator = Field(...) - source: Optional[List[Union[ErrorSource, int, str]]] = Field(None) - sourceOperator: Optional[MathOperator] = Field(None) - filters: Optional[List[RequestGraphqlFilterSchema]] = Field(None) + operator: Union[SearchEventOperator, ClickEventExtraOperator] = Field(...) + source: Optional[List[Union[ErrorSource, int, str]]] = Field(default=None) + sourceOperator: Optional[MathOperator] = Field(default=None) + filters: Optional[List[Union[RequestGraphqlFilterSchema, IssueFilterSchema]]] = Field(default=None) + + @root_validator(pre=True) + def transform(cls, values): + if values.get("type") is None: + return values + values["type"] = { + "CLICK": EventType.click.value, + "INPUT": EventType.input.value, + "LOCATION": EventType.location.value, + "CUSTOM": EventType.custom.value, + "REQUEST": EventType.request.value, + "FETCH": EventType.request_details.value, + "GRAPHQL": EventType.graphql.value, + "STATEACTION": EventType.state_action.value, + "ERROR": EventType.error.value, + "CLICK_IOS": EventType.click_ios.value, + "INPUT_IOS": EventType.input_ios.value, + "VIEW_IOS": EventType.view_ios.value, + "CUSTOM_IOS": EventType.custom_ios.value, + "REQUEST_IOS": EventType.request_ios.value, + "ERROR_IOS": EventType.error_ios.value, + "DOM_COMPLETE": PerformanceEventType.location_dom_complete.value, + "LARGEST_CONTENTFUL_PAINT_TIME": PerformanceEventType.location_largest_contentful_paint_time.value, + "TIME_BETWEEN_EVENTS": PerformanceEventType.time_between_events.value, + "TTFB": PerformanceEventType.location_ttfb.value, + "AVG_CPU_LOAD": PerformanceEventType.location_avg_cpu_load.value, + "AVG_MEMORY_USAGE": PerformanceEventType.location_avg_memory_usage.value, + "FETCH_FAILED": PerformanceEventType.fetch_failed.value, + }.get(values["type"], values["type"]) + return values @root_validator def event_validator(cls, values): @@ -548,7 +616,7 @@ class _SessionSearchEventRaw(__MixedSearchFilter): assert values.get("sourceOperator") is not None, \ "sourceOperator should not be null for PerformanceEventType" if values["type"] == PerformanceEventType.time_between_events: - assert values["sourceOperator"] != MathOperator._equal.value, \ + assert values["sourceOperator"] != MathOperator._equal, \ f"{MathOperator._equal} is not allowed for duration of {PerformanceEventType.time_between_events}" assert len(values.get("value", [])) == 2, \ f"must provide 2 Events as value for {PerformanceEventType.time_between_events}" @@ -566,11 +634,14 @@ class _SessionSearchEventRaw(__MixedSearchFilter): values["source"] = [ErrorSource.js_exception] elif values.get("type") == EventType.request_details: assert isinstance(values.get("filters"), List) and len(values.get("filters", [])) > 0, \ - f"filters should be defined for {EventType.request_details.value}" + f"filters should be defined for {EventType.request_details}" elif values.get("type") == EventType.graphql: assert isinstance(values.get("filters"), List) and len(values.get("filters", [])) > 0, \ - f"filters should be defined for {EventType.graphql.value}" + f"filters should be defined for {EventType.graphql}" + if isinstance(values.get("operator"), ClickEventExtraOperator): + assert values.get("type") == EventType.click, \ + f"operator:{values['operator']} is only available for event-type: {EventType.click}" return values @@ -580,11 +651,44 @@ class _SessionSearchEventSchema(_SessionSearchEventRaw): class SessionSearchFilterSchema(__MixedSearchFilter): is_event: bool = Field(False, const=False) - value: Union[Optional[Union[IssueType, PlatformType, int, str]], - Optional[List[Union[IssueType, PlatformType, int, str]]]] = Field(...) + # TODO: remove this if there nothing broken from the UI + # value: Union[Optional[Union[IssueType, PlatformType, int, str]], + # Optional[List[Union[IssueType, PlatformType, int, str]]]] = Field(...) + value: List[Union[IssueType, PlatformType, int, str]] = Field(default=[]) type: FilterType = Field(...) operator: Union[SearchEventOperator, MathOperator] = Field(...) source: Optional[Union[ErrorSource, str]] = Field(default=None) + filters: List[IssueFilterSchema] = Field(default=[]) + + @root_validator(pre=True) + def transform(cls, values): + if values.get("type") is None: + return values + values["type"] = { + "USEROS": FilterType.user_os.value, + "USERBROWSER": FilterType.user_browser.value, + "USERDEVICE": FilterType.user_device.value, + "USERCOUNTRY": FilterType.user_country.value, + "USERID": FilterType.user_id.value, + "USERANONYMOUSID": FilterType.user_anonymous_id.value, + "REFERRER": FilterType.referrer.value, + "REVID": FilterType.rev_id.value, + "USEROS_IOS": FilterType.user_os_ios.value, + "USERDEVICE_IOS": FilterType.user_device_ios.value, + "USERCOUNTRY_IOS": FilterType.user_country_ios.value, + "USERID_IOS": FilterType.user_id_ios.value, + "USERANONYMOUSID_IOS": FilterType.user_anonymous_id_ios.value, + "REVID_IOS": FilterType.rev_id_ios.value, + "DURATION": FilterType.duration.value, + "PLATFORM": FilterType.platform.value, + "METADATA": FilterType.metadata.value, + "ISSUE": FilterType.issue.value, + "EVENTS_COUNT": FilterType.events_count.value, + "UTM_SOURCE": FilterType.utm_source.value, + "UTM_MEDIUM": FilterType.utm_medium.value, + "UTM_CAMPAIGN": FilterType.utm_campaign.value + }.get(values["type"], values["type"]) + return values @root_validator def filter_validator(cls, values): @@ -632,7 +736,12 @@ class SessionsSearchPayloadSchema(_PaginatedSchema): @root_validator(pre=True) def transform_order(cls, values): - if values.get("order") is not None: + if values.get("sort") is None: + values["sort"] = "startTs" + + if values.get("order") is None: + values["order"] = SortOrderType.desc + else: values["order"] = values["order"].upper() return values @@ -698,18 +807,12 @@ class FunnelSearchPayloadSchema(FlatSessionsSearchPayloadSchema): class FunnelSchema(BaseModel): name: str = Field(...) filter: FunnelSearchPayloadSchema = Field([]) - is_public: bool = Field(False) + is_public: bool = Field(default=False) class Config: alias_generator = attribute_to_camel_case -class UpdateFunnelSchema(FunnelSchema): - name: Optional[str] = Field(None) - filter: Optional[FunnelSearchPayloadSchema] = Field(None) - is_public: Optional[bool] = Field(None) - - class FunnelInsightsPayloadSchema(FlatSessionsSearchPayloadSchema): # class FunnelInsightsPayloadSchema(SessionsSearchPayloadSchema): sort: Optional[str] = Field(None) @@ -739,7 +842,7 @@ class SearchErrorsSchema(FlatSessionsSearchPayloadSchema): query: Optional[str] = Field(default=None) -class MetricPayloadSchema(BaseModel): +class MetricPayloadSchema(_TimedSchema): startTimestamp: int = Field(TimeUTC.now(delta_days=-1)) endTimestamp: int = Field(TimeUTC.now()) density: int = Field(7) @@ -764,19 +867,19 @@ class MobileSignPayloadSchema(BaseModel): keys: List[str] = Field(...) -class CustomMetricSeriesFilterSchema(SearchErrorsSchema): - startDate: Optional[int] = Field(None) - endDate: Optional[int] = Field(None) - sort: Optional[str] = Field(None) - order: Optional[str] = Field(None) +class CardSeriesFilterSchema(SearchErrorsSchema): + startDate: Optional[int] = Field(default=None) + endDate: Optional[int] = Field(default=None) + sort: Optional[str] = Field(default=None) + order: SortOrderType = Field(default=SortOrderType.desc) group_by_user: Optional[bool] = Field(default=False, const=True) -class CustomMetricCreateSeriesSchema(BaseModel): +class CardCreateSeriesSchema(BaseModel): series_id: Optional[int] = Field(None) name: Optional[str] = Field(None) index: Optional[int] = Field(None) - filter: Optional[CustomMetricSeriesFilterSchema] = Field([]) + filter: Optional[CardSeriesFilterSchema] = Field([]) class Config: alias_generator = attribute_to_camel_case @@ -793,114 +896,229 @@ class MetricTableViewType(str, Enum): pie_chart = "pieChart" +class MetricOtherViewType(str, Enum): + other_chart = "chart" + list_chart = "list" + + class MetricType(str, Enum): timeseries = "timeseries" table = "table" - predefined = "predefined" funnel = "funnel" + errors = "errors" + performance = "performance" + resources = "resources" + web_vital = "webVitals" + pathAnalysis = "pathAnalysis" + retention = "retention" + stickiness = "stickiness" + click_map = "clickMap" + insights = "insights" -class TableMetricOfType(str, Enum): +class MetricOfErrors(str, Enum): + calls_errors = "callsErrors" # calls_errors + domains_errors_4xx = "domainsErrors4xx" # domains_errors_4xx + domains_errors_5xx = "domainsErrors5xx" # domains_errors_5xx + errors_per_domains = "errorsPerDomains" # errors_per_domains + errors_per_type = "errorsPerType" # errors_per_type + impacted_sessions_by_js_errors = "impactedSessionsByJsErrors" # impacted_sessions_by_js_errors + resources_by_party = "resourcesByParty" # resources_by_party + + +class MetricOfPerformance(str, Enum): + cpu = "cpu" # cpu + crashes = "crashes" # crashes + fps = "fps" # fps + impacted_sessions_by_slow_pages = "impactedSessionsBySlowPages" # impacted_sessions_by_slow_pages + memory_consumption = "memoryConsumption" # memory_consumption + pages_dom_buildtime = "pagesDomBuildtime" # pages_dom_buildtime + pages_response_time = "pagesResponseTime" # pages_response_time + pages_response_time_distribution = "pagesResponseTimeDistribution" # pages_response_time_distribution + resources_vs_visually_complete = "resourcesVsVisuallyComplete" # resources_vs_visually_complete + sessions_per_browser = "sessionsPerBrowser" # sessions_per_browser + slowest_domains = "slowestDomains" # slowest_domains + speed_location = "speedLocation" # speed_location + time_to_render = "timeToRender" # time_to_render + + +class MetricOfResources(str, Enum): + missing_resources = "missingResources" # missing_resources + resources_count_by_type = "resourcesCountByType" # resources_count_by_type + resources_loading_time = "resourcesLoadingTime" # resources_loading_time + resource_type_vs_response_end = "resourceTypeVsResponseEnd" # resource_type_vs_response_end + slowest_resources = "slowestResources" # slowest_resources + + +class MetricOfWebVitals(str, Enum): + avg_cpu = "avgCpu" # avg_cpu + avg_dom_content_loaded = "avgDomContentLoaded" # avg_dom_content_loaded + avg_dom_content_load_start = "avgDomContentLoadStart" # avg_dom_content_load_start + avg_first_contentful_pixel = "avgFirstContentfulPixel" # avg_first_contentful_pixel + avg_first_paint = "avgFirstPaint" # avg_first_paint + avg_fps = "avgFps" # avg_fps + avg_image_load_time = "avgImageLoadTime" # avg_image_load_time + avg_page_load_time = "avgPageLoadTime" # avg_page_load_time + avg_pages_dom_buildtime = "avgPagesDomBuildtime" # avg_pages_dom_buildtime + avg_pages_response_time = "avgPagesResponseTime" # avg_pages_response_time + avg_request_load_time = "avgRequestLoadTime" # avg_request_load_time + avg_response_time = "avgResponseTime" # avg_response_time + avg_session_duration = "avgSessionDuration" # avg_session_duration + avg_till_first_byte = "avgTillFirstByte" # avg_till_first_byte + avg_time_to_interactive = "avgTimeToInteractive" # avg_time_to_interactive + avg_time_to_render = "avgTimeToRender" # avg_time_to_render + avg_used_js_heap_size = "avgUsedJsHeapSize" # avg_used_js_heap_size + avg_visited_pages = "avgVisitedPages" # avg_visited_pages + count_requests = "countRequests" # count_requests + count_sessions = "countSessions" # count_sessions + + +class MetricOfTable(str, Enum): user_os = FilterType.user_os.value user_browser = FilterType.user_browser.value user_device = FilterType.user_device.value user_country = FilterType.user_country.value user_id = FilterType.user_id.value issues = FilterType.issue.value - visited_url = EventType.location.value - sessions = "SESSIONS" - errors = IssueType.js_exception.value + visited_url = "location" + sessions = "sessions" + errors = "jsException" -class TimeseriesMetricOfType(str, Enum): +class MetricOfTimeseries(str, Enum): session_count = "sessionCount" -class CustomMetricSessionsPayloadSchema(FlatSessionsSearch, _PaginatedSchema): +class MetricOfClickMap(str, Enum): + click_map_url = "clickMapUrl" + + +class CardSessionsSchema(FlatSessionsSearch, _PaginatedSchema, _TimedSchema): startTimestamp: int = Field(TimeUTC.now(-7)) endTimestamp: int = Field(TimeUTC.now()) - series: Optional[List[CustomMetricCreateSeriesSchema]] = Field(default=None) + series: List[CardCreateSeriesSchema] = Field(default=[]) class Config: alias_generator = attribute_to_camel_case -class CustomMetricChartPayloadSchema(CustomMetricSessionsPayloadSchema, _PaginatedSchema): +class CardChartSchema(CardSessionsSchema): density: int = Field(7) - class Config: - alias_generator = attribute_to_camel_case - -class TryCustomMetricsPayloadSchema(CustomMetricChartPayloadSchema): - name: str = Field(...) - series: List[CustomMetricCreateSeriesSchema] = Field(...) - is_public: bool = Field(default=True) - view_type: Union[MetricTimeseriesViewType, MetricTableViewType] = Field(MetricTimeseriesViewType.line_chart) - metric_type: MetricType = Field(MetricType.timeseries) - metric_of: Union[TableMetricOfType, TimeseriesMetricOfType] = Field(TableMetricOfType.user_id) - metric_value: List[IssueType] = Field([]) - metric_format: Optional[MetricFormatType] = Field(None) - - # metricFraction: float = Field(None, gt=0, lt=1) - # This is used to handle wrong values sent by the UI - @root_validator(pre=True) - def remove_metric_value(cls, values): - if values.get("metricType") == MetricType.timeseries \ - or values.get("metricType") == MetricType.table \ - and values.get("metricOf") != TableMetricOfType.issues: - values["metricValue"] = [] - return values - - @root_validator - def validator(cls, values): - if values.get("metric_type") == MetricType.table: - assert isinstance(values.get("view_type"), MetricTableViewType), \ - f"viewType must be of type {MetricTableViewType} for metricType:{MetricType.table.value}" - assert isinstance(values.get("metric_of"), TableMetricOfType), \ - f"metricOf must be of type {TableMetricOfType} for metricType:{MetricType.table.value}" - if values.get("metric_of") != TableMetricOfType.issues: - assert values.get("metric_value") is None or len(values.get("metric_value")) == 0, \ - f"metricValue is only available for metricOf:{TableMetricOfType.issues.value}" - elif values.get("metric_type") == MetricType.timeseries: - assert isinstance(values.get("view_type"), MetricTimeseriesViewType), \ - f"viewType must be of type {MetricTimeseriesViewType} for metricType:{MetricType.timeseries.value}" - assert isinstance(values.get("metric_of"), TimeseriesMetricOfType), \ - f"metricOf must be of type {TimeseriesMetricOfType} for metricType:{MetricType.timeseries.value}" - return values - - class Config: - alias_generator = attribute_to_camel_case - - -class CustomMetricsConfigSchema(BaseModel): +class CardConfigSchema(BaseModel): col: Optional[int] = Field(...) row: Optional[int] = Field(default=2) position: Optional[int] = Field(default=0) -class CreateCustomMetricsSchema(TryCustomMetricsPayloadSchema): - series: List[CustomMetricCreateSeriesSchema] = Field(..., min_items=1) - config: CustomMetricsConfigSchema = Field(...) +class CreateCardSchema(CardChartSchema): + name: Optional[str] = Field(...) + is_public: bool = Field(default=True) + view_type: Union[MetricTimeseriesViewType, \ + MetricTableViewType, MetricOtherViewType] = Field(...) + metric_type: MetricType = Field(...) + metric_of: Union[MetricOfTimeseries, MetricOfTable, MetricOfErrors, \ + MetricOfPerformance, MetricOfResources, MetricOfWebVitals, \ + MetricOfClickMap] = Field(MetricOfTable.user_id) + metric_value: List[IssueType] = Field(default=[]) + metric_format: Optional[MetricFormatType] = Field(default=None) + default_config: CardConfigSchema = Field(..., alias="config") + is_template: bool = Field(default=False) + thumbnail: Optional[str] = Field(default=None) + # This is used to handle wrong values sent by the UI @root_validator(pre=True) - def transform_series(cls, values): - if values.get("series") is not None and len(values["series"]) > 1 and values.get( - "metric_type") == MetricType.funnel.value: + def transform(cls, values): + values["isTemplate"] = values.get("metricType") in [MetricType.errors, MetricType.performance, + MetricType.resources, MetricType.web_vital] + if values.get("metricType") == MetricType.timeseries \ + or values.get("metricType") == MetricType.table \ + and values.get("metricOf") != MetricOfTable.issues: + values["metricValue"] = [] + + if values.get("metricType") == MetricType.funnel and \ + values.get("series") is not None and len(values["series"]) > 0: values["series"] = [values["series"][0]] + elif values.get("metricType") not in [MetricType.table, + MetricType.timeseries, + MetricType.insights, + MetricType.click_map, + MetricType.funnel] \ + and values.get("series") is not None and len(values["series"]) > 0: + values["series"] = [] return values + @root_validator + def restrictions(cls, values): + assert values.get("metric_type") != MetricType.insights, f"metricType:{MetricType.insights} not supported yet" + return values -class CustomMetricUpdateSeriesSchema(CustomMetricCreateSeriesSchema): + @root_validator + def validator(cls, values): + if values.get("metric_type") == MetricType.timeseries: + assert isinstance(values.get("view_type"), MetricTimeseriesViewType), \ + f"viewType must be of type {MetricTimeseriesViewType} for metricType:{MetricType.timeseries}" + assert isinstance(values.get("metric_of"), MetricOfTimeseries), \ + f"metricOf must be of type {MetricOfTimeseries} for metricType:{MetricType.timeseries}" + elif values.get("metric_type") == MetricType.table: + assert isinstance(values.get("view_type"), MetricTableViewType), \ + f"viewType must be of type {MetricTableViewType} for metricType:{MetricType.table}" + assert isinstance(values.get("metric_of"), MetricOfTable), \ + f"metricOf must be of type {MetricOfTable} for metricType:{MetricType.table}" + if values.get("metric_of") in (MetricOfTable.sessions, MetricOfTable.errors): + assert values.get("view_type") == MetricTableViewType.table, \ + f"viewType must be '{MetricTableViewType.table}' for metricOf:{values['metric_of']}" + if values.get("metric_of") != MetricOfTable.issues: + assert values.get("metric_value") is None or len(values.get("metric_value")) == 0, \ + f"metricValue is only available for metricOf:{MetricOfTable.issues}" + elif values.get("metric_type") == MetricType.funnel: + pass + # allow UI sot send empty series for funnel + # assert len(values["series"]) == 1, f"must have only 1 series for metricType:{MetricType.funnel}" + # ignore this for now, let the UI send whatever he wants for metric_of + # assert isinstance(values.get("metric_of"), MetricOfTimeseries), \ + # f"metricOf must be of type {MetricOfTimeseries} for metricType:{MetricType.funnel}" + else: + if values.get("metric_type") == MetricType.errors: + assert isinstance(values.get("metric_of"), MetricOfErrors), \ + f"metricOf must be of type {MetricOfErrors} for metricType:{MetricType.errors}" + elif values.get("metric_type") == MetricType.performance: + assert isinstance(values.get("metric_of"), MetricOfPerformance), \ + f"metricOf must be of type {MetricOfPerformance} for metricType:{MetricType.performance}" + elif values.get("metric_type") == MetricType.resources: + assert isinstance(values.get("metric_of"), MetricOfResources), \ + f"metricOf must be of type {MetricOfResources} for metricType:{MetricType.resources}" + elif values.get("metric_type") == MetricType.web_vital: + assert isinstance(values.get("metric_of"), MetricOfWebVitals), \ + f"metricOf must be of type {MetricOfWebVitals} for metricType:{MetricType.web_vital}" + elif values.get("metric_type") == MetricType.click_map: + assert isinstance(values.get("metric_of"), MetricOfClickMap), \ + f"metricOf must be of type {MetricOfClickMap} for metricType:{MetricType.click_map}" + # Allow only LOCATION events for clickMap + for s in values.get("series", []): + for f in s.filter.events: + assert f.type == EventType.location, f"only events of type:{EventType.location} are allowed for metricOf:{MetricType.click_map}" + + assert isinstance(values.get("view_type"), MetricOtherViewType), \ + f"viewType must be 'chart|list' for metricOf:{values.get('metric_of')}" + + return values + + class Config: + alias_generator = attribute_to_camel_case + + +class CardUpdateSeriesSchema(CardCreateSeriesSchema): series_id: Optional[int] = Field(None) class Config: alias_generator = attribute_to_camel_case -class UpdateCustomMetricsSchema(CreateCustomMetricsSchema): - series: List[CustomMetricUpdateSeriesSchema] = Field(..., min_items=1) +class UpdateCardSchema(CreateCardSchema): + series: List[CardUpdateSeriesSchema] = Field(...) class UpdateCustomMetricsStatusSchema(BaseModel): @@ -941,55 +1159,6 @@ class AddWidgetToDashboardPayloadSchema(UpdateWidgetPayloadSchema): alias_generator = attribute_to_camel_case -# these values should match the keys in metrics table -class TemplatePredefinedKeys(str, Enum): - count_sessions = "count_sessions" - avg_request_load_time = "avg_request_load_time" - avg_page_load_time = "avg_page_load_time" - avg_image_load_time = "avg_image_load_time" - avg_dom_content_load_start = "avg_dom_content_load_start" - avg_first_contentful_pixel = "avg_first_contentful_pixel" - avg_visited_pages = "avg_visited_pages" - avg_session_duration = "avg_session_duration" - avg_pages_dom_buildtime = "avg_pages_dom_buildtime" - avg_pages_response_time = "avg_pages_response_time" - avg_response_time = "avg_response_time" - avg_first_paint = "avg_first_paint" - avg_dom_content_loaded = "avg_dom_content_loaded" - avg_till_first_bit = "avg_till_first_byte" - avg_time_to_interactive = "avg_time_to_interactive" - count_requests = "count_requests" - avg_time_to_render = "avg_time_to_render" - avg_used_js_heap_size = "avg_used_js_heap_size" - avg_cpu = "avg_cpu" - avg_fps = "avg_fps" - impacted_sessions_by_js_errors = "impacted_sessions_by_js_errors" - domains_errors_4xx = "domains_errors_4xx" - domains_errors_5xx = "domains_errors_5xx" - errors_per_domains = "errors_per_domains" - calls_errors = "calls_errors" - errors_by_type = "errors_per_type" - errors_by_origin = "resources_by_party" - speed_index_by_location = "speed_location" - slowest_domains = "slowest_domains" - sessions_per_browser = "sessions_per_browser" - time_to_render = "time_to_render" - impacted_sessions_by_slow_pages = "impacted_sessions_by_slow_pages" - memory_consumption = "memory_consumption" - cpu_load = "cpu" - frame_rate = "fps" - crashes = "crashes" - resources_vs_visually_complete = "resources_vs_visually_complete" - pages_dom_buildtime = "pages_dom_buildtime" - pages_response_time = "pages_response_time" - pages_response_time_distribution = "pages_response_time_distribution" - missing_resources = "missing_resources" - slowest_resources = "slowest_resources" - resources_fetch_time = "resources_loading_time" - resource_type_vs_response_end = "resource_type_vs_response_end" - resources_count_by_type = "resources_count_by_type" - - class TemplatePredefinedUnits(str, Enum): millisecond = "ms" second = "s" @@ -1000,15 +1169,6 @@ class TemplatePredefinedUnits(str, Enum): count = "count" -class CustomMetricAndTemplate(BaseModel): - is_template: bool = Field(...) - project_id: Optional[int] = Field(...) - predefined_key: Optional[TemplatePredefinedKeys] = Field(...) - - class Config: - alias_generator = attribute_to_camel_case - - class LiveFilterType(str, Enum): user_os = FilterType.user_os.value user_browser = FilterType.user_browser.value @@ -1018,25 +1178,25 @@ class LiveFilterType(str, Enum): user_anonymous_id = FilterType.user_anonymous_id.value rev_id = FilterType.rev_id.value platform = FilterType.platform.value - page_title = "PAGETITLE" - session_id = "SESSIONID" - metadata = "METADATA" - user_UUID = "USERUUID" - tracker_version = "TRACKERVERSION" - user_browser_version = "USERBROWSERVERSION" - user_device_type = "USERDEVICETYPE" + page_title = "pageTitle" + session_id = "sessionId" + metadata = FilterType.metadata.value + user_UUID = "userUuid" + tracker_version = "trackerVersion" + user_browser_version = "userBrowserVersion" + user_device_type = "userDeviceType" class LiveSessionSearchFilterSchema(BaseModel): value: Union[List[str], str] = Field(...) type: LiveFilterType = Field(...) - source: Optional[str] = Field(None) - operator: Literal[SearchEventOperator._is.value, - SearchEventOperator._contains.value] = Field(SearchEventOperator._contains.value) + source: Optional[str] = Field(default=None) + operator: Literal[SearchEventOperator._is, \ + SearchEventOperator._contains] = Field(default=SearchEventOperator._contains) @root_validator def validator(cls, values): - if values.get("type") is not None and values["type"] == LiveFilterType.metadata.value: + if values.get("type") is not None and values["type"] == LiveFilterType.metadata: assert values.get("source") is not None, "source should not be null for METADATA type" assert len(values.get("source")) > 0, "source should not be empty for METADATA type" return values @@ -1059,8 +1219,8 @@ class LiveSessionsSearchPayloadSchema(_PaginatedSchema): else: i += 1 for i in values["filters"]: - if i.get("type") == LiveFilterType.platform.value: - i["type"] = LiveFilterType.user_device_type.value + if i.get("type") == LiveFilterType.platform: + i["type"] = LiveFilterType.user_device_type if values.get("sort") is not None: if values["sort"].lower() == "startts": values["sort"] = "TIMESTAMP" @@ -1074,6 +1234,7 @@ class IntegrationType(str, Enum): github = "GITHUB" jira = "JIRA" slack = "SLACK" + ms_teams = "MSTEAMS" sentry = "SENTRY" bugsnag = "BUGSNAG" rollbar = "ROLLBAR" @@ -1121,3 +1282,80 @@ class SessionUpdateNoteSchema(SessionNoteSchema): break assert c > 0, "at least 1 value should be provided for update" return values + + +class WebhookType(str, Enum): + webhook = "webhook" + slack = "slack" + email = "email" + msteams = "msteams" + + +class SearchCardsSchema(_PaginatedSchema): + order: SortOrderType = Field(default=SortOrderType.desc) + shared_only: bool = Field(default=False) + mine_only: bool = Field(default=False) + query: Optional[str] = Field(default=None) + + class Config: + alias_generator = attribute_to_camel_case + + +class _ClickMapSearchEventRaw(_SessionSearchEventRaw): + type: Literal[EventType.location] = Field(...) + + +class FlatClickMapSessionsSearch(SessionsSearchPayloadSchema): + events: Optional[List[_ClickMapSearchEventRaw]] = Field([]) + filters: List[Union[SessionSearchFilterSchema, _ClickMapSearchEventRaw]] = Field([]) + + @root_validator(pre=True) + def transform(cls, values): + for f in values.get("filters", []): + if f.get("type") == FilterType.duration: + return values + values["filters"] = values.get("filters", []) + values["filters"].append({"value": [5000], "type": FilterType.duration, + "operator": SearchEventOperator._is, "filters": []}) + return values + + @root_validator() + def flat_to_original(cls, values): + if len(values["events"]) > 0: + return values + n_filters = [] + n_events = [] + for v in values.get("filters", []): + if isinstance(v, _ClickMapSearchEventRaw): + n_events.append(v) + else: + n_filters.append(v) + values["events"] = n_events + values["filters"] = n_filters + return values + + +class IssueAdvancedFilter(BaseModel): + type: IssueFilterType = Field(default=IssueFilterType._selector) + value: List[str] = Field(default=[]) + operator: SearchEventOperator = Field(default=SearchEventOperator._is) + + +class ClickMapFilterSchema(BaseModel): + value: List[Literal[IssueType.click_rage, IssueType.dead_click]] = Field(default=[]) + type: Literal[FilterType.issue] = Field(...) + operator: Literal[SearchEventOperator._is, MathOperator._equal] = Field(...) + # source: Optional[Union[ErrorSource, str]] = Field(default=None) + filters: List[IssueAdvancedFilter] = Field(default=[]) + + +class GetHeatmapPayloadSchema(BaseModel): + startDate: int = Field(TimeUTC.now(delta_days=-30)) + endDate: int = Field(TimeUTC.now()) + url: str = Field(...) + # issues: List[Literal[IssueType.click_rage, IssueType.dead_click]] = Field(default=[]) + filters: List[ClickMapFilterSchema] = Field(default=[]) + click_rage: bool = Field(default=False) + + class Config: + alias_generator = attribute_to_camel_case diff --git a/backend/Dockerfile b/backend/Dockerfile index 63b75dc61..c7606559e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.18-alpine3.15 AS prepare +FROM golang:1.18-alpine3.17 AS prepare RUN apk add --no-cache git openssh openssl-dev pkgconf gcc g++ make libc-dev bash librdkafka-dev cyrus-sasl cyrus-sasl-gssapiv2 krb5 @@ -19,11 +19,16 @@ RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o service -tags dynamic open FROM alpine AS entrypoint +ARG GIT_SHA +LABEL GIT_SHA=$GIT_SHA + RUN apk add --no-cache ca-certificates librdkafka-dev cyrus-sasl cyrus-sasl-gssapiv2 krb5 RUN adduser -u 1001 openreplay -D +ARG SERVICE_NAME ENV TZ=UTC \ - FS_ULIMIT=1000 \ + GIT_SHA=$GIT_SHA \ + FS_ULIMIT=10000 \ FS_DIR=/mnt/efs \ MAXMINDDB_FILE=/home/openreplay/geoip.mmdb \ UAPARSER_FILE=/home/openreplay/regexes.yaml \ @@ -71,11 +76,15 @@ ENV TZ=UTC \ BEACON_SIZE_LIMIT=1000000 \ USE_FAILOVER=false \ GROUP_STORAGE_FAILOVER=failover \ - TOPIC_STORAGE_FAILOVER=storage-failover + TOPIC_STORAGE_FAILOVER=storage-failover \ + SERVICE_NAME=$SERVICE_NAME \ + PROFILER_ENABLED=false \ + COMPRESSION_TYPE=zstd \ + CH_USERNAME="default" \ + CH_PASSWORD="" \ + CH_DATABASE="default" - -ARG SERVICE_NAME RUN if [ "$SERVICE_NAME" = "http" ]; then \ wget https://raw.githubusercontent.com/ua-parser/uap-core/master/regexes.yaml -O "$UAPARSER_FILE" &&\ wget https://static.openreplay.com/geoip/GeoLite2-Country.mmdb -O "$MAXMINDDB_FILE"; fi diff --git a/backend/build.sh b/backend/build.sh index 073f540df..95a833139 100755 --- a/backend/build.sh +++ b/backend/build.sh @@ -9,7 +9,8 @@ # Usage: IMAGE_TAG=latest DOCKER_REPO=myDockerHubID bash build.sh set -e -git_sha1=${IMAGE_TAG:-$(git rev-parse HEAD)} +git_sha=$(git rev-parse --short HEAD) +image_tag=${IMAGE_TAG:-git_sha} ee="false" check_prereq() { which docker || { @@ -22,9 +23,12 @@ check_prereq() { function build_service() { image="$1" echo "BUILDING $image" - docker build -t ${DOCKER_REPO:-'local'}/$image:${git_sha1} --platform linux/amd64 --build-arg SERVICE_NAME=$image . + docker build -t ${DOCKER_REPO:-'local'}/$image:${image_tag} --platform linux/amd64 --build-arg SERVICE_NAME=$image --build-arg GIT_SHA=$git_sha . [[ $PUSH_IMAGE -eq 1 ]] && { - docker push ${DOCKER_REPO:-'local'}/$image:${git_sha1} + docker push ${DOCKER_REPO:-'local'}/$image:${image_tag} + } + [[ $SIGN_IMAGE -eq 1 ]] && { + cosign sign --key $SIGN_KEY ${DOCKER_REPO:-'local'}/$image:${image_tag} } echo "Build completed for $image" return @@ -51,7 +55,7 @@ function build_api(){ for image in $(ls cmd); do build_service $image - echo "::set-output name=image::${DOCKER_REPO:-'local'}/$image:${git_sha1}" + echo "::set-output name=image::${DOCKER_REPO:-'local'}/$image:${image_tag}" done cd ../backend rm -rf ../${destination} diff --git a/backend/cmd/assets/main.go b/backend/cmd/assets/main.go index 5fdc85107..b05ecbe52 100644 --- a/backend/cmd/assets/main.go +++ b/backend/cmd/assets/main.go @@ -1,7 +1,6 @@ package main import ( - "context" "log" "os" "os/signal" @@ -12,29 +11,30 @@ import ( "openreplay/backend/internal/assets/cacher" config "openreplay/backend/internal/config/assets" "openreplay/backend/pkg/messages" - "openreplay/backend/pkg/monitoring" + "openreplay/backend/pkg/metrics" + assetsMetrics "openreplay/backend/pkg/metrics/assets" + "openreplay/backend/pkg/pprof" "openreplay/backend/pkg/queue" ) func main() { - metrics := monitoring.New("assets") + m := metrics.New() + m.Register(assetsMetrics.List()) log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) cfg := config.New() - - cacher := cacher.NewCacher(cfg, metrics) - - totalAssets, err := metrics.RegisterCounter("assets_total") - if err != nil { - log.Printf("can't create assets_total metric: %s", err) + if cfg.UseProfiler { + pprof.StartProfilingServer() } + cacher := cacher.NewCacher(cfg) + msgHandler := func(msg messages.Message) { switch m := msg.(type) { case *messages.AssetCache: cacher.CacheURL(m.SessionID(), m.URL) - totalAssets.Add(context.Background(), 1) + assetsMetrics.IncreaseProcessesSessions() // TODO: connect to "raw" topic in order to listen for JSException case *messages.JSException: sourceList, err := assets.ExtractJSExceptionSources(&m.Payload) diff --git a/backend/cmd/db/main.go b/backend/cmd/db/main.go index 8db029394..84b0d81ed 100644 --- a/backend/cmd/db/main.go +++ b/backend/cmd/db/main.go @@ -3,8 +3,6 @@ package main import ( "errors" "log" - types2 "openreplay/backend/pkg/db/types" - "openreplay/backend/pkg/queue/types" "os" "os/signal" "syscall" @@ -14,25 +12,31 @@ import ( "openreplay/backend/internal/db/datasaver" "openreplay/backend/pkg/db/cache" "openreplay/backend/pkg/db/postgres" + types2 "openreplay/backend/pkg/db/types" "openreplay/backend/pkg/handlers" custom2 "openreplay/backend/pkg/handlers/custom" - logger "openreplay/backend/pkg/log" "openreplay/backend/pkg/messages" - "openreplay/backend/pkg/monitoring" + "openreplay/backend/pkg/metrics" + databaseMetrics "openreplay/backend/pkg/metrics/database" + "openreplay/backend/pkg/pprof" "openreplay/backend/pkg/queue" "openreplay/backend/pkg/sessions" ) func main() { - metrics := monitoring.New("db") + m := metrics.New() + m.Register(databaseMetrics.List()) log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) cfg := db.New() + if cfg.UseProfiler { + pprof.StartProfilingServer() + } // Init database pg := cache.NewPGCache( - postgres.NewConn(cfg.Postgres, cfg.BatchQueueLimit, cfg.BatchSizeLimit, metrics), cfg.ProjectExpirationTimeoutMs) + postgres.NewConn(cfg.Postgres.String(), cfg.BatchQueueLimit, cfg.BatchSizeLimit), cfg.ProjectExpirationTimeoutMs) defer pg.Close() // HandlersFabric returns the list of message handlers we want to be applied to each incoming message. @@ -47,29 +51,20 @@ func main() { // Create handler's aggregator builderMap := sessions.NewBuilderMap(handlersFabric) - var producer types.Producer = nil - if cfg.UseQuickwit { - producer = queue.NewProducer(cfg.MessageSizeLimit, true) - defer producer.Close(15000) - } - // Init modules - saver := datasaver.New(pg, producer) + saver := datasaver.New(pg, cfg) saver.InitStats() - statsLogger := logger.NewQueueStats(cfg.LoggerTimeout) msgFilter := []int{messages.MsgMetadata, messages.MsgIssueEvent, messages.MsgSessionStart, messages.MsgSessionEnd, messages.MsgUserID, messages.MsgUserAnonymousID, messages.MsgClickEvent, messages.MsgIntegrationEvent, messages.MsgPerformanceTrackAggr, messages.MsgJSException, messages.MsgResourceTiming, - messages.MsgRawCustomEvent, messages.MsgCustomIssue, messages.MsgFetch, messages.MsgGraphQL, + messages.MsgCustomEvent, messages.MsgCustomIssue, messages.MsgFetch, messages.MsgNetworkRequest, messages.MsgGraphQL, messages.MsgStateAction, messages.MsgSetInputTarget, messages.MsgSetInputValue, messages.MsgCreateDocument, messages.MsgMouseClick, messages.MsgSetPageLocation, messages.MsgPageLoadTiming, messages.MsgPageRenderTiming} // Handler logic msgHandler := func(msg messages.Message) { - statsLogger.Collect(msg) - // Just save session data into db without additional checks if err := saver.InsertMessage(msg); err != nil { if !postgres.IsPkeyViolation(err) { @@ -122,8 +117,8 @@ func main() { consumer := queue.NewConsumer( cfg.GroupDB, []string{ - cfg.TopicRawWeb, - cfg.TopicAnalytics, + cfg.TopicRawWeb, // from tracker + cfg.TopicAnalytics, // from heuristics }, messages.NewMessageIterator(msgHandler, msgFilter, true), false, @@ -139,30 +134,34 @@ func main() { // Send collected batches to db commitDBUpdates := func() { - start := time.Now() - pg.CommitBatches() - pgDur := time.Now().Sub(start).Milliseconds() - - start = time.Now() + // Commit collected batches and bulks of information to PG + pg.Commit() + // Commit collected batches of information to CH if err := saver.CommitStats(); err != nil { log.Printf("Error on stats commit: %v", err) } - chDur := time.Now().Sub(start).Milliseconds() - log.Printf("commit duration(ms), pg: %d, ch: %d", pgDur, chDur) - + // Commit current position in queue if err := consumer.Commit(); err != nil { log.Printf("Error on consumer commit: %v", err) } } + for { select { case sig := <-sigchan: log.Printf("Caught signal %s: terminating\n", sig.String()) commitDBUpdates() + if err := pg.Close(); err != nil { + log.Printf("db.Close error: %s", err) + } + if err := saver.Close(); err != nil { + log.Printf("saver.Close error: %s", err) + } consumer.Close() os.Exit(0) case <-commitTick: commitDBUpdates() + builderMap.ClearOldSessions() case msg := <-consumer.Rebalanced(): log.Println(msg) default: diff --git a/backend/cmd/ender/main.go b/backend/cmd/ender/main.go index beb69bd42..da7ca9b89 100644 --- a/backend/cmd/ender/main.go +++ b/backend/cmd/ender/main.go @@ -2,32 +2,42 @@ package main import ( "log" - "openreplay/backend/internal/storage" "os" "os/signal" + "strings" "syscall" "time" "openreplay/backend/internal/config/ender" "openreplay/backend/internal/sessionender" + "openreplay/backend/internal/storage" "openreplay/backend/pkg/db/cache" "openreplay/backend/pkg/db/postgres" "openreplay/backend/pkg/intervals" - logger "openreplay/backend/pkg/log" "openreplay/backend/pkg/messages" - "openreplay/backend/pkg/monitoring" + "openreplay/backend/pkg/metrics" + databaseMetrics "openreplay/backend/pkg/metrics/database" + enderMetrics "openreplay/backend/pkg/metrics/ender" + "openreplay/backend/pkg/pprof" "openreplay/backend/pkg/queue" ) func main() { - log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) - metrics := monitoring.New("ender") - cfg := ender.New() + m := metrics.New() + m.Register(enderMetrics.List()) + m.Register(databaseMetrics.List()) - pg := cache.NewPGCache(postgres.NewConn(cfg.Postgres, 0, 0, metrics), cfg.ProjectExpirationTimeoutMs) + log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) + + cfg := ender.New() + if cfg.UseProfiler { + pprof.StartProfilingServer() + } + + pg := cache.NewPGCache(postgres.NewConn(cfg.Postgres.String(), 0, 0), cfg.ProjectExpirationTimeoutMs) defer pg.Close() - sessions, err := sessionender.New(metrics, intervals.EVENTS_SESSION_END_TIMEOUT, cfg.PartitionsNumber, logger.NewQueueStats(cfg.LoggerTimeout)) + sessions, err := sessionender.New(intervals.EVENTS_SESSION_END_TIMEOUT, cfg.PartitionsNumber) if err != nil { log.Printf("can't init ender service: %s", err) return @@ -37,7 +47,7 @@ func main() { consumer := queue.NewConsumer( cfg.GroupEnder, []string{cfg.TopicRawWeb}, - messages.NewMessageIterator( + messages.NewEnderMessageIterator( func(msg messages.Message) { sessions.UpdateSession(msg) }, []int{messages.MsgTimestamp}, false), @@ -62,6 +72,9 @@ func main() { consumer.Close() os.Exit(0) case <-tick: + failedSessionEnds := make(map[uint64]int64) + duplicatedSessionEnds := make(map[uint64]uint64) + // Find ended sessions and send notification to other services sessions.HandleEndedSessions(func(sessionID uint64, timestamp int64) bool { msg := &messages.SessionEnd{Timestamp: uint64(timestamp)} @@ -71,12 +84,17 @@ func main() { } newDuration, err := pg.InsertSessionEnd(sessionID, msg.Timestamp) if err != nil { + if strings.Contains(err.Error(), "integer out of range") { + // Skip session with broken duration + failedSessionEnds[sessionID] = timestamp + return true + } log.Printf("can't save sessionEnd to database, sessID: %d, err: %s", sessionID, err) return false } if currDuration == newDuration { - log.Printf("sessionEnd duplicate, sessID: %d, prevDur: %d, newDur: %d", sessionID, - currDuration, newDuration) + // Skip session end duplicate + duplicatedSessionEnds[sessionID] = currDuration return true } if cfg.UseEncryption { @@ -94,6 +112,12 @@ func main() { } return true }) + if len(failedSessionEnds) > 0 { + log.Println("sessions with wrong duration:", failedSessionEnds) + } + if len(duplicatedSessionEnds) > 0 { + log.Println("session end duplicates:", duplicatedSessionEnds) + } producer.Flush(cfg.ProducerTimeout) if err := consumer.CommitBack(intervals.EVENTS_BACK_COMMIT_GAP); err != nil { log.Printf("can't commit messages with offset: %s", err) diff --git a/backend/cmd/heuristics/main.go b/backend/cmd/heuristics/main.go index 0a5c77ea5..ac55b83bc 100644 --- a/backend/cmd/heuristics/main.go +++ b/backend/cmd/heuristics/main.go @@ -2,6 +2,7 @@ package main import ( "log" + "openreplay/backend/pkg/pprof" "os" "os/signal" "syscall" @@ -11,7 +12,6 @@ import ( "openreplay/backend/pkg/handlers" web2 "openreplay/backend/pkg/handlers/web" "openreplay/backend/pkg/intervals" - logger "openreplay/backend/pkg/log" "openreplay/backend/pkg/messages" "openreplay/backend/pkg/queue" "openreplay/backend/pkg/sessions" @@ -20,8 +20,10 @@ import ( func main() { log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) - // Load service configuration cfg := heuristics.New() + if cfg.UseProfiler { + pprof.StartProfilingServer() + } // HandlersFabric returns the list of message handlers we want to be applied to each incoming message. handlersFabric := func() []handlers.MessageProcessor { @@ -41,14 +43,10 @@ func main() { // Create handler's aggregator builderMap := sessions.NewBuilderMap(handlersFabric) - // Init logger - statsLogger := logger.NewQueueStats(cfg.LoggerTimeout) - // Init producer and consumer for data bus producer := queue.NewProducer(cfg.MessageSizeLimit, true) msgHandler := func(msg messages.Message) { - statsLogger.Collect(msg) builderMap.HandleMessage(msg) } diff --git a/backend/cmd/http/main.go b/backend/cmd/http/main.go index 7012917e4..83eedaf29 100644 --- a/backend/cmd/http/main.go +++ b/backend/cmd/http/main.go @@ -2,40 +2,48 @@ package main import ( "log" - "openreplay/backend/internal/config/http" - "openreplay/backend/internal/http/router" - "openreplay/backend/internal/http/server" - "openreplay/backend/internal/http/services" - "openreplay/backend/pkg/monitoring" "os" "os/signal" "syscall" + "openreplay/backend/internal/config/http" + "openreplay/backend/internal/http/router" + "openreplay/backend/internal/http/server" + "openreplay/backend/internal/http/services" "openreplay/backend/pkg/db/cache" "openreplay/backend/pkg/db/postgres" + "openreplay/backend/pkg/metrics" + databaseMetrics "openreplay/backend/pkg/metrics/database" + httpMetrics "openreplay/backend/pkg/metrics/http" + "openreplay/backend/pkg/pprof" "openreplay/backend/pkg/queue" ) func main() { - metrics := monitoring.New("http") + m := metrics.New() + m.Register(httpMetrics.List()) + m.Register(databaseMetrics.List()) log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) cfg := http.New() + if cfg.UseProfiler { + pprof.StartProfilingServer() + } // Connect to queue producer := queue.NewProducer(cfg.MessageSizeLimit, true) defer producer.Close(15000) // Connect to database - dbConn := cache.NewPGCache(postgres.NewConn(cfg.Postgres, 0, 0, metrics), 1000*60*20) + dbConn := cache.NewPGCache(postgres.NewConn(cfg.Postgres.String(), 0, 0), 1000*60*20) defer dbConn.Close() // Build all services services := services.New(cfg, producer, dbConn) // Init server's routes - router, err := router.NewRouter(cfg, services, metrics) + router, err := router.NewRouter(cfg, services) if err != nil { log.Fatalf("failed while creating engine: %s", err) } diff --git a/backend/cmd/integrations/main.go b/backend/cmd/integrations/main.go index 4f5a30dcf..3fa07ee9c 100644 --- a/backend/cmd/integrations/main.go +++ b/backend/cmd/integrations/main.go @@ -2,29 +2,34 @@ package main import ( "log" - config "openreplay/backend/internal/config/integrations" - "openreplay/backend/internal/integrations/clientManager" - "openreplay/backend/pkg/monitoring" - "time" - "os" "os/signal" "syscall" + "time" + config "openreplay/backend/internal/config/integrations" + "openreplay/backend/internal/integrations/clientManager" "openreplay/backend/pkg/db/postgres" "openreplay/backend/pkg/intervals" + "openreplay/backend/pkg/metrics" + databaseMetrics "openreplay/backend/pkg/metrics/database" + "openreplay/backend/pkg/pprof" "openreplay/backend/pkg/queue" "openreplay/backend/pkg/token" ) func main() { - metrics := monitoring.New("integrations") + m := metrics.New() + m.Register(databaseMetrics.List()) log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) cfg := config.New() + if cfg.UseProfiler { + pprof.StartProfilingServer() + } - pg := postgres.NewConn(cfg.PostgresURI, 0, 0, metrics) + pg := postgres.NewConn(cfg.Postgres.String(), 0, 0) defer pg.Close() tokenizer := token.NewTokenizer(cfg.TokenSecret) @@ -47,7 +52,7 @@ func main() { producer := queue.NewProducer(cfg.MessageSizeLimit, true) defer producer.Close(15000) - listener, err := postgres.NewIntegrationsListener(cfg.PostgresURI) + listener, err := postgres.NewIntegrationsListener(cfg.Postgres.String()) if err != nil { log.Printf("Postgres listener error: %v\n", err) log.Fatalf("Postgres listener error") diff --git a/backend/cmd/sink/main.go b/backend/cmd/sink/main.go index 03f11b200..4bbaeeee4 100644 --- a/backend/cmd/sink/main.go +++ b/backend/cmd/sink/main.go @@ -1,7 +1,8 @@ package main import ( - "context" + "bytes" + "encoding/binary" "log" "os" "os/signal" @@ -13,17 +14,22 @@ import ( "openreplay/backend/internal/sink/sessionwriter" "openreplay/backend/internal/storage" "openreplay/backend/pkg/messages" - "openreplay/backend/pkg/monitoring" + "openreplay/backend/pkg/metrics" + sinkMetrics "openreplay/backend/pkg/metrics/sink" + "openreplay/backend/pkg/pprof" "openreplay/backend/pkg/queue" "openreplay/backend/pkg/url/assets" ) func main() { - metrics := monitoring.New("sink") - + m := metrics.New() + m.Register(sinkMetrics.List()) log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) cfg := sink.New() + if cfg.UseProfiler { + pprof.StartProfilingServer() + } if _, err := os.Stat(cfg.FsDir); os.IsNotExist(err) { log.Fatalf("%v doesn't exist. %v", cfg.FsDir, err) @@ -34,26 +40,43 @@ func main() { producer := queue.NewProducer(cfg.MessageSizeLimit, true) defer producer.Close(cfg.ProducerCloseTimeout) rewriter := assets.NewRewriter(cfg.AssetsOrigin) - assetMessageHandler := assetscache.New(cfg, rewriter, producer, metrics) - + assetMessageHandler := assetscache.New(cfg, rewriter, producer) counter := storage.NewLogCounter() - // Session message metrics - totalMessages, err := metrics.RegisterCounter("messages_total") - if err != nil { - log.Printf("can't create messages_total metric: %s", err) - } - savedMessages, err := metrics.RegisterCounter("messages_saved") - if err != nil { - log.Printf("can't create messages_saved metric: %s", err) - } - messageSize, err := metrics.RegisterHistogram("messages_size") - if err != nil { - log.Printf("can't create messages_size metric: %s", err) - } + + var ( + sessionID uint64 + messageIndex = make([]byte, 8) + domBuffer = bytes.NewBuffer(make([]byte, 1024)) + devBuffer = bytes.NewBuffer(make([]byte, 1024)) + ) + + // Reset buffers + domBuffer.Reset() + devBuffer.Reset() msgHandler := func(msg messages.Message) { - // [METRICS] Increase the number of processed messages - totalMessages.Add(context.Background(), 1) + // Check batchEnd signal (nil message) + if msg == nil { + // Skip empty buffers + if domBuffer.Len() <= 0 && devBuffer.Len() <= 0 { + return + } + sinkMetrics.RecordWrittenBytes(float64(domBuffer.Len()), "dom") + sinkMetrics.RecordWrittenBytes(float64(devBuffer.Len()), "devtools") + + // Write buffered batches to the session + if err := writer.Write(sessionID, domBuffer.Bytes(), devBuffer.Bytes()); err != nil { + log.Printf("writer error: %s", err) + } + + // Prepare buffer for the next batch + domBuffer.Reset() + devBuffer.Reset() + sessionID = 0 + return + } + + sinkMetrics.IncreaseTotalMessages() // Send SessionEnd trigger to storage service if msg.TypeID() == messages.MsgSessionEnd { @@ -98,15 +121,61 @@ func main() { return } - // Write message to file - if err := writer.Write(msg); err != nil { - log.Printf("writer error: %s", err) - return + // Write message to the batch buffer + if sessionID == 0 { + sessionID = msg.SessionID() } - // [METRICS] Increase the number of written to the files messages and the message size - messageSize.Record(context.Background(), float64(len(msg.Encode()))) - savedMessages.Add(context.Background(), 1) + // Encode message index + binary.LittleEndian.PutUint64(messageIndex, msg.Meta().Index) + + var ( + n int + err error + ) + + // Add message to dom buffer + if messages.IsDOMType(msg.TypeID()) { + // Write message index + n, err = domBuffer.Write(messageIndex) + if err != nil { + log.Printf("domBuffer index write err: %s", err) + } + if n != len(messageIndex) { + log.Printf("domBuffer index not full write: %d/%d", n, len(messageIndex)) + } + // Write message body + n, err = domBuffer.Write(msg.Encode()) + if err != nil { + log.Printf("domBuffer message write err: %s", err) + } + if n != len(msg.Encode()) { + log.Printf("domBuffer message not full write: %d/%d", n, len(messageIndex)) + } + } + + // Add message to dev buffer + if !messages.IsDOMType(msg.TypeID()) || msg.TypeID() == messages.MsgTimestamp { + // Write message index + n, err = devBuffer.Write(messageIndex) + if err != nil { + log.Printf("devBuffer index write err: %s", err) + } + if n != len(messageIndex) { + log.Printf("devBuffer index not full write: %d/%d", n, len(messageIndex)) + } + // Write message body + n, err = devBuffer.Write(msg.Encode()) + if err != nil { + log.Printf("devBuffer message write err: %s", err) + } + if n != len(msg.Encode()) { + log.Printf("devBuffer message not full write: %d/%d", n, len(messageIndex)) + } + } + + sinkMetrics.IncreaseWrittenMessages() + sinkMetrics.RecordMessageSize(float64(len(msg.Encode()))) } consumer := queue.NewConsumer( @@ -114,7 +183,7 @@ func main() { []string{ cfg.TopicRawWeb, }, - messages.NewMessageIterator(msgHandler, nil, false), + messages.NewSinkMessageIterator(msgHandler, nil, false), false, cfg.MessageSizeLimit, ) diff --git a/backend/cmd/storage/main.go b/backend/cmd/storage/main.go index 251ce82e2..472324b95 100644 --- a/backend/cmd/storage/main.go +++ b/backend/cmd/storage/main.go @@ -11,20 +11,26 @@ import ( "openreplay/backend/internal/storage" "openreplay/backend/pkg/failover" "openreplay/backend/pkg/messages" - "openreplay/backend/pkg/monitoring" + "openreplay/backend/pkg/metrics" + storageMetrics "openreplay/backend/pkg/metrics/storage" + "openreplay/backend/pkg/pprof" "openreplay/backend/pkg/queue" - s3storage "openreplay/backend/pkg/storage" + cloud "openreplay/backend/pkg/storage" ) func main() { - metrics := monitoring.New("storage") + m := metrics.New() + m.Register(storageMetrics.List()) log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) cfg := config.New() + if cfg.UseProfiler { + pprof.StartProfilingServer() + } - s3 := s3storage.NewS3(cfg.S3Region, cfg.S3Bucket) - srv, err := storage.New(cfg, s3, metrics) + s3 := cloud.NewS3(cfg.S3Region, cfg.S3Bucket) + srv, err := storage.New(cfg, s3) if err != nil { log.Printf("can't init storage service: %s", err) return @@ -44,8 +50,8 @@ func main() { messages.NewMessageIterator( func(msg messages.Message) { sesEnd := msg.(*messages.SessionEnd) - if err := srv.UploadSessionFiles(sesEnd); err != nil { - log.Printf("can't find session: %d", msg.SessionID()) + if err := srv.Upload(sesEnd); err != nil { + log.Printf("upload session err: %s, sessID: %d", err, msg.SessionID()) sessionFinder.Find(msg.SessionID(), sesEnd.Timestamp) } // Log timestamp of last processed session @@ -54,7 +60,7 @@ func main() { []int{messages.MsgSessionEnd}, true, ), - true, + false, cfg.MessageSizeLimit, ) @@ -69,10 +75,15 @@ func main() { case sig := <-sigchan: log.Printf("Caught signal %v: terminating\n", sig) sessionFinder.Stop() + srv.Wait() consumer.Close() os.Exit(0) case <-counterTick: go counter.Print() + srv.Wait() + if err := consumer.Commit(); err != nil { + log.Printf("can't commit messages: %s", err) + } case msg := <-consumer.Rebalanced(): log.Println(msg) default: diff --git a/backend/go.mod b/backend/go.mod index 61d644a17..9633f2b18 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -8,7 +8,7 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/aws/aws-sdk-go v1.44.98 github.com/btcsuite/btcutil v1.0.2 - github.com/confluentinc/confluent-kafka-go v1.8.2 + github.com/confluentinc/confluent-kafka-go v1.9.2 github.com/elastic/go-elasticsearch/v7 v7.13.1 github.com/go-redis/redis v6.15.9+incompatible github.com/google/uuid v1.3.0 @@ -20,14 +20,11 @@ require ( github.com/klauspost/pgzip v1.2.5 github.com/oschwald/maxminddb-golang v1.7.0 github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.12.1 github.com/sethvargo/go-envconfig v0.7.0 github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce github.com/ua-parser/uap-go v0.0.0-20200325213135-e1c09f13e2fe - go.opentelemetry.io/otel v1.7.0 - go.opentelemetry.io/otel/exporters/prometheus v0.30.0 - go.opentelemetry.io/otel/metric v0.30.0 - go.opentelemetry.io/otel/sdk/metric v0.30.0 - golang.org/x/net v0.0.0-20220906165146-f3363e06e74c + golang.org/x/net v0.1.1-0.20221104162952-702349b0e862 google.golang.org/api v0.81.0 ) @@ -38,8 +35,6 @@ require ( cloud.google.com/go/storage v1.14.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/go-logr/logr v1.2.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.8 // indirect @@ -55,20 +50,19 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/paulmach/orb v0.7.1 // indirect github.com/pierrec/lz4/v4 v4.1.15 // indirect - github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/stretchr/testify v1.8.0 // indirect go.opencensus.io v0.23.0 // indirect - go.opentelemetry.io/otel/sdk v1.7.0 // indirect + go.opentelemetry.io/otel v1.7.0 // indirect go.opentelemetry.io/otel/trace v1.7.0 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect - golang.org/x/text v0.4.0 // indirect + golang.org/x/sys v0.1.0 // indirect + golang.org/x/text v0.7.0 // indirect golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect diff --git a/backend/go.sum b/backend/go.sum index c7abea25e..676cf479b 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -68,6 +68,9 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/actgardner/gogen-avro/v10 v10.1.0/go.mod h1:o+ybmVjEa27AAr35FRqU98DJu1fXES56uXniYFv4yDA= +github.com/actgardner/gogen-avro/v10 v10.2.1/go.mod h1:QUhjeHPchheYmMDni/Nx7VB0RsT/ee8YIgGY/xpEQgQ= +github.com/actgardner/gogen-avro/v9 v9.1.0/go.mod h1:nyTj6wPqDJoxM3qdnjcLv+EnMDSDFqE0qDpva2QRmKc= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -77,8 +80,6 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/aws/aws-sdk-go v1.44.98 h1:fX+NxebSdO/9T6DTNOLhpC+Vv6RNkKRfsMg0a7o/yBo= github.com/aws/aws-sdk-go v1.44.98/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -115,11 +116,12 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/confluentinc/confluent-kafka-go v1.8.2 h1:PBdbvYpyOdFLehj8j+9ba7FL4c4Moxn79gy9cYKxG5E= -github.com/confluentinc/confluent-kafka-go v1.8.2/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= +github.com/confluentinc/confluent-kafka-go v1.9.2 h1:gV/GxhMBUb03tFWkN+7kdhg+zf+QUM+wVkI9zwh770Q= +github.com/confluentinc/confluent-kafka-go v1.9.2/go.mod h1:ptXNqsuDfYbAE/LBW6pnwWZElUoWxHoV8E43DCrliyo= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -136,6 +138,10 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20= +github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= +github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= +github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -148,9 +154,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= @@ -195,10 +199,13 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -233,6 +240,7 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20211008130755-947d60d73cc0/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= @@ -250,12 +258,17 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hamba/avro v1.5.6/go.mod h1:3vNT0RLXXpFm2Tb/5KC71ZRJlOroggq1Rcitb6k4Fr8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/heetch/avro v0.3.1/go.mod h1:4xn38Oz/+hiEUTpbVfGVLfvOg0yKLlRP7Q9+gJJILgA= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/invopop/jsonschema v0.4.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -301,6 +314,11 @@ github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv github.com/jackc/puddle v1.2.2-0.20220404125616-4e959849469a h1:oH7y/b+q2BEerCnARr/HZc1NxOYbKSJor4MqQXlhh+s= github.com/jackc/puddle v1.2.2-0.20220404125616-4e959849469a/go.mod h1:ZQuO1Un86Xpe1ShKl08ERTzYhzWq+OvrvotbpeE3XO0= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= +github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= +github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ= +github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E= +github.com/jhump/protoreflect v1.12.0/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI4tTg7puQFKI= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -314,6 +332,7 @@ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/juju/qthttptest v0.1.1/go.mod h1:aTlAv8TYaflIiTDIQYzxnl1QdPjAg8Q8qJMErpKy6A4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -327,16 +346,23 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/linkedin/goavro v2.1.0+incompatible/go.mod h1:bBCwI2eGYpUI/4820s67MElg9tdeLbINjLjiM2xZFYM= +github.com/linkedin/goavro/v2 v2.10.0/go.mod h1:UgQUb2N/pmueQYH9bfqFioWxzYCZXSfF8Jw03O5sjqA= +github.com/linkedin/goavro/v2 v2.10.1/go.mod h1:UgQUb2N/pmueQYH9bfqFioWxzYCZXSfF8Jw03O5sjqA= +github.com/linkedin/goavro/v2 v2.11.1/go.mod h1:UgQUb2N/pmueQYH9bfqFioWxzYCZXSfF8Jw03O5sjqA= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -354,6 +380,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nrwiersma/avro-benchmarks v0.0.0-20210913175520-21aec48c8f76/go.mod h1:iKyFMidsk/sVYONJRE372sJuX/QTRPacU7imPqqsu7g= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -367,6 +394,7 @@ github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKf github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -395,11 +423,16 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a/go.mod h1:4r5QyqhjIWCcK8DO4KMclc5Iknq5qVBAlbYYzAbUScQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sethvargo/go-envconfig v0.7.0 h1:P/ljQXSRjgAgsnIripHs53Jg/uNVXu2FYQ9yLSDappA= github.com/sethvargo/go-envconfig v0.7.0/go.mod h1:00S1FAhRUuTNJazWBWcJGvEHOM+NO6DhoRMAOX7FY5o= @@ -420,6 +453,7 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -451,14 +485,6 @@ go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM= go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= -go.opentelemetry.io/otel/exporters/prometheus v0.30.0 h1:YXo5ZY5nofaEYMCMTTMaRH2cLDZB8+0UGuk5RwMfIo0= -go.opentelemetry.io/otel/exporters/prometheus v0.30.0/go.mod h1:qN5feW+0/d661KDtJuATEmHtw5bKBK7NSvNEP927zSs= -go.opentelemetry.io/otel/metric v0.30.0 h1:Hs8eQZ8aQgs0U49diZoaS6Uaxw3+bBE3lcMUKBFIk3c= -go.opentelemetry.io/otel/metric v0.30.0/go.mod h1:/ShZ7+TS4dHzDFmfi1kSXMhMVubNoP0oIaBp70J6UXU= -go.opentelemetry.io/otel/sdk v1.7.0 h1:4OmStpcKVOfvDOgCt7UriAPtKolwIhxpnSNI/yK+1B0= -go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU= -go.opentelemetry.io/otel/sdk/metric v0.30.0 h1:XTqQ4y3erR2Oj8xSAOL5ovO5011ch2ELg51z4fVkpME= -go.opentelemetry.io/otel/sdk/metric v0.30.0/go.mod h1:8AKFRi5HyvTR0RRty3paN1aMC9HMT+NzcEhw/BLkLX8= go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o= go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= @@ -539,6 +565,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -562,8 +589,8 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220906165146-f3363e06e74c h1:yKufUcDwucU5urd+50/Opbt4AYpqthk7wHpHok8f1lo= -golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.1-0.20221104162952-702349b0e862 h1:KrLJ+iz8J6j6VVr/OCfULAcK+xozUmWE43fKpMR4MlI= +golang.org/x/net v0.1.1-0.20221104162952-702349b0e862/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -651,7 +678,6 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -663,6 +689,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -675,8 +702,8 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -688,8 +715,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -729,6 +756,7 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -882,6 +910,7 @@ google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220503193339-ba3ae3f07e29/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I= google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= @@ -933,14 +962,19 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/avro.v0 v0.0.0-20171217001914-a730b5802183/go.mod h1:FvqrFXt+jCsyQibeRv4xxEJBL5iG2DDW5aeJwzDiq4A= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v1 v1.0.0/go.mod h1:CxwszS/Xz1C49Ucd2i6Zil5UToP1EmyrFhKaMVbg1mk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/httprequest.v1 v1.2.1/go.mod h1:x2Otw96yda5+8+6ZeWwHIJTFkEHWP/qP8pJOzqEtWPM= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/retry.v1 v1.0.3/go.mod h1:FJkXmWiMaAo7xB+xhvDF59zhfjDWyzmyAxiT4dB688g= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -948,11 +982,13 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/backend/internal/assets/cacher/cacher.go b/backend/internal/assets/cacher/cacher.go index b56c97d74..4b0353a9a 100644 --- a/backend/internal/assets/cacher/cacher.go +++ b/backend/internal/assets/cacher/cacher.go @@ -1,16 +1,13 @@ package cacher import ( - "context" "crypto/tls" "fmt" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" "io" "io/ioutil" - "log" "mime" "net/http" - "openreplay/backend/pkg/monitoring" + metrics "openreplay/backend/pkg/metrics/assets" "path/filepath" "strings" "time" @@ -25,30 +22,22 @@ import ( const MAX_CACHE_DEPTH = 5 type cacher struct { - timeoutMap *timeoutMap // Concurrency implemented - s3 *storage.S3 // AWS Docs: "These clients are safe to use concurrently." - httpClient *http.Client // Docs: "Clients are safe for concurrent use by multiple goroutines." - rewriter *assets.Rewriter // Read only - Errors chan error - sizeLimit int - downloadedAssets syncfloat64.Counter - requestHeaders map[string]string - workers *WorkerPool + timeoutMap *timeoutMap // Concurrency implemented + s3 *storage.S3 // AWS Docs: "These clients are safe to use concurrently." + httpClient *http.Client // Docs: "Clients are safe for concurrent use by multiple goroutines." + rewriter *assets.Rewriter // Read only + Errors chan error + sizeLimit int + requestHeaders map[string]string + workers *WorkerPool } func (c *cacher) CanCache() bool { return c.workers.CanAddTask() } -func NewCacher(cfg *config.Config, metrics *monitoring.Metrics) *cacher { +func NewCacher(cfg *config.Config) *cacher { rewriter := assets.NewRewriter(cfg.AssetsOrigin) - if metrics == nil { - log.Fatalf("metrics are empty") - } - downloadedAssets, err := metrics.RegisterCounter("assets_downloaded") - if err != nil { - log.Printf("can't create downloaded_assets metric: %s", err) - } c := &cacher{ timeoutMap: newTimeoutMap(), s3: storage.NewS3(cfg.AWSRegion, cfg.S3BucketAssets), @@ -59,11 +48,10 @@ func NewCacher(cfg *config.Config, metrics *monitoring.Metrics) *cacher { TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, }, - rewriter: rewriter, - Errors: make(chan error), - sizeLimit: cfg.AssetsSizeLimit, - downloadedAssets: downloadedAssets, - requestHeaders: cfg.AssetsRequestHeaders, + rewriter: rewriter, + Errors: make(chan error), + sizeLimit: cfg.AssetsSizeLimit, + requestHeaders: cfg.AssetsRequestHeaders, } c.workers = NewPool(64, c.CacheFile) return c @@ -75,6 +63,7 @@ func (c *cacher) CacheFile(task *Task) { func (c *cacher) cacheURL(t *Task) { t.retries-- + start := time.Now() req, _ := http.NewRequest("GET", t.requestURL, nil) if t.retries%2 == 0 { req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; rv:31.0) Gecko/20100101 Firefox/31.0") @@ -87,11 +76,12 @@ func (c *cacher) cacheURL(t *Task) { c.Errors <- errors.Wrap(err, t.urlContext) return } + metrics.RecordDownloadDuration(float64(time.Now().Sub(start).Milliseconds()), res.StatusCode) defer res.Body.Close() if res.StatusCode >= 400 { printErr := true - // Retry 403 error - if res.StatusCode == 403 && t.retries > 0 { + // Retry 403/503 errors + if (res.StatusCode == 403 || res.StatusCode == 503) && t.retries > 0 { c.workers.AddTask(t) printErr = false } @@ -122,12 +112,15 @@ func (c *cacher) cacheURL(t *Task) { } // TODO: implement in streams + start = time.Now() err = c.s3.Upload(strings.NewReader(strData), t.cachePath, contentType, false) if err != nil { + metrics.RecordUploadDuration(float64(time.Now().Sub(start).Milliseconds()), true) c.Errors <- errors.Wrap(err, t.urlContext) return } - c.downloadedAssets.Add(context.Background(), 1) + metrics.RecordUploadDuration(float64(time.Now().Sub(start).Milliseconds()), false) + metrics.IncreaseSavedSessions() if isCSS { if t.depth > 0 { diff --git a/backend/internal/config/assets/config.go b/backend/internal/config/assets/config.go index 1dfc8a4a8..399ee84f4 100644 --- a/backend/internal/config/assets/config.go +++ b/backend/internal/config/assets/config.go @@ -14,6 +14,7 @@ type Config struct { AssetsOrigin string `env:"ASSETS_ORIGIN,required"` AssetsSizeLimit int `env:"ASSETS_SIZE_LIMIT,required"` AssetsRequestHeaders map[string]string `env:"ASSETS_REQUEST_HEADERS"` + UseProfiler bool `env:"PROFILER_ENABLED,default=false"` } func New() *Config { diff --git a/backend/internal/config/common/config.go b/backend/internal/config/common/config.go index 658aeec62..c3246c10c 100644 --- a/backend/internal/config/common/config.go +++ b/backend/internal/config/common/config.go @@ -1,5 +1,7 @@ package common +import "strings" + type Config struct { ConfigFilePath string `env:"CONFIG_FILE_PATH"` MessageSizeLimit int `env:"QUEUE_MESSAGE_SIZE_LIMIT,default=1048576"` @@ -12,3 +14,21 @@ type Configer interface { func (c *Config) GetConfigPath() string { return c.ConfigFilePath } + +type Postgres struct { + Postgres string `env:"POSTGRES_STRING,required"` + ApplicationName string `env:"SERVICE_NAME,default='worker'"` +} + +func (cfg *Postgres) String() string { + str := cfg.Postgres + if !strings.Contains(cfg.Postgres, "application_name") { + if strings.Contains(cfg.Postgres, "?") { + str += "&" + } else { + str += "?" + } + str += "application_name=" + cfg.ApplicationName + } + return str +} diff --git a/backend/internal/config/db/config.go b/backend/internal/config/db/config.go index 715d9ff8e..d56137e77 100644 --- a/backend/internal/config/db/config.go +++ b/backend/internal/config/db/config.go @@ -8,7 +8,7 @@ import ( type Config struct { common.Config - Postgres string `env:"POSTGRES_STRING,required"` + common.Postgres ProjectExpirationTimeoutMs int64 `env:"PROJECT_EXPIRATION_TIMEOUT_MS,default=1200000"` LoggerTimeout int `env:"LOG_QUEUE_STATS_INTERVAL_SEC,required"` GroupDB string `env:"GROUP_DB,required"` @@ -18,6 +18,8 @@ type Config struct { BatchQueueLimit int `env:"DB_BATCH_QUEUE_LIMIT,required"` BatchSizeLimit int `env:"DB_BATCH_SIZE_LIMIT,required"` UseQuickwit bool `env:"QUICKWIT_ENABLED,default=false"` + QuickwitTopic string `env:"QUICKWIT_TOPIC,default=saas-quickwit"` + UseProfiler bool `env:"PROFILER_ENABLED,default=false"` } func New() *Config { diff --git a/backend/internal/config/ender/config.go b/backend/internal/config/ender/config.go index fb315acbe..23e927270 100644 --- a/backend/internal/config/ender/config.go +++ b/backend/internal/config/ender/config.go @@ -7,7 +7,7 @@ import ( type Config struct { common.Config - Postgres string `env:"POSTGRES_STRING,required"` + common.Postgres ProjectExpirationTimeoutMs int64 `env:"PROJECT_EXPIRATION_TIMEOUT_MS,default=1200000"` GroupEnder string `env:"GROUP_ENDER,required"` LoggerTimeout int `env:"LOG_QUEUE_STATS_INTERVAL_SEC,required"` @@ -15,6 +15,7 @@ type Config struct { ProducerTimeout int `env:"PRODUCER_TIMEOUT,default=2000"` PartitionsNumber int `env:"PARTITIONS_NUMBER,required"` UseEncryption bool `env:"USE_ENCRYPTION,default=false"` + UseProfiler bool `env:"PROFILER_ENABLED,default=false"` } func New() *Config { diff --git a/backend/internal/config/heuristics/config.go b/backend/internal/config/heuristics/config.go index fbe0eab81..6552944a3 100644 --- a/backend/internal/config/heuristics/config.go +++ b/backend/internal/config/heuristics/config.go @@ -13,6 +13,7 @@ type Config struct { TopicRawWeb string `env:"TOPIC_RAW_WEB,required"` TopicRawIOS string `env:"TOPIC_RAW_IOS,required"` ProducerTimeout int `env:"PRODUCER_TIMEOUT,default=2000"` + UseProfiler bool `env:"PROFILER_ENABLED,default=false"` } func New() *Config { diff --git a/backend/internal/config/http/config.go b/backend/internal/config/http/config.go index 3c30d3980..a4af87bd3 100644 --- a/backend/internal/config/http/config.go +++ b/backend/internal/config/http/config.go @@ -9,6 +9,7 @@ import ( type Config struct { common.Config + common.Postgres HTTPHost string `env:"HTTP_HOST,default="` HTTPPort string `env:"HTTP_PORT,required"` HTTPTimeout time.Duration `env:"HTTP_TIMEOUT,default=60s"` @@ -19,10 +20,10 @@ type Config struct { FileSizeLimit int64 `env:"FILE_SIZE_LIMIT,default=10000000"` AWSRegion string `env:"AWS_REGION,required"` S3BucketIOSImages string `env:"S3_BUCKET_IOS_IMAGES,required"` - Postgres string `env:"POSTGRES_STRING,required"` TokenSecret string `env:"TOKEN_SECRET,required"` UAParserFile string `env:"UAPARSER_FILE,required"` MaxMinDBFile string `env:"MAXMINDDB_FILE,required"` + UseProfiler bool `env:"PROFILER_ENABLED,default=false"` WorkerID uint16 } diff --git a/backend/internal/config/integrations/config.go b/backend/internal/config/integrations/config.go index c61377b8c..5687d710f 100644 --- a/backend/internal/config/integrations/config.go +++ b/backend/internal/config/integrations/config.go @@ -7,9 +7,10 @@ import ( type Config struct { common.Config + common.Postgres TopicAnalytics string `env:"TOPIC_ANALYTICS,required"` - PostgresURI string `env:"POSTGRES_STRING,required"` TokenSecret string `env:"TOKEN_SECRET,required"` + UseProfiler bool `env:"PROFILER_ENABLED,default=false"` } func New() *Config { diff --git a/backend/internal/config/sink/config.go b/backend/internal/config/sink/config.go index 53e3517a4..802dfb54b 100644 --- a/backend/internal/config/sink/config.go +++ b/backend/internal/config/sink/config.go @@ -21,6 +21,8 @@ type Config struct { ProducerCloseTimeout int `env:"PRODUCER_CLOSE_TIMEOUT,default=15000"` CacheThreshold int64 `env:"CACHE_THRESHOLD,default=5"` CacheExpiration int64 `env:"CACHE_EXPIRATION,default=120"` + CacheBlackList string `env:"CACHE_BLACK_LIST,default="` + UseProfiler bool `env:"PROFILER_ENABLED,default=false"` } func New() *Config { diff --git a/backend/internal/config/storage/config.go b/backend/internal/config/storage/config.go index 6083f0249..ca4ff8028 100644 --- a/backend/internal/config/storage/config.go +++ b/backend/internal/config/storage/config.go @@ -21,6 +21,8 @@ type Config struct { ProducerCloseTimeout int `env:"PRODUCER_CLOSE_TIMEOUT,default=15000"` UseFailover bool `env:"USE_FAILOVER,default=false"` MaxFileSize int64 `env:"MAX_FILE_SIZE,default=524288000"` + UseSort bool `env:"USE_SESSION_SORT,default=true"` + UseProfiler bool `env:"PROFILER_ENABLED,default=false"` } func New() *Config { diff --git a/backend/internal/db/datasaver/messages.go b/backend/internal/db/datasaver/messages.go index 621659c6d..12e7152b4 100644 --- a/backend/internal/db/datasaver/messages.go +++ b/backend/internal/db/datasaver/messages.go @@ -36,14 +36,11 @@ func (mi *Saver) InsertMessage(msg Message) error { // Unique Web messages case *PageEvent: - mi.sendToFTS(msg, sessionID) return mi.pg.InsertWebPageEvent(sessionID, m) - case *FetchEvent: - mi.sendToFTS(msg, sessionID) - return mi.pg.InsertWebFetchEvent(sessionID, m) - case *GraphQLEvent: - mi.sendToFTS(msg, sessionID) - return mi.pg.InsertWebGraphQLEvent(sessionID, m) + case *NetworkRequest: + return mi.pg.InsertWebNetworkRequest(sessionID, m) + case *GraphQL: + return mi.pg.InsertWebGraphQL(sessionID, m) case *JSException: return mi.pg.InsertWebJSException(m) case *IntegrationEvent: diff --git a/backend/internal/db/datasaver/saver.go b/backend/internal/db/datasaver/saver.go index d41756a4d..2a356d120 100644 --- a/backend/internal/db/datasaver/saver.go +++ b/backend/internal/db/datasaver/saver.go @@ -1,6 +1,7 @@ package datasaver import ( + "openreplay/backend/internal/config/db" "openreplay/backend/pkg/db/cache" "openreplay/backend/pkg/queue/types" ) @@ -10,6 +11,6 @@ type Saver struct { producer types.Producer } -func New(pg *cache.PGCache, producer types.Producer) *Saver { - return &Saver{pg: pg, producer: producer} +func New(pg *cache.PGCache, _ *db.Config) *Saver { + return &Saver{pg: pg, producer: nil} } diff --git a/backend/internal/db/datasaver/stats.go b/backend/internal/db/datasaver/stats.go index b523ecdbe..c7daeb3dc 100644 --- a/backend/internal/db/datasaver/stats.go +++ b/backend/internal/db/datasaver/stats.go @@ -23,3 +23,7 @@ func (si *Saver) InsertStats(session *Session, msg Message) error { func (si *Saver) CommitStats() error { return nil } + +func (si *Saver) Close() error { + return nil +} diff --git a/backend/internal/http/router/handlers-ios.go b/backend/internal/http/router/handlers-ios.go index e0fc73b6f..b11918d54 100644 --- a/backend/internal/http/router/handlers-ios.go +++ b/backend/internal/http/router/handlers-ios.go @@ -22,28 +22,28 @@ func (e *Router) startSessionHandlerIOS(w http.ResponseWriter, r *http.Request) req := &StartIOSSessionRequest{} if r.Body == nil { - ResponseWithError(w, http.StatusBadRequest, errors.New("request body is empty")) + ResponseWithError(w, http.StatusBadRequest, errors.New("request body is empty"), startTime, r.URL.Path, 0) return } body := http.MaxBytesReader(w, r.Body, e.cfg.JsonSizeLimit) defer body.Close() if err := json.NewDecoder(body).Decode(req); err != nil { - ResponseWithError(w, http.StatusBadRequest, err) + ResponseWithError(w, http.StatusBadRequest, err, startTime, r.URL.Path, 0) return } if req.ProjectKey == nil { - ResponseWithError(w, http.StatusForbidden, errors.New("ProjectKey value required")) + ResponseWithError(w, http.StatusForbidden, errors.New("ProjectKey value required"), startTime, r.URL.Path, 0) return } p, err := e.services.Database.GetProjectByKey(*req.ProjectKey) if err != nil { if postgres.IsNoRowsErr(err) { - ResponseWithError(w, http.StatusNotFound, errors.New("Project doesn't exist or is not active")) + ResponseWithError(w, http.StatusNotFound, errors.New("Project doesn't exist or is not active"), startTime, r.URL.Path, 0) } else { - ResponseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging + ResponseWithError(w, http.StatusInternalServerError, err, startTime, r.URL.Path, 0) // TODO: send error here only on staging } return } @@ -53,18 +53,18 @@ func (e *Router) startSessionHandlerIOS(w http.ResponseWriter, r *http.Request) if err != nil { // Starting the new one dice := byte(rand.Intn(100)) // [0, 100) if dice >= p.SampleRate { - ResponseWithError(w, http.StatusForbidden, errors.New("cancel")) + ResponseWithError(w, http.StatusForbidden, errors.New("cancel"), startTime, r.URL.Path, 0) return } ua := e.services.UaParser.ParseFromHTTPRequest(r) if ua == nil { - ResponseWithError(w, http.StatusForbidden, errors.New("browser not recognized")) + ResponseWithError(w, http.StatusForbidden, errors.New("browser not recognized"), startTime, r.URL.Path, 0) return } sessionID, err := e.services.Flaker.Compose(uint64(startTime.UnixMilli())) if err != nil { - ResponseWithError(w, http.StatusInternalServerError, err) + ResponseWithError(w, http.StatusInternalServerError, err, startTime, r.URL.Path, 0) return } // TODO: if EXPIRED => send message for two sessions association @@ -94,22 +94,24 @@ func (e *Router) startSessionHandlerIOS(w http.ResponseWriter, r *http.Request) UserUUID: userUUID, SessionID: strconv.FormatUint(tokenData.ID, 10), BeaconSizeLimit: e.cfg.BeaconSizeLimit, - }) + }, startTime, r.URL.Path, 0) } func (e *Router) pushMessagesHandlerIOS(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() sessionData, err := e.services.Tokenizer.ParseFromHTTPRequest(r) if err != nil { - ResponseWithError(w, http.StatusUnauthorized, err) + ResponseWithError(w, http.StatusUnauthorized, err, startTime, r.URL.Path, 0) return } e.pushMessages(w, r, sessionData.ID, e.cfg.TopicRawIOS) } func (e *Router) pushLateMessagesHandlerIOS(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() sessionData, err := e.services.Tokenizer.ParseFromHTTPRequest(r) if err != nil && err != token.EXPIRED { - ResponseWithError(w, http.StatusUnauthorized, err) + ResponseWithError(w, http.StatusUnauthorized, err, startTime, r.URL.Path, 0) return } // Check timestamps here? @@ -117,16 +119,17 @@ func (e *Router) pushLateMessagesHandlerIOS(w http.ResponseWriter, r *http.Reque } func (e *Router) imagesUploadHandlerIOS(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() log.Printf("recieved imagerequest") sessionData, err := e.services.Tokenizer.ParseFromHTTPRequest(r) if err != nil { // Should accept expired token? - ResponseWithError(w, http.StatusUnauthorized, err) + ResponseWithError(w, http.StatusUnauthorized, err, startTime, r.URL.Path, 0) return } if r.Body == nil { - ResponseWithError(w, http.StatusBadRequest, errors.New("request body is empty")) + ResponseWithError(w, http.StatusBadRequest, errors.New("request body is empty"), startTime, r.URL.Path, 0) return } r.Body = http.MaxBytesReader(w, r.Body, e.cfg.FileSizeLimit) @@ -134,21 +137,21 @@ func (e *Router) imagesUploadHandlerIOS(w http.ResponseWriter, r *http.Request) err = r.ParseMultipartForm(1e6) // ~1Mb if err == http.ErrNotMultipart || err == http.ErrMissingBoundary { - ResponseWithError(w, http.StatusUnsupportedMediaType, err) + ResponseWithError(w, http.StatusUnsupportedMediaType, err, startTime, r.URL.Path, 0) return // } else if err == multipart.ErrMessageTooLarge // if non-files part exceeds 10 MB } else if err != nil { - ResponseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging + ResponseWithError(w, http.StatusInternalServerError, err, startTime, r.URL.Path, 0) // TODO: send error here only on staging return } if r.MultipartForm == nil { - ResponseWithError(w, http.StatusInternalServerError, errors.New("Multipart not parsed")) + ResponseWithError(w, http.StatusInternalServerError, errors.New("Multipart not parsed"), startTime, r.URL.Path, 0) return } if len(r.MultipartForm.Value["projectKey"]) == 0 { - ResponseWithError(w, http.StatusBadRequest, errors.New("projectKey parameter missing")) // status for missing/wrong parameter? + ResponseWithError(w, http.StatusBadRequest, errors.New("projectKey parameter missing"), startTime, r.URL.Path, 0) // status for missing/wrong parameter? return } diff --git a/backend/internal/http/router/handlers-web.go b/backend/internal/http/router/handlers-web.go index 08c4c75c5..52a37b7f0 100644 --- a/backend/internal/http/router/handlers-web.go +++ b/backend/internal/http/router/handlers-web.go @@ -3,18 +3,17 @@ package router import ( "encoding/json" "errors" - "github.com/Masterminds/semver" - "go.opentelemetry.io/otel/attribute" "io" "log" "math/rand" "net/http" - "openreplay/backend/internal/http/uuid" - "openreplay/backend/pkg/flakeid" "strconv" "time" + "github.com/Masterminds/semver" + "openreplay/backend/internal/http/uuid" "openreplay/backend/pkg/db/postgres" + "openreplay/backend/pkg/flakeid" . "openreplay/backend/pkg/messages" "openreplay/backend/pkg/token" ) @@ -28,13 +27,6 @@ func (e *Router) readBody(w http.ResponseWriter, r *http.Request, limit int64) ( if err != nil { return nil, err } - - reqSize := len(bodyBytes) - e.requestSize.Record( - r.Context(), - float64(reqSize), - []attribute.KeyValue{attribute.String("method", r.URL.Path)}..., - ) return bodyBytes, nil } @@ -56,40 +48,43 @@ func getSessionTimestamp(req *StartSessionRequest, startTimeMili int64) (ts uint func (e *Router) startSessionHandlerWeb(w http.ResponseWriter, r *http.Request) { startTime := time.Now() + bodySize := 0 // Check request body if r.Body == nil { - ResponseWithError(w, http.StatusBadRequest, errors.New("request body is empty")) + ResponseWithError(w, http.StatusBadRequest, errors.New("request body is empty"), startTime, r.URL.Path, bodySize) return } bodyBytes, err := e.readBody(w, r, e.cfg.JsonSizeLimit) if err != nil { log.Printf("error while reading request body: %s", err) - ResponseWithError(w, http.StatusRequestEntityTooLarge, err) + ResponseWithError(w, http.StatusRequestEntityTooLarge, err, startTime, r.URL.Path, bodySize) return } + bodySize = len(bodyBytes) // Parse request body req := &StartSessionRequest{} if err := json.Unmarshal(bodyBytes, req); err != nil { - ResponseWithError(w, http.StatusBadRequest, err) + ResponseWithError(w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) return } // Handler's logic if req.ProjectKey == nil { - ResponseWithError(w, http.StatusForbidden, errors.New("ProjectKey value required")) + ResponseWithError(w, http.StatusForbidden, errors.New("ProjectKey value required"), startTime, r.URL.Path, bodySize) return } p, err := e.services.Database.GetProjectByKey(*req.ProjectKey) if err != nil { if postgres.IsNoRowsErr(err) { - ResponseWithError(w, http.StatusNotFound, errors.New("project doesn't exist or capture limit has been reached")) + ResponseWithError(w, http.StatusNotFound, + errors.New("project doesn't exist or capture limit has been reached"), startTime, r.URL.Path, bodySize) } else { log.Printf("can't get project by key: %s", err) - ResponseWithError(w, http.StatusInternalServerError, errors.New("can't get project by key")) + ResponseWithError(w, http.StatusInternalServerError, errors.New("can't get project by key"), startTime, r.URL.Path, bodySize) } return } @@ -99,19 +94,19 @@ func (e *Router) startSessionHandlerWeb(w http.ResponseWriter, r *http.Request) if err != nil || req.Reset { // Starting the new one dice := byte(rand.Intn(100)) // [0, 100) if dice >= p.SampleRate { - ResponseWithError(w, http.StatusForbidden, errors.New("cancel")) + ResponseWithError(w, http.StatusForbidden, errors.New("cancel"), startTime, r.URL.Path, bodySize) return } ua := e.services.UaParser.ParseFromHTTPRequest(r) if ua == nil { - ResponseWithError(w, http.StatusForbidden, errors.New("browser not recognized")) + ResponseWithError(w, http.StatusForbidden, errors.New("browser not recognized"), startTime, r.URL.Path, bodySize) return } startTimeMili := startTime.UnixMilli() sessionID, err := e.services.Flaker.Compose(uint64(startTimeMili)) if err != nil { - ResponseWithError(w, http.StatusInternalServerError, err) + ResponseWithError(w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize) return } // TODO: if EXPIRED => send message for two sessions association @@ -152,37 +147,44 @@ func (e *Router) startSessionHandlerWeb(w http.ResponseWriter, r *http.Request) } } + // Save information about session beacon size + e.addBeaconSize(tokenData.ID, p.BeaconSize) + ResponseWithJSON(w, &StartSessionResponse{ Token: e.services.Tokenizer.Compose(*tokenData), UserUUID: userUUID, SessionID: strconv.FormatUint(tokenData.ID, 10), ProjectID: strconv.FormatUint(uint64(p.ProjectID), 10), - BeaconSizeLimit: e.cfg.BeaconSizeLimit, + BeaconSizeLimit: e.getBeaconSize(tokenData.ID), StartTimestamp: int64(flakeid.ExtractTimestamp(tokenData.ID)), Delay: tokenData.Delay, - }) + }, startTime, r.URL.Path, bodySize) } func (e *Router) pushMessagesHandlerWeb(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + // Check authorization sessionData, err := e.services.Tokenizer.ParseFromHTTPRequest(r) if err != nil { - ResponseWithError(w, http.StatusUnauthorized, err) + ResponseWithError(w, http.StatusUnauthorized, err, startTime, r.URL.Path, bodySize) return } // Check request body if r.Body == nil { - ResponseWithError(w, http.StatusBadRequest, errors.New("request body is empty")) + ResponseWithError(w, http.StatusBadRequest, errors.New("request body is empty"), startTime, r.URL.Path, bodySize) return } - bodyBytes, err := e.readBody(w, r, e.cfg.BeaconSizeLimit) + bodyBytes, err := e.readBody(w, r, e.getBeaconSize(sessionData.ID)) if err != nil { log.Printf("error while reading request body: %s", err) - ResponseWithError(w, http.StatusRequestEntityTooLarge, err) + ResponseWithError(w, http.StatusRequestEntityTooLarge, err, startTime, r.URL.Path, bodySize) return } + bodySize = len(bodyBytes) // Send processed messages to queue as array of bytes // TODO: check bytes for nonsense crap @@ -191,39 +193,43 @@ func (e *Router) pushMessagesHandlerWeb(w http.ResponseWriter, r *http.Request) log.Printf("can't send processed messages to queue: %s", err) } - w.WriteHeader(http.StatusOK) + ResponseOK(w, startTime, r.URL.Path, bodySize) } func (e *Router) notStartedHandlerWeb(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + // Check request body if r.Body == nil { - ResponseWithError(w, http.StatusBadRequest, errors.New("request body is empty")) + ResponseWithError(w, http.StatusBadRequest, errors.New("request body is empty"), startTime, r.URL.Path, bodySize) return } bodyBytes, err := e.readBody(w, r, e.cfg.JsonSizeLimit) if err != nil { log.Printf("error while reading request body: %s", err) - ResponseWithError(w, http.StatusRequestEntityTooLarge, err) + ResponseWithError(w, http.StatusRequestEntityTooLarge, err, startTime, r.URL.Path, bodySize) return } + bodySize = len(bodyBytes) // Parse request body req := &NotStartedRequest{} if err := json.Unmarshal(bodyBytes, req); err != nil { - ResponseWithError(w, http.StatusBadRequest, err) + ResponseWithError(w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) return } // Handler's logic if req.ProjectKey == nil { - ResponseWithError(w, http.StatusForbidden, errors.New("projectKey value required")) + ResponseWithError(w, http.StatusForbidden, errors.New("projectKey value required"), startTime, r.URL.Path, bodySize) return } ua := e.services.UaParser.ParseFromHTTPRequest(r) // TODO?: insert anyway if ua == nil { - ResponseWithError(w, http.StatusForbidden, errors.New("browser not recognized")) + ResponseWithError(w, http.StatusForbidden, errors.New("browser not recognized"), startTime, r.URL.Path, bodySize) return } country := e.services.GeoIP.ExtractISOCodeFromHTTPRequest(r) @@ -245,5 +251,5 @@ func (e *Router) notStartedHandlerWeb(w http.ResponseWriter, r *http.Request) { log.Printf("Unable to insert Unstarted Session: %v\n", err) } - w.WriteHeader(http.StatusOK) + ResponseOK(w, startTime, r.URL.Path, bodySize) } diff --git a/backend/internal/http/router/handlers.go b/backend/internal/http/router/handlers.go index c36fdd668..425177341 100644 --- a/backend/internal/http/router/handlers.go +++ b/backend/internal/http/router/handlers.go @@ -6,9 +6,11 @@ import ( "io/ioutil" "log" "net/http" + "time" ) func (e *Router) pushMessages(w http.ResponseWriter, r *http.Request, sessionID uint64, topicName string) { + start := time.Now() body := http.MaxBytesReader(w, r.Body, e.cfg.BeaconSizeLimit) defer body.Close() @@ -21,7 +23,7 @@ func (e *Router) pushMessages(w http.ResponseWriter, r *http.Request, sessionID reader, err = gzip.NewReader(body) if err != nil { - ResponseWithError(w, http.StatusInternalServerError, err) // TODO: stage-dependent response + ResponseWithError(w, http.StatusInternalServerError, err, start, r.URL.Path, 0) // TODO: stage-dependent response return } //log.Println("Gzip reader init", reader) @@ -32,7 +34,7 @@ func (e *Router) pushMessages(w http.ResponseWriter, r *http.Request, sessionID //log.Println("Reader after switch:", reader) buf, err := ioutil.ReadAll(reader) if err != nil { - ResponseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging + ResponseWithError(w, http.StatusInternalServerError, err, start, r.URL.Path, 0) // TODO: send error here only on staging return } e.services.Producer.Produce(topicName, sessionID, buf) // What if not able to send? diff --git a/backend/internal/http/router/response.go b/backend/internal/http/router/response.go index 0b4725419..b66b7c563 100644 --- a/backend/internal/http/router/response.go +++ b/backend/internal/http/router/response.go @@ -4,21 +4,44 @@ import ( "encoding/json" "log" "net/http" + "time" + + metrics "openreplay/backend/pkg/metrics/http" ) -func ResponseWithJSON(w http.ResponseWriter, res interface{}) { +func recordMetrics(requestStart time.Time, url string, code, bodySize int) { + if bodySize > 0 { + metrics.RecordRequestSize(float64(bodySize), url, code) + } + metrics.IncreaseTotalRequests() + metrics.RecordRequestDuration(float64(time.Now().Sub(requestStart).Milliseconds()), url, code) +} + +func ResponseOK(w http.ResponseWriter, requestStart time.Time, url string, bodySize int) { + w.WriteHeader(http.StatusOK) + recordMetrics(requestStart, url, http.StatusOK, bodySize) +} + +func ResponseWithJSON(w http.ResponseWriter, res interface{}, requestStart time.Time, url string, bodySize int) { body, err := json.Marshal(res) if err != nil { log.Println(err) } w.Header().Set("Content-Type", "application/json") w.Write(body) + recordMetrics(requestStart, url, http.StatusOK, bodySize) } -func ResponseWithError(w http.ResponseWriter, code int, err error) { - type response struct { - Error string `json:"error"` +type response struct { + Error string `json:"error"` +} + +func ResponseWithError(w http.ResponseWriter, code int, err error, requestStart time.Time, url string, bodySize int) { + body, err := json.Marshal(&response{err.Error()}) + if err != nil { + log.Println(err) } w.WriteHeader(code) - ResponseWithJSON(w, &response{err.Error()}) + w.Write(body) + recordMetrics(requestStart, url, code, bodySize) } diff --git a/backend/internal/http/router/router.go b/backend/internal/http/router/router.go index 6d31b7396..6cd7efe79 100644 --- a/backend/internal/http/router/router.go +++ b/backend/internal/http/router/router.go @@ -1,47 +1,85 @@ package router import ( - "context" "fmt" - "github.com/gorilla/mux" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" "log" "net/http" + "sync" + "time" + + "github.com/gorilla/mux" http3 "openreplay/backend/internal/config/http" http2 "openreplay/backend/internal/http/services" "openreplay/backend/internal/http/util" - "openreplay/backend/pkg/monitoring" - "time" ) +type BeaconSize struct { + size int64 + time time.Time +} + type Router struct { router *mux.Router cfg *http3.Config services *http2.ServicesBuilder - requestSize syncfloat64.Histogram - requestDuration syncfloat64.Histogram - totalRequests syncfloat64.Counter + mutex *sync.RWMutex + beaconSizeCache map[uint64]*BeaconSize // Cache for session's beaconSize } -func NewRouter(cfg *http3.Config, services *http2.ServicesBuilder, metrics *monitoring.Metrics) (*Router, error) { +func NewRouter(cfg *http3.Config, services *http2.ServicesBuilder) (*Router, error) { switch { case cfg == nil: return nil, fmt.Errorf("config is empty") case services == nil: return nil, fmt.Errorf("services is empty") - case metrics == nil: - return nil, fmt.Errorf("metrics is empty") } e := &Router{ - cfg: cfg, - services: services, + cfg: cfg, + services: services, + mutex: &sync.RWMutex{}, + beaconSizeCache: make(map[uint64]*BeaconSize), } - e.initMetrics(metrics) e.init() + go e.clearBeaconSizes() return e, nil } +func (e *Router) addBeaconSize(sessionID uint64, size int64) { + if size <= 0 { + return + } + e.mutex.Lock() + defer e.mutex.Unlock() + e.beaconSizeCache[sessionID] = &BeaconSize{ + size: size, + time: time.Now(), + } +} + +func (e *Router) getBeaconSize(sessionID uint64) int64 { + e.mutex.RLock() + defer e.mutex.RUnlock() + if beaconSize, ok := e.beaconSizeCache[sessionID]; ok { + beaconSize.time = time.Now() + return beaconSize.size + } + return e.cfg.BeaconSizeLimit +} + +func (e *Router) clearBeaconSizes() { + for { + time.Sleep(time.Minute * 2) + now := time.Now() + e.mutex.Lock() + for sid, bs := range e.beaconSizeCache { + if now.Sub(bs.time) > time.Minute*3 { + delete(e.beaconSizeCache, sid) + } + } + e.mutex.Unlock() + } +} + func (e *Router) init() { e.router = mux.NewRouter() @@ -68,22 +106,6 @@ func (e *Router) init() { e.router.Use(e.corsMiddleware) } -func (e *Router) initMetrics(metrics *monitoring.Metrics) { - var err error - e.requestSize, err = metrics.RegisterHistogram("requests_body_size") - if err != nil { - log.Printf("can't create requests_body_size metric: %s", err) - } - e.requestDuration, err = metrics.RegisterHistogram("requests_duration") - if err != nil { - log.Printf("can't create requests_duration metric: %s", err) - } - e.totalRequests, err = metrics.RegisterCounter("requests_total") - if err != nil { - log.Printf("can't create requests_total metric: %s", err) - } -} - func (e *Router) root(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } @@ -102,17 +124,8 @@ func (e *Router) corsMiddleware(next http.Handler) http.Handler { log.Printf("Request: %v - %v ", r.Method, util.SafeString(r.URL.Path)) - requestStart := time.Now() - // Serve request next.ServeHTTP(w, r) - - metricsContext, _ := context.WithTimeout(context.Background(), time.Millisecond*100) - e.totalRequests.Add(metricsContext, 1) - e.requestDuration.Record(metricsContext, - float64(time.Now().Sub(requestStart).Milliseconds()), - []attribute.KeyValue{attribute.String("method", r.URL.Path)}..., - ) }) } diff --git a/backend/internal/sessionender/ender.go b/backend/internal/sessionender/ender.go index dbd3eb901..e1ddb0ffe 100644 --- a/backend/internal/sessionender/ender.go +++ b/backend/internal/sessionender/ender.go @@ -1,14 +1,11 @@ package sessionender import ( - "context" - "fmt" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" "log" - log2 "openreplay/backend/pkg/log" - "openreplay/backend/pkg/messages" - "openreplay/backend/pkg/monitoring" "time" + + "openreplay/backend/pkg/messages" + "openreplay/backend/pkg/metrics/ender" ) // EndedSessionHandler handler for ended sessions @@ -24,40 +21,21 @@ type session struct { // SessionEnder updates timestamp of last message for each session type SessionEnder struct { - timeout int64 - sessions map[uint64]*session // map[sessionID]session - timeCtrl *timeController - activeSessions syncfloat64.UpDownCounter - totalSessions syncfloat64.Counter - stats log2.QueueStats + timeout int64 + sessions map[uint64]*session // map[sessionID]session + timeCtrl *timeController } -func New(metrics *monitoring.Metrics, timeout int64, parts int, stats log2.QueueStats) (*SessionEnder, error) { - if metrics == nil { - return nil, fmt.Errorf("metrics module is empty") - } - activeSessions, err := metrics.RegisterUpDownCounter("sessions_active") - if err != nil { - return nil, fmt.Errorf("can't register session.active metric: %s", err) - } - totalSessions, err := metrics.RegisterCounter("sessions_total") - if err != nil { - return nil, fmt.Errorf("can't register session.total metric: %s", err) - } - +func New(timeout int64, parts int) (*SessionEnder, error) { return &SessionEnder{ - timeout: timeout, - sessions: make(map[uint64]*session), - timeCtrl: NewTimeController(parts), - activeSessions: activeSessions, - totalSessions: totalSessions, - stats: stats, + timeout: timeout, + sessions: make(map[uint64]*session), + timeCtrl: NewTimeController(parts), }, nil } // UpdateSession save timestamp for new sessions and update for existing sessions func (se *SessionEnder) UpdateSession(msg messages.Message) { - se.stats.Collect(msg) var ( sessionID = msg.Meta().SessionID() batchTimestamp = msg.Meta().Batch().Timestamp() @@ -78,8 +56,8 @@ func (se *SessionEnder) UpdateSession(msg messages.Message) { lastUserTime: msgTimestamp, // last timestamp from user's machine isEnded: false, } - se.activeSessions.Add(context.Background(), 1) - se.totalSessions.Add(context.Background(), 1) + ender.IncreaseActiveSessions() + ender.IncreaseTotalSessions() return } // Keep the highest user's timestamp for correct session duration value @@ -104,7 +82,8 @@ func (se *SessionEnder) HandleEndedSessions(handler EndedSessionHandler) { sess.isEnded = true if handler(sessID, sess.lastUserTime) { delete(se.sessions, sessID) - se.activeSessions.Add(context.Background(), -1) + ender.DecreaseActiveSessions() + ender.IncreaseClosedSessions() removedSessions++ } else { log.Printf("sessID: %d, userTime: %d", sessID, sess.lastUserTime) diff --git a/backend/internal/sink/assetscache/assets.go b/backend/internal/sink/assetscache/assets.go index aa2dccbba..387ee5c92 100644 --- a/backend/internal/sink/assetscache/assets.go +++ b/backend/internal/sink/assetscache/assets.go @@ -1,19 +1,19 @@ package assetscache import ( - "context" "crypto/md5" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" "io" "log" "net/url" - "openreplay/backend/internal/config/sink" - "openreplay/backend/pkg/messages" - "openreplay/backend/pkg/monitoring" - "openreplay/backend/pkg/queue/types" - "openreplay/backend/pkg/url/assets" + metrics "openreplay/backend/pkg/metrics/sink" + "strings" "sync" "time" + + "openreplay/backend/internal/config/sink" + "openreplay/backend/pkg/messages" + "openreplay/backend/pkg/queue/types" + "openreplay/backend/pkg/url/assets" ) type CachedAsset struct { @@ -22,57 +22,37 @@ type CachedAsset struct { } type AssetsCache struct { - mutex sync.RWMutex - cfg *sink.Config - rewriter *assets.Rewriter - producer types.Producer - cache map[string]*CachedAsset - totalAssets syncfloat64.Counter - cachedAssets syncfloat64.Counter - skippedAssets syncfloat64.Counter - assetSize syncfloat64.Histogram - assetDuration syncfloat64.Histogram + mutex sync.RWMutex + cfg *sink.Config + rewriter *assets.Rewriter + producer types.Producer + cache map[string]*CachedAsset + blackList []string // use "example.com" to filter all domains or ".example.com" to filter only third-level domain } -func New(cfg *sink.Config, rewriter *assets.Rewriter, producer types.Producer, metrics *monitoring.Metrics) *AssetsCache { - // Assets metrics - totalAssets, err := metrics.RegisterCounter("assets_total") - if err != nil { - log.Printf("can't create assets_total metric: %s", err) - } - cachedAssets, err := metrics.RegisterCounter("assets_cached") - if err != nil { - log.Printf("can't create assets_cached metric: %s", err) - } - skippedAssets, err := metrics.RegisterCounter("assets_skipped") - if err != nil { - log.Printf("can't create assets_skipped metric: %s", err) - } - assetSize, err := metrics.RegisterHistogram("asset_size") - if err != nil { - log.Printf("can't create asset_size metric: %s", err) - } - assetDuration, err := metrics.RegisterHistogram("asset_duration") - if err != nil { - log.Printf("can't create asset_duration metric: %s", err) - } +func New(cfg *sink.Config, rewriter *assets.Rewriter, producer types.Producer) *AssetsCache { assetsCache := &AssetsCache{ - cfg: cfg, - rewriter: rewriter, - producer: producer, - cache: make(map[string]*CachedAsset, 64), - totalAssets: totalAssets, - cachedAssets: cachedAssets, - skippedAssets: skippedAssets, - assetSize: assetSize, - assetDuration: assetDuration, + cfg: cfg, + rewriter: rewriter, + producer: producer, + cache: make(map[string]*CachedAsset, 64), + blackList: make([]string, 0), + } + // Parse black list for cache layer + if len(cfg.CacheBlackList) > 0 { + blackList := strings.Split(cfg.CacheBlackList, ",") + for _, domain := range blackList { + if len(domain) > 0 { + assetsCache.blackList = append(assetsCache.blackList, domain) + } + } } go assetsCache.cleaner() return assetsCache } func (e *AssetsCache) cleaner() { - cleanTick := time.Tick(time.Minute * 30) + cleanTick := time.Tick(time.Minute * 3) for { select { case <-cleanTick: @@ -93,11 +73,28 @@ func (e *AssetsCache) clearCache() { if int64(now.Sub(cache.ts).Minutes()) > e.cfg.CacheExpiration { deleted++ delete(e.cache, id) + metrics.DecreaseCachedAssets() } } log.Printf("cache cleaner: deleted %d/%d assets", deleted, cacheSize) } +func (e *AssetsCache) shouldSkipAsset(baseURL string) bool { + if len(e.blackList) == 0 { + return false + } + host, err := parseHost(baseURL) + if err != nil { + return false + } + for _, blackHost := range e.blackList { + if strings.Contains(host, blackHost) { + return true + } + } + return false +} + func (e *AssetsCache) ParseAssets(msg messages.Message) messages.Message { switch m := msg.(type) { case *messages.SetNodeAttributeURLBased: @@ -110,6 +107,9 @@ func (e *AssetsCache) ParseAssets(msg messages.Message) messages.Message { newMsg.SetMeta(msg.Meta()) return newMsg } else if m.Name == "style" { + if e.shouldSkipAsset(m.BaseURL) { + return msg + } newMsg := &messages.SetNodeAttribute{ ID: m.ID, Name: m.Name, @@ -119,6 +119,9 @@ func (e *AssetsCache) ParseAssets(msg messages.Message) messages.Message { return newMsg } case *messages.SetCSSDataURLBased: + if e.shouldSkipAsset(m.BaseURL) { + return msg + } newMsg := &messages.SetCSSData{ ID: m.ID, Data: e.handleCSS(m.SessionID(), m.BaseURL, m.Data), @@ -126,6 +129,9 @@ func (e *AssetsCache) ParseAssets(msg messages.Message) messages.Message { newMsg.SetMeta(msg.Meta()) return newMsg case *messages.CSSInsertRuleURLBased: + if e.shouldSkipAsset(m.BaseURL) { + return msg + } newMsg := &messages.CSSInsertRule{ ID: m.ID, Index: m.Index, @@ -134,6 +140,9 @@ func (e *AssetsCache) ParseAssets(msg messages.Message) messages.Message { newMsg.SetMeta(msg.Meta()) return newMsg case *messages.AdoptedSSReplaceURLBased: + if e.shouldSkipAsset(m.BaseURL) { + return msg + } newMsg := &messages.AdoptedSSReplace{ SheetID: m.SheetID, Text: e.handleCSS(m.SessionID(), m.BaseURL, m.Text), @@ -141,6 +150,9 @@ func (e *AssetsCache) ParseAssets(msg messages.Message) messages.Message { newMsg.SetMeta(msg.Meta()) return newMsg case *messages.AdoptedSSInsertRuleURLBased: + if e.shouldSkipAsset(m.BaseURL) { + return msg + } newMsg := &messages.AdoptedSSInsertRule{ SheetID: m.SheetID, Index: m.Index, @@ -180,13 +192,20 @@ func (e *AssetsCache) handleURL(sessionID uint64, baseURL string, urlVal string) } } +func parseHost(baseURL string) (string, error) { + u, err := url.Parse(baseURL) + if err != nil { + return "", err + } + return u.Scheme + "://" + u.Host + "/", nil +} + func (e *AssetsCache) handleCSS(sessionID uint64, baseURL string, css string) string { - ctx := context.Background() - e.totalAssets.Add(ctx, 1) + metrics.IncreaseTotalAssets() // Try to find asset in cache h := md5.New() // Cut first part of url (scheme + host) - u, err := url.Parse(baseURL) + justUrl, err := parseHost(baseURL) if err != nil { log.Printf("can't parse url: %s, err: %s", baseURL, err) if e.cfg.CacheAssets { @@ -194,7 +213,6 @@ func (e *AssetsCache) handleCSS(sessionID uint64, baseURL string, css string) st } return e.getRewrittenCSS(sessionID, baseURL, css) } - justUrl := u.Scheme + "://" + u.Host + "/" // Calculate hash sum of url + css io.WriteString(h, justUrl) io.WriteString(h, css) @@ -205,7 +223,7 @@ func (e *AssetsCache) handleCSS(sessionID uint64, baseURL string, css string) st e.mutex.RUnlock() if ok { if int64(time.Now().Sub(cachedAsset.ts).Minutes()) < e.cfg.CacheExpiration { - e.skippedAssets.Add(ctx, 1) + metrics.IncreaseSkippedAssets() return cachedAsset.msg } } @@ -217,8 +235,8 @@ func (e *AssetsCache) handleCSS(sessionID uint64, baseURL string, css string) st start := time.Now() res := e.getRewrittenCSS(sessionID, baseURL, css) duration := time.Now().Sub(start).Milliseconds() - e.assetSize.Record(ctx, float64(len(res))) - e.assetDuration.Record(ctx, float64(duration)) + metrics.RecordAssetSize(float64(len(res))) + metrics.RecordProcessAssetDuration(float64(duration)) // Save asset to cache if we spent more than threshold if duration > e.cfg.CacheThreshold { e.mutex.Lock() @@ -227,7 +245,7 @@ func (e *AssetsCache) handleCSS(sessionID uint64, baseURL string, css string) st ts: time.Now(), } e.mutex.Unlock() - e.cachedAssets.Add(ctx, 1) + metrics.IncreaseCachedAssets() } // Return rewritten asset return res diff --git a/backend/internal/sink/sessionwriter/session.go b/backend/internal/sink/sessionwriter/session.go index 8cf8881de..cd74db27d 100644 --- a/backend/internal/sink/sessionwriter/session.go +++ b/backend/internal/sink/sessionwriter/session.go @@ -1,19 +1,15 @@ package sessionwriter import ( - "encoding/binary" "fmt" "strconv" "sync" - - "openreplay/backend/pkg/messages" ) type Session struct { lock *sync.Mutex dom *File dev *File - index []byte updated bool } @@ -37,40 +33,22 @@ func NewSession(sessID uint64, workDir string, bufSize int) (*Session, error) { lock: &sync.Mutex{}, dom: dom, dev: dev, - index: make([]byte, 8), updated: false, }, nil } -func (s *Session) Write(msg messages.Message) error { +func (s *Session) Write(domBuffer, devBuffer []byte) error { s.lock.Lock() defer s.lock.Unlock() - // Encode message index - binary.LittleEndian.PutUint64(s.index, msg.Meta().Index) - - // Write message to dom.mob file - if messages.IsDOMType(msg.TypeID()) { - // Write message index - if err := s.dom.Write(s.index); err != nil { - return err - } - // Write message body - if err := s.dom.Write(msg.Encode()); err != nil { - return err - } + // Write dom buffer to the file (file buffer) + if err := s.dom.Write(domBuffer); err != nil { + return err } - s.updated = true - // Write message to dev.mob file - if !messages.IsDOMType(msg.TypeID()) || msg.TypeID() == messages.MsgTimestamp { - // Write message index - if err := s.dev.Write(s.index); err != nil { - return err - } - // Write message body - if err := s.dev.Write(msg.Encode()); err != nil { - return err - } + + // Write dev buffer to the file (file buffer) + if err := s.dev.Write(devBuffer); err != nil { + return err } return nil } diff --git a/backend/internal/sink/sessionwriter/writer.go b/backend/internal/sink/sessionwriter/writer.go index 7da1ae878..b7d2a2c68 100644 --- a/backend/internal/sink/sessionwriter/writer.go +++ b/backend/internal/sink/sessionwriter/writer.go @@ -5,8 +5,6 @@ import ( "log" "sync" "time" - - "openreplay/backend/pkg/messages" ) type SessionWriter struct { @@ -35,11 +33,8 @@ func NewWriter(filesLimit uint16, workingDir string, fileBuffer int, syncTimeout return w } -func (w *SessionWriter) Write(msg messages.Message) (err error) { - var ( - sess *Session - sid = msg.SessionID() - ) +func (w *SessionWriter) Write(sid uint64, domBuffer, devBuffer []byte) (err error) { + var sess *Session // Load session sessObj, ok := w.sessions.Load(sid) @@ -65,7 +60,7 @@ func (w *SessionWriter) Write(msg messages.Message) (err error) { } // Write data to session - return sess.Write(msg) + return sess.Write(domBuffer, devBuffer) } func (w *SessionWriter) sync(sid uint64) error { diff --git a/backend/internal/storage/storage.go b/backend/internal/storage/storage.go index 12a37183f..1e2507163 100644 --- a/backend/internal/storage/storage.go +++ b/backend/internal/storage/storage.go @@ -2,233 +2,268 @@ package storage import ( "bytes" - "context" "fmt" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" "log" - config "openreplay/backend/internal/config/storage" - "openreplay/backend/pkg/flakeid" - "openreplay/backend/pkg/messages" - "openreplay/backend/pkg/monitoring" - "openreplay/backend/pkg/storage" "os" "strconv" + "strings" + "sync" "time" + + config "openreplay/backend/internal/config/storage" + "openreplay/backend/pkg/messages" + metrics "openreplay/backend/pkg/metrics/storage" + "openreplay/backend/pkg/storage" + + gzip "github.com/klauspost/pgzip" ) +type FileType string + +const ( + DOM FileType = "/dom.mob" + DEV FileType = "/devtools.mob" +) + +func (t FileType) String() string { + if t == DOM { + return "dom" + } + return "devtools" +} + +type Task struct { + id string + doms *bytes.Buffer + dome *bytes.Buffer + dev *bytes.Buffer +} + type Storage struct { cfg *config.Config s3 *storage.S3 startBytes []byte - - totalSessions syncfloat64.Counter - sessionDOMSize syncfloat64.Histogram - sessionDevtoolsSize syncfloat64.Histogram - readingDOMTime syncfloat64.Histogram - readingTime syncfloat64.Histogram - archivingTime syncfloat64.Histogram + tasks chan *Task + ready chan struct{} } -func New(cfg *config.Config, s3 *storage.S3, metrics *monitoring.Metrics) (*Storage, error) { +func New(cfg *config.Config, s3 *storage.S3) (*Storage, error) { switch { case cfg == nil: return nil, fmt.Errorf("config is empty") case s3 == nil: return nil, fmt.Errorf("s3 storage is empty") } - // Create metrics - totalSessions, err := metrics.RegisterCounter("sessions_total") - if err != nil { - log.Printf("can't create sessions_total metric: %s", err) + newStorage := &Storage{ + cfg: cfg, + s3: s3, + startBytes: make([]byte, cfg.FileSplitSize), + tasks: make(chan *Task, 1), + ready: make(chan struct{}), } - sessionDOMSize, err := metrics.RegisterHistogram("sessions_size") - if err != nil { - log.Printf("can't create session_size metric: %s", err) - } - sessionDevtoolsSize, err := metrics.RegisterHistogram("sessions_dt_size") - if err != nil { - log.Printf("can't create sessions_dt_size metric: %s", err) - } - readingTime, err := metrics.RegisterHistogram("reading_duration") - if err != nil { - log.Printf("can't create reading_duration metric: %s", err) - } - archivingTime, err := metrics.RegisterHistogram("archiving_duration") - if err != nil { - log.Printf("can't create archiving_duration metric: %s", err) - } - return &Storage{ - cfg: cfg, - s3: s3, - startBytes: make([]byte, cfg.FileSplitSize), - totalSessions: totalSessions, - sessionDOMSize: sessionDOMSize, - sessionDevtoolsSize: sessionDevtoolsSize, - readingTime: readingTime, - archivingTime: archivingTime, - }, nil + go newStorage.worker() + return newStorage, nil } -func (s *Storage) UploadSessionFiles(msg *messages.SessionEnd) error { - if err := s.uploadKey(msg.SessionID(), "/dom.mob", true, 5, msg.EncryptionKey); err != nil { +func (s *Storage) Wait() { + <-s.ready +} + +func (s *Storage) Upload(msg *messages.SessionEnd) (err error) { + // Generate file path + sessionID := strconv.FormatUint(msg.SessionID(), 10) + filePath := s.cfg.FSDir + "/" + sessionID + // Prepare sessions + newTask := &Task{ + id: sessionID, + } + wg := &sync.WaitGroup{} + wg.Add(2) + go func() { + if prepErr := s.prepareSession(filePath, DOM, newTask); prepErr != nil { + err = fmt.Errorf("prepareSession DOM err: %s", prepErr) + } + wg.Done() + }() + go func() { + if prepErr := s.prepareSession(filePath, DEV, newTask); prepErr != nil { + err = fmt.Errorf("prepareSession DEV err: %s", prepErr) + } + wg.Done() + }() + wg.Wait() + if err != nil { + if strings.Contains(err.Error(), "big file") { + log.Printf("%s, sess: %d", err, msg.SessionID()) + return nil + } return err } - if err := s.uploadKey(msg.SessionID(), "/devtools.mob", false, 4, msg.EncryptionKey); err != nil { - log.Printf("can't find devtools for session: %d, err: %s", msg.SessionID(), err) - } + // Send new task to worker + s.tasks <- newTask + // Unload worker + <-s.ready return nil } -// TODO: make a bit cleaner. -// TODO: Of course, I'll do! -func (s *Storage) uploadKey(sessID uint64, suffix string, shouldSplit bool, retryCount int, encryptionKey string) error { - if retryCount <= 0 { - return nil - } - start := time.Now() - fileName := strconv.FormatUint(sessID, 10) - mobFileName := fileName - if suffix == "/devtools.mob" { - mobFileName += "devtools" - } - filePath := s.cfg.FSDir + "/" + mobFileName - +func (s *Storage) openSession(filePath string, tp FileType) ([]byte, error) { // Check file size before download into memory info, err := os.Stat(filePath) - if err == nil { - if info.Size() > s.cfg.MaxFileSize { - log.Printf("big file, size: %d, session: %d", info.Size(), sessID) - return nil - } + if err == nil && info.Size() > s.cfg.MaxFileSize { + return nil, fmt.Errorf("big file, size: %d", info.Size()) } - file, err := os.Open(filePath) + // Read file into memory + raw, err := os.ReadFile(filePath) if err != nil { - return fmt.Errorf("File open error: %v; sessID: %s, part: %d, sessStart: %s\n", - err, fileName, sessID%16, - time.UnixMilli(int64(flakeid.ExtractTimestamp(sessID))), - ) + return nil, err } - defer file.Close() - - var fileSize int64 = 0 - fileInfo, err := file.Stat() + if !s.cfg.UseSort { + return raw, nil + } + start := time.Now() + res, err := s.sortSessionMessages(raw) if err != nil { - log.Printf("can't get file info: %s", err) - } else { - fileSize = fileInfo.Size() + return nil, fmt.Errorf("can't sort session, err: %s", err) } + metrics.RecordSessionSortDuration(float64(time.Now().Sub(start).Milliseconds()), tp.String()) + return res, nil +} - var encryptedData []byte - fileName += suffix - if shouldSplit { - nRead, err := file.Read(s.startBytes) - if err != nil { - log.Printf("File read error: %s; sessID: %s, part: %d, sessStart: %s", - err, - fileName, - sessID%16, - time.UnixMilli(int64(flakeid.ExtractTimestamp(sessID))), - ) - time.AfterFunc(s.cfg.RetryTimeout, func() { - s.uploadKey(sessID, suffix, shouldSplit, retryCount-1, encryptionKey) - }) +func (s *Storage) sortSessionMessages(raw []byte) ([]byte, error) { + // Parse messages, sort by index and save result into slice of bytes + unsortedMessages, err := messages.SplitMessages(raw) + if err != nil { + log.Printf("can't sort session, err: %s", err) + return raw, nil + } + return messages.MergeMessages(raw, messages.SortMessages(unsortedMessages)), nil +} + +func (s *Storage) prepareSession(path string, tp FileType, task *Task) error { + // Open mob file + if tp == DEV { + path += "devtools" + } + startRead := time.Now() + mob, err := s.openSession(path, tp) + if err != nil { + return err + } + metrics.RecordSessionSize(float64(len(mob)), tp.String()) + metrics.RecordSessionReadDuration(float64(time.Now().Sub(startRead).Milliseconds()), tp.String()) + + // Encode and compress session + if tp == DEV { + start := time.Now() + task.dev = s.compressSession(mob) + metrics.RecordSessionCompressDuration(float64(time.Now().Sub(start).Milliseconds()), tp.String()) + } else { + if len(mob) <= s.cfg.FileSplitSize { + start := time.Now() + task.doms = s.compressSession(mob) + metrics.RecordSessionCompressDuration(float64(time.Now().Sub(start).Milliseconds()), tp.String()) return nil } - s.readingTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds())) - - start = time.Now() - // Encrypt session file if we have encryption key - if encryptionKey != "" { - encryptedData, err = EncryptData(s.startBytes[:nRead], []byte(encryptionKey)) - if err != nil { - log.Printf("can't encrypt data: %s", err) - encryptedData = s.startBytes[:nRead] - } - } else { - encryptedData = s.startBytes[:nRead] - } - // Compress and save to s3 - startReader := bytes.NewBuffer(encryptedData) - if err := s.s3.Upload(s.gzipFile(startReader), fileName+"s", "application/octet-stream", true); err != nil { - log.Fatalf("Storage: start upload failed. %v\n", err) - } - // TODO: fix possible error (if we read less then FileSplitSize) - if nRead == s.cfg.FileSplitSize { - restPartSize := fileSize - int64(nRead) - fileData := make([]byte, restPartSize) - nRead, err = file.Read(fileData) - if err != nil { - log.Printf("File read error: %s; sessID: %s, part: %d, sessStart: %s", - err, - fileName, - sessID%16, - time.UnixMilli(int64(flakeid.ExtractTimestamp(sessID))), - ) - return nil - } - if int64(nRead) != restPartSize { - log.Printf("can't read the rest part of file") - } - - // Encrypt session file if we have encryption key - if encryptionKey != "" { - encryptedData, err = EncryptData(fileData, []byte(encryptionKey)) - if err != nil { - log.Printf("can't encrypt data: %s", err) - encryptedData = fileData - } - } else { - encryptedData = fileData - } - // Compress and save to s3 - endReader := bytes.NewBuffer(encryptedData) - if err := s.s3.Upload(s.gzipFile(endReader), fileName+"e", "application/octet-stream", true); err != nil { - log.Fatalf("Storage: end upload failed. %v\n", err) - } - } - s.archivingTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds())) - } else { - start = time.Now() - fileData := make([]byte, fileSize) - nRead, err := file.Read(fileData) - if err != nil { - log.Printf("File read error: %s; sessID: %s, part: %d, sessStart: %s", - err, - fileName, - sessID%16, - time.UnixMilli(int64(flakeid.ExtractTimestamp(sessID))), - ) - return nil - } - if int64(nRead) != fileSize { - log.Printf("can't read the rest part of file") - } - - // Encrypt session file if we have encryption key - if encryptionKey != "" { - encryptedData, err = EncryptData(fileData, []byte(encryptionKey)) - if err != nil { - log.Printf("can't encrypt data: %s", err) - encryptedData = fileData - } - } else { - encryptedData = fileData - } - endReader := bytes.NewBuffer(encryptedData) - if err := s.s3.Upload(s.gzipFile(endReader), fileName, "application/octet-stream", true); err != nil { - log.Fatalf("Storage: end upload failed. %v\n", err) - } - s.archivingTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds())) + wg := &sync.WaitGroup{} + wg.Add(2) + var firstPart, secondPart int64 + go func() { + start := time.Now() + task.doms = s.compressSession(mob[:s.cfg.FileSplitSize]) + firstPart = time.Now().Sub(start).Milliseconds() + wg.Done() + }() + go func() { + start := time.Now() + task.dome = s.compressSession(mob[s.cfg.FileSplitSize:]) + secondPart = time.Now().Sub(start).Milliseconds() + wg.Done() + }() + wg.Wait() + metrics.RecordSessionCompressDuration(float64(firstPart+secondPart), tp.String()) } - - // Save metrics - ctx, _ := context.WithTimeout(context.Background(), time.Millisecond*200) - if shouldSplit { - s.totalSessions.Add(ctx, 1) - s.sessionDOMSize.Record(ctx, float64(fileSize)) - } else { - s.sessionDevtoolsSize.Record(ctx, float64(fileSize)) - } - return nil } + +func (s *Storage) encryptSession(data []byte, encryptionKey string) []byte { + var encryptedData []byte + var err error + if encryptionKey != "" { + encryptedData, err = EncryptData(data, []byte(encryptionKey)) + if err != nil { + log.Printf("can't encrypt data: %s", err) + encryptedData = data + } + } else { + encryptedData = data + } + return encryptedData +} + +func (s *Storage) compressSession(data []byte) *bytes.Buffer { + zippedMob := new(bytes.Buffer) + z, _ := gzip.NewWriterLevel(zippedMob, gzip.BestSpeed) + if _, err := z.Write(data); err != nil { + log.Printf("can't write session data to compressor: %s", err) + } + if err := z.Close(); err != nil { + log.Printf("can't close compressor: %s", err) + } + return zippedMob +} + +func (s *Storage) uploadSession(task *Task) { + wg := &sync.WaitGroup{} + wg.Add(3) + var ( + uploadDoms int64 = 0 + uploadDome int64 = 0 + uploadDev int64 = 0 + ) + go func() { + if task.doms != nil { + start := time.Now() + if err := s.s3.Upload(task.doms, task.id+string(DOM)+"s", "application/octet-stream", true); err != nil { + log.Fatalf("Storage: start upload failed. %s", err) + } + uploadDoms = time.Now().Sub(start).Milliseconds() + } + wg.Done() + }() + go func() { + if task.dome != nil { + start := time.Now() + if err := s.s3.Upload(task.dome, task.id+string(DOM)+"e", "application/octet-stream", true); err != nil { + log.Fatalf("Storage: start upload failed. %s", err) + } + uploadDome = time.Now().Sub(start).Milliseconds() + } + wg.Done() + }() + go func() { + if task.dev != nil { + start := time.Now() + if err := s.s3.Upload(task.dev, task.id+string(DEV), "application/octet-stream", true); err != nil { + log.Fatalf("Storage: start upload failed. %s", err) + } + uploadDev = time.Now().Sub(start).Milliseconds() + } + wg.Done() + }() + wg.Wait() + metrics.RecordSessionUploadDuration(float64(uploadDoms+uploadDome), DOM.String()) + metrics.RecordSessionUploadDuration(float64(uploadDev), DEV.String()) + metrics.IncreaseStorageTotalSessions() +} + +func (s *Storage) worker() { + for { + select { + case task := <-s.tasks: + s.uploadSession(task) + default: + // Signal that worker finished all tasks + s.ready <- struct{}{} + } + } +} diff --git a/backend/pkg/db/cache/messages-web.go b/backend/pkg/db/cache/messages-web.go index 931d1f639..1df3d1520 100644 --- a/backend/pkg/db/cache/messages-web.go +++ b/backend/pkg/db/cache/messages-web.go @@ -99,7 +99,7 @@ func (c *PGCache) InsertSessionReferrer(sessionID uint64, referrer string) error return c.Conn.InsertSessionReferrer(sessionID, referrer) } -func (c *PGCache) InsertWebFetchEvent(sessionID uint64, e *FetchEvent) error { +func (c *PGCache) InsertWebNetworkRequest(sessionID uint64, e *NetworkRequest) error { session, err := c.Cache.GetSession(sessionID) if err != nil { return err @@ -108,10 +108,10 @@ func (c *PGCache) InsertWebFetchEvent(sessionID uint64, e *FetchEvent) error { if err != nil { return err } - return c.Conn.InsertWebFetchEvent(sessionID, session.ProjectID, project.SaveRequestPayloads, e) + return c.Conn.InsertWebNetworkRequest(sessionID, session.ProjectID, project.SaveRequestPayloads, e) } -func (c *PGCache) InsertWebGraphQLEvent(sessionID uint64, e *GraphQLEvent) error { +func (c *PGCache) InsertWebGraphQL(sessionID uint64, e *GraphQL) error { session, err := c.Cache.GetSession(sessionID) if err != nil { return err @@ -120,7 +120,7 @@ func (c *PGCache) InsertWebGraphQLEvent(sessionID uint64, e *GraphQLEvent) error if err != nil { return err } - return c.Conn.InsertWebGraphQLEvent(sessionID, session.ProjectID, project.SaveRequestPayloads, e) + return c.Conn.InsertWebGraphQL(sessionID, session.ProjectID, project.SaveRequestPayloads, e) } func (c *PGCache) InsertWebCustomEvent(sessionID uint64, e *CustomEvent) error { diff --git a/backend/pkg/db/postgres/batches.go b/backend/pkg/db/postgres/batches.go new file mode 100644 index 000000000..8b9f2484d --- /dev/null +++ b/backend/pkg/db/postgres/batches.go @@ -0,0 +1,209 @@ +package postgres + +import ( + "log" + "strings" + "time" + + "openreplay/backend/pkg/metrics/database" + + "github.com/jackc/pgx/v4" +) + +type batchItem struct { + query string + arguments []interface{} +} + +type SessionBatch struct { + sessID uint64 + batch *pgx.Batch + size int + items []*batchItem + updates *sessionUpdates +} + +func NewSessionBatch(sessionID uint64) *SessionBatch { + return &SessionBatch{ + sessID: sessionID, + batch: &pgx.Batch{}, + size: 0, + items: make([]*batchItem, 0), + updates: NewSessionUpdates(sessionID), + } +} + +func (b *SessionBatch) SessionID() uint64 { + return b.sessID +} + +func (b *SessionBatch) Queue(query string, arguments ...interface{}) { + b.batch.Queue(query, arguments...) + b.items = append(b.items, &batchItem{ + query: query, + arguments: arguments, + }) +} + +func (b *SessionBatch) Update(pages, events int) { + b.updates.addEvents(pages, events) +} + +func (b *SessionBatch) AddToSize(size int) { + b.size += size +} + +func (b *SessionBatch) Size() int { + return b.size +} + +func (b *SessionBatch) Len() int { + return b.batch.Len() +} + +func (b *SessionBatch) Prepare() { + sql, args := b.updates.request() + if sql != "" { + b.Queue(sql, args...) + } +} + +type batchesTask struct { + batches []*SessionBatch +} + +func NewBatchesTask(size int) *batchesTask { + return &batchesTask{batches: make([]*SessionBatch, 0, size)} +} + +type BatchSet struct { + c Pool + batches map[uint64]*SessionBatch + batchQueueLimit int + batchSizeLimit int + updates map[uint64]*sessionUpdates + workerTask chan *batchesTask + done chan struct{} + finished chan struct{} +} + +func NewBatchSet(c Pool, queueLimit, sizeLimit int) *BatchSet { + bs := &BatchSet{ + c: c, + batches: make(map[uint64]*SessionBatch), + batchQueueLimit: queueLimit, + batchSizeLimit: sizeLimit, + workerTask: make(chan *batchesTask, 1), + done: make(chan struct{}), + finished: make(chan struct{}), + updates: make(map[uint64]*sessionUpdates), + } + go bs.worker() + return bs +} + +func (conn *BatchSet) getBatch(sessionID uint64) *SessionBatch { + sessionID = sessionID % 10 + if _, ok := conn.batches[sessionID]; !ok { + conn.batches[sessionID] = NewSessionBatch(sessionID) + } + return conn.batches[sessionID] +} + +func (conn *BatchSet) batchQueue(sessionID uint64, sql string, args ...interface{}) { + conn.getBatch(sessionID).Queue(sql, args...) +} + +func (conn *BatchSet) updateSessionEvents(sessionID uint64, events, pages int) { + upd, ok := conn.updates[sessionID] + if !ok { + upd = NewSessionUpdates(sessionID) + conn.updates[sessionID] = upd + } + upd.addEvents(pages, events) +} + +func (conn *BatchSet) updateSessionIssues(sessionID uint64, errors, issueScore int) { + upd, ok := conn.updates[sessionID] + if !ok { + upd = NewSessionUpdates(sessionID) + conn.updates[sessionID] = upd + } + upd.addIssues(errors, issueScore) +} + +func (conn *BatchSet) updateBatchSize(sessionID uint64, reqSize int) { + conn.getBatch(sessionID).AddToSize(reqSize) +} + +func (conn *BatchSet) Commit() { + newTask := NewBatchesTask(len(conn.batches) + 2) + // Copy batches + for _, b := range conn.batches { + newTask.batches = append(newTask.batches, b) + } + // Reset current batches + conn.batches = make(map[uint64]*SessionBatch) + + // common batch for user's updates + batch := NewSessionBatch(0) + for _, upd := range conn.updates { + if str, args := upd.request(); str != "" { + batch.Queue(str, args...) + } + } + newTask.batches = append(newTask.batches, batch) + conn.updates = make(map[uint64]*sessionUpdates) + + conn.workerTask <- newTask +} + +func (conn *BatchSet) Stop() { + conn.done <- struct{}{} + <-conn.finished +} + +func (conn *BatchSet) sendBatches(t *batchesTask) { + for _, batch := range t.batches { + // Append session update sql request to the end of batch + batch.Prepare() + // Record batch size in bytes and number of lines + database.RecordBatchSize(float64(batch.Size())) + database.RecordBatchElements(float64(batch.Len())) + + start := time.Now() + + // Send batch to db and execute + br := conn.c.SendBatch(batch.batch) + l := batch.Len() + for i := 0; i < l; i++ { + if _, err := br.Exec(); err != nil { + log.Printf("Error in PG batch (session: %d): %v \n", batch.SessionID(), err) + failedSql := batch.items[i] + query := strings.ReplaceAll(failedSql.query, "\n", " ") + log.Println("failed sql req:", query, failedSql.arguments) + } + } + br.Close() // returns err + database.RecordBatchInsertDuration(float64(time.Now().Sub(start).Milliseconds())) + } +} + +func (conn *BatchSet) worker() { + for { + select { + case t := <-conn.workerTask: + start := time.Now() + conn.sendBatches(t) + log.Printf("pg batches dur: %d", time.Now().Sub(start).Milliseconds()) + case <-conn.done: + if len(conn.workerTask) > 0 { + for t := range conn.workerTask { + conn.sendBatches(t) + } + } + conn.finished <- struct{}{} + return + } + } +} diff --git a/backend/pkg/db/postgres/bulk.go b/backend/pkg/db/postgres/bulk.go index 16f59efcd..b6a2ddd35 100644 --- a/backend/pkg/db/postgres/bulk.go +++ b/backend/pkg/db/postgres/bulk.go @@ -4,6 +4,8 @@ import ( "bytes" "errors" "fmt" + "openreplay/backend/pkg/metrics/database" + "time" ) const ( @@ -15,6 +17,7 @@ const ( type Bulk interface { Append(args ...interface{}) error Send() error + Table() string } type bulkImpl struct { @@ -45,7 +48,13 @@ func (b *bulkImpl) Send() error { return b.send() } +func (b *bulkImpl) Table() string { + return b.table +} + func (b *bulkImpl) send() error { + start := time.Now() + size := len(b.values) / b.setSize request := bytes.NewBufferString(insertPrefix + b.table + b.columns + insertValues) args := make([]interface{}, b.setSize) for i := 0; i < len(b.values)/b.setSize; i++ { @@ -63,6 +72,9 @@ func (b *bulkImpl) send() error { if err != nil { return fmt.Errorf("send bulk err: %s", err) } + // Save bulk metrics + database.RecordBulkElements(float64(size), "pg", b.table) + database.RecordBulkInsertDuration(float64(time.Now().Sub(start).Milliseconds()), "pg", b.table) return nil } diff --git a/backend/pkg/db/postgres/bulks.go b/backend/pkg/db/postgres/bulks.go new file mode 100644 index 000000000..f3e9e95c9 --- /dev/null +++ b/backend/pkg/db/postgres/bulks.go @@ -0,0 +1,259 @@ +package postgres + +import ( + "log" + "time" +) + +type bulksTask struct { + bulks []Bulk +} + +func NewBulksTask() *bulksTask { + return &bulksTask{bulks: make([]Bulk, 0, 14)} +} + +type BulkSet struct { + c Pool + autocompletes Bulk + requests Bulk + customEvents Bulk + webPageEvents Bulk + webInputEvents Bulk + webGraphQL Bulk + webErrors Bulk + webErrorEvents Bulk + webErrorTags Bulk + webIssues Bulk + webIssueEvents Bulk + webCustomEvents Bulk + webClickEvents Bulk + webNetworkRequest Bulk + workerTask chan *bulksTask + done chan struct{} + finished chan struct{} +} + +func NewBulkSet(c Pool) *BulkSet { + bs := &BulkSet{ + c: c, + workerTask: make(chan *bulksTask, 1), + done: make(chan struct{}), + finished: make(chan struct{}), + } + bs.initBulks() + go bs.worker() + return bs +} + +func (conn *BulkSet) Get(name string) Bulk { + switch name { + case "autocompletes": + return conn.autocompletes + case "requests": + return conn.requests + case "customEvents": + return conn.customEvents + case "webPageEvents": + return conn.webPageEvents + case "webInputEvents": + return conn.webInputEvents + case "webGraphQL": + return conn.webGraphQL + case "webErrors": + return conn.webErrors + case "webErrorEvents": + return conn.webErrorEvents + case "webErrorTags": + return conn.webErrorTags + case "webIssues": + return conn.webIssues + case "webIssueEvents": + return conn.webIssueEvents + case "webCustomEvents": + return conn.webCustomEvents + case "webClickEvents": + return conn.webClickEvents + case "webNetworkRequest": + return conn.webNetworkRequest + default: + return nil + } +} + +func (conn *BulkSet) initBulks() { + var err error + conn.autocompletes, err = NewBulk(conn.c, + "autocomplete", + "(value, type, project_id)", + "($%d, $%d, $%d)", + 3, 200) + if err != nil { + log.Fatalf("can't create autocomplete bulk: %s", err) + } + conn.requests, err = NewBulk(conn.c, + "events_common.requests", + "(session_id, timestamp, seq_index, url, duration, success)", + "($%d, $%d, $%d, LEFT($%d, 8000), $%d, $%d)", + 6, 200) + if err != nil { + log.Fatalf("can't create requests bulk: %s", err) + } + conn.customEvents, err = NewBulk(conn.c, + "events_common.customs", + "(session_id, timestamp, seq_index, name, payload)", + "($%d, $%d, $%d, LEFT($%d, 2000), $%d)", + 5, 200) + if err != nil { + log.Fatalf("can't create customEvents bulk: %s", err) + } + conn.webPageEvents, err = NewBulk(conn.c, + "events.pages", + "(session_id, message_id, timestamp, referrer, base_referrer, host, path, query, dom_content_loaded_time, "+ + "load_time, response_end, first_paint_time, first_contentful_paint_time, speed_index, visually_complete, "+ + "time_to_interactive, response_time, dom_building_time)", + "($%d, $%d, $%d, LEFT($%d, 8000), LEFT($%d, 8000), LEFT($%d, 300), LEFT($%d, 2000), LEFT($%d, 8000), "+ + "NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0),"+ + " NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0))", + 18, 200) + if err != nil { + log.Fatalf("can't create webPageEvents bulk: %s", err) + } + conn.webInputEvents, err = NewBulk(conn.c, + "events.inputs", + "(session_id, message_id, timestamp, value, label)", + "($%d, $%d, $%d, LEFT($%d, 2000), NULLIF(LEFT($%d, 2000),''))", + 5, 200) + if err != nil { + log.Fatalf("can't create webPageEvents bulk: %s", err) + } + conn.webGraphQL, err = NewBulk(conn.c, + "events.graphql", + "(session_id, timestamp, message_id, name, request_body, response_body)", + "($%d, $%d, $%d, LEFT($%d, 2000), $%d, $%d)", + 6, 200) + if err != nil { + log.Fatalf("can't create webPageEvents bulk: %s", err) + } + conn.webErrors, err = NewBulk(conn.c, + "errors", + "(error_id, project_id, source, name, message, payload)", + "($%d, $%d, $%d, $%d, $%d, $%d::jsonb)", + 6, 200) + if err != nil { + log.Fatalf("can't create webErrors bulk: %s", err) + } + conn.webErrorEvents, err = NewBulk(conn.c, + "events.errors", + "(session_id, message_id, timestamp, error_id)", + "($%d, $%d, $%d, $%d)", + 4, 200) + if err != nil { + log.Fatalf("can't create webErrorEvents bulk: %s", err) + } + conn.webErrorTags, err = NewBulk(conn.c, + "public.errors_tags", + "(session_id, message_id, error_id, key, value)", + "($%d, $%d, $%d, $%d, $%d)", + 5, 200) + if err != nil { + log.Fatalf("can't create webErrorEvents bulk: %s", err) + } + conn.webIssues, err = NewBulk(conn.c, + "issues", + "(project_id, issue_id, type, context_string)", + "($%d, $%d, $%d, $%d)", + 4, 200) + if err != nil { + log.Fatalf("can't create webIssues bulk: %s", err) + } + conn.webIssueEvents, err = NewBulk(conn.c, + "events_common.issues", + "(session_id, issue_id, timestamp, seq_index, payload)", + "($%d, $%d, $%d, $%d, CAST($%d AS jsonb))", + 5, 200) + if err != nil { + log.Fatalf("can't create webIssueEvents bulk: %s", err) + } + conn.webCustomEvents, err = NewBulk(conn.c, + "events_common.customs", + "(session_id, seq_index, timestamp, name, payload, level)", + "($%d, $%d, $%d, LEFT($%d, 2000), $%d, $%d)", + 6, 200) + if err != nil { + log.Fatalf("can't create webCustomEvents bulk: %s", err) + } + conn.webClickEvents, err = NewBulk(conn.c, + "events.clicks", + "(session_id, message_id, timestamp, label, selector, url, path)", + "($%d, $%d, $%d, NULLIF(LEFT($%d, 2000), ''), LEFT($%d, 8000), LEFT($%d, 2000), LEFT($%d, 2000))", + 7, 200) + if err != nil { + log.Fatalf("can't create webClickEvents bulk: %s", err) + } + conn.webNetworkRequest, err = NewBulk(conn.c, + "events_common.requests", + "(session_id, timestamp, seq_index, url, host, path, query, request_body, response_body, status_code, method, duration, success)", + "($%d, $%d, $%d, LEFT($%d, 8000), LEFT($%d, 300), LEFT($%d, 2000), LEFT($%d, 8000), $%d, $%d, $%d::smallint, NULLIF($%d, '')::http_method, $%d, $%d)", + 13, 200) + if err != nil { + log.Fatalf("can't create webNetworkRequest bulk: %s", err) + } +} + +func (conn *BulkSet) Send() { + newTask := NewBulksTask() + + // Prepare set of bulks to send + newTask.bulks = append(newTask.bulks, conn.autocompletes) + newTask.bulks = append(newTask.bulks, conn.requests) + newTask.bulks = append(newTask.bulks, conn.customEvents) + newTask.bulks = append(newTask.bulks, conn.webPageEvents) + newTask.bulks = append(newTask.bulks, conn.webInputEvents) + newTask.bulks = append(newTask.bulks, conn.webGraphQL) + newTask.bulks = append(newTask.bulks, conn.webErrors) + newTask.bulks = append(newTask.bulks, conn.webErrorEvents) + newTask.bulks = append(newTask.bulks, conn.webErrorTags) + newTask.bulks = append(newTask.bulks, conn.webIssues) + newTask.bulks = append(newTask.bulks, conn.webIssueEvents) + newTask.bulks = append(newTask.bulks, conn.webCustomEvents) + newTask.bulks = append(newTask.bulks, conn.webClickEvents) + newTask.bulks = append(newTask.bulks, conn.webNetworkRequest) + + conn.workerTask <- newTask + + // Reset new bulks + conn.initBulks() +} + +func (conn *BulkSet) Stop() { + conn.done <- struct{}{} + <-conn.finished +} + +func (conn *BulkSet) sendBulks(t *bulksTask) { + for _, bulk := range t.bulks { + if err := bulk.Send(); err != nil { + log.Printf("%s bulk send err: %s", bulk.Table(), err) + } + } +} + +func (conn *BulkSet) worker() { + for { + select { + case t := <-conn.workerTask: + start := time.Now() + conn.sendBulks(t) + log.Printf("pg bulks dur: %d", time.Now().Sub(start).Milliseconds()) + case <-conn.done: + if len(conn.workerTask) > 0 { + for t := range conn.workerTask { + conn.sendBulks(t) + } + } + conn.finished <- struct{}{} + return + } + } +} diff --git a/backend/pkg/db/postgres/connector.go b/backend/pkg/db/postgres/connector.go index 9e269fcc0..6904dc135 100644 --- a/backend/pkg/db/postgres/connector.go +++ b/backend/pkg/db/postgres/connector.go @@ -2,163 +2,55 @@ package postgres import ( "context" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" "log" - "openreplay/backend/pkg/db/types" - "openreplay/backend/pkg/monitoring" - "strings" - "time" - "github.com/jackc/pgx/v4" "github.com/jackc/pgx/v4/pgxpool" + "openreplay/backend/pkg/db/types" ) type CH interface { InsertAutocomplete(session *types.Session, msgType, msgValue string) error } -type batchItem struct { - query string - arguments []interface{} -} - // Conn contains batches, bulks and cache for all sessions type Conn struct { - c Pool - batches map[uint64]*pgx.Batch - batchSizes map[uint64]int - rawBatches map[uint64][]*batchItem - autocompletes Bulk - requests Bulk - customEvents Bulk - webPageEvents Bulk - webInputEvents Bulk - webGraphQLEvents Bulk - sessionUpdates map[uint64]*sessionUpdates - batchQueueLimit int - batchSizeLimit int - batchSizeBytes syncfloat64.Histogram - batchSizeLines syncfloat64.Histogram - sqlRequestTime syncfloat64.Histogram - sqlRequestCounter syncfloat64.Counter - chConn CH + c Pool + batches *BatchSet + bulks *BulkSet + chConn CH } func (conn *Conn) SetClickHouse(ch CH) { conn.chConn = ch } -func NewConn(url string, queueLimit, sizeLimit int, metrics *monitoring.Metrics) *Conn { - if metrics == nil { - log.Fatalf("metrics is nil") - } +func NewConn(url string, queueLimit, sizeLimit int) *Conn { c, err := pgxpool.Connect(context.Background(), url) if err != nil { log.Fatalf("pgxpool.Connect err: %s", err) } - conn := &Conn{ - batches: make(map[uint64]*pgx.Batch), - batchSizes: make(map[uint64]int), - rawBatches: make(map[uint64][]*batchItem), - sessionUpdates: make(map[uint64]*sessionUpdates), - batchQueueLimit: queueLimit, - batchSizeLimit: sizeLimit, - } - conn.initMetrics(metrics) - conn.c, err = NewPool(c, conn.sqlRequestTime, conn.sqlRequestCounter) + conn := &Conn{} + conn.c, err = NewPool(c) if err != nil { log.Fatalf("can't create new pool wrapper: %s", err) } - conn.initBulks() + conn.bulks = NewBulkSet(conn.c) + conn.batches = NewBatchSet(conn.c, queueLimit, sizeLimit) return conn } func (conn *Conn) Close() error { + conn.bulks.Stop() + conn.batches.Stop() conn.c.Close() return nil } -func (conn *Conn) initMetrics(metrics *monitoring.Metrics) { - var err error - conn.batchSizeBytes, err = metrics.RegisterHistogram("batch_size_bytes") - if err != nil { - log.Printf("can't create batchSizeBytes metric: %s", err) - } - conn.batchSizeLines, err = metrics.RegisterHistogram("batch_size_lines") - if err != nil { - log.Printf("can't create batchSizeLines metric: %s", err) - } - conn.sqlRequestTime, err = metrics.RegisterHistogram("sql_request_time") - if err != nil { - log.Printf("can't create sqlRequestTime metric: %s", err) - } - conn.sqlRequestCounter, err = metrics.RegisterCounter("sql_request_number") - if err != nil { - log.Printf("can't create sqlRequestNumber metric: %s", err) - } -} - -func (conn *Conn) initBulks() { - var err error - conn.autocompletes, err = NewBulk(conn.c, - "autocomplete", - "(value, type, project_id)", - "($%d, $%d, $%d)", - 3, 100) - if err != nil { - log.Fatalf("can't create autocomplete bulk") - } - conn.requests, err = NewBulk(conn.c, - "events_common.requests", - "(session_id, timestamp, seq_index, url, duration, success)", - "($%d, $%d, $%d, left($%d, 2700), $%d, $%d)", - 6, 100) - if err != nil { - log.Fatalf("can't create requests bulk") - } - conn.customEvents, err = NewBulk(conn.c, - "events_common.customs", - "(session_id, timestamp, seq_index, name, payload)", - "($%d, $%d, $%d, left($%d, 2700), $%d)", - 5, 100) - if err != nil { - log.Fatalf("can't create customEvents bulk") - } - conn.webPageEvents, err = NewBulk(conn.c, - "events.pages", - "(session_id, message_id, timestamp, referrer, base_referrer, host, path, query, dom_content_loaded_time, "+ - "load_time, response_end, first_paint_time, first_contentful_paint_time, speed_index, visually_complete, "+ - "time_to_interactive, response_time, dom_building_time)", - "($%d, $%d, $%d, $%d, $%d, $%d, $%d, $%d, NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0),"+ - " NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0), NULLIF($%d, 0))", - 18, 100) - if err != nil { - log.Fatalf("can't create webPageEvents bulk") - } - conn.webInputEvents, err = NewBulk(conn.c, - "events.inputs", - "(session_id, message_id, timestamp, value, label)", - "($%d, $%d, $%d, $%d, NULLIF($%d,''))", - 5, 100) - if err != nil { - log.Fatalf("can't create webPageEvents bulk") - } - conn.webGraphQLEvents, err = NewBulk(conn.c, - "events.graphql", - "(session_id, timestamp, message_id, name, request_body, response_body)", - "($%d, $%d, $%d, left($%d, 2700), $%d, $%d)", - 6, 100) - if err != nil { - log.Fatalf("can't create webPageEvents bulk") - } -} - func (conn *Conn) insertAutocompleteValue(sessionID uint64, projectID uint32, tp string, value string) { if len(value) == 0 { return } - if err := conn.autocompletes.Append(value, tp, projectID); err != nil { + if err := conn.bulks.Get("autocompletes").Append(value, tp, projectID); err != nil { log.Printf("autocomplete bulk err: %s", err) } if conn.chConn == nil { @@ -171,161 +63,22 @@ func (conn *Conn) insertAutocompleteValue(sessionID uint64, projectID uint32, tp } func (conn *Conn) batchQueue(sessionID uint64, sql string, args ...interface{}) { - batch, ok := conn.batches[sessionID] - if !ok { - conn.batches[sessionID] = &pgx.Batch{} - conn.rawBatches[sessionID] = make([]*batchItem, 0) - batch = conn.batches[sessionID] - } - batch.Queue(sql, args...) - conn.rawBatch(sessionID, sql, args...) -} - -func (conn *Conn) rawBatch(sessionID uint64, sql string, args ...interface{}) { - // Temp raw batch store - raw := conn.rawBatches[sessionID] - raw = append(raw, &batchItem{ - query: sql, - arguments: args, - }) - conn.rawBatches[sessionID] = raw + conn.batches.batchQueue(sessionID, sql, args...) } func (conn *Conn) updateSessionEvents(sessionID uint64, events, pages int) { - if _, ok := conn.sessionUpdates[sessionID]; !ok { - conn.sessionUpdates[sessionID] = NewSessionUpdates(sessionID) - } - conn.sessionUpdates[sessionID].add(pages, events) + conn.batches.updateSessionEvents(sessionID, events, pages) } -func (conn *Conn) sendBulks() { - if err := conn.autocompletes.Send(); err != nil { - log.Printf("autocomplete bulk send err: %s", err) - } - if err := conn.requests.Send(); err != nil { - log.Printf("requests bulk send err: %s", err) - } - if err := conn.customEvents.Send(); err != nil { - log.Printf("customEvents bulk send err: %s", err) - } - if err := conn.webPageEvents.Send(); err != nil { - log.Printf("webPageEvents bulk send err: %s", err) - } - if err := conn.webInputEvents.Send(); err != nil { - log.Printf("webInputEvents bulk send err: %s", err) - } - if err := conn.webGraphQLEvents.Send(); err != nil { - log.Printf("webGraphQLEvents bulk send err: %s", err) - } +func (conn *Conn) updateSessionIssues(sessionID uint64, errors, issueScore int) { + conn.batches.updateSessionIssues(sessionID, errors, issueScore) } -func (conn *Conn) CommitBatches() { - conn.sendBulks() - for sessID, b := range conn.batches { - // Append session update sql request to the end of batch - if update, ok := conn.sessionUpdates[sessID]; ok { - sql, args := update.request() - if sql != "" { - conn.batchQueue(sessID, sql, args...) - b, _ = conn.batches[sessID] - } - } - // Record batch size in bytes and number of lines - conn.batchSizeBytes.Record(context.Background(), float64(conn.batchSizes[sessID])) - conn.batchSizeLines.Record(context.Background(), float64(b.Len())) - - start := time.Now() - isFailed := false - - // Send batch to db and execute - br := conn.c.SendBatch(b) - l := b.Len() - for i := 0; i < l; i++ { - if ct, err := br.Exec(); err != nil { - log.Printf("Error in PG batch (command tag %s, session: %d): %v \n", ct.String(), sessID, err) - failedSql := conn.rawBatches[sessID][i] - query := strings.ReplaceAll(failedSql.query, "\n", " ") - log.Println("failed sql req:", query, failedSql.arguments) - isFailed = true - } - } - br.Close() // returns err - conn.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", "batch"), attribute.Bool("failed", isFailed)) - conn.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", "batch"), attribute.Bool("failed", isFailed)) - if !isFailed { - delete(conn.sessionUpdates, sessID) - } - } - conn.batches = make(map[uint64]*pgx.Batch) - conn.batchSizes = make(map[uint64]int) - conn.rawBatches = make(map[uint64][]*batchItem) - - // Session updates - for sessID, su := range conn.sessionUpdates { - sql, args := su.request() - if sql == "" { - continue - } - if err := conn.c.Exec(sql, args...); err != nil { - log.Printf("failed session update, sessID: %d, err: %s", sessID, err) - } - } - conn.sessionUpdates = make(map[uint64]*sessionUpdates) +func (conn *Conn) Commit() { + conn.bulks.Send() + conn.batches.Commit() } func (conn *Conn) updateBatchSize(sessionID uint64, reqSize int) { - conn.batchSizes[sessionID] += reqSize - if conn.batchSizes[sessionID] >= conn.batchSizeLimit || conn.batches[sessionID].Len() >= conn.batchQueueLimit { - conn.commitBatch(sessionID) - } -} - -// Send only one batch to pg -func (conn *Conn) commitBatch(sessionID uint64) { - b, ok := conn.batches[sessionID] - if !ok { - log.Printf("can't find batch for session: %d", sessionID) - return - } - // Append session update sql request to the end of batch - if update, ok := conn.sessionUpdates[sessionID]; ok { - sql, args := update.request() - if sql != "" { - conn.batchQueue(sessionID, sql, args...) - b, _ = conn.batches[sessionID] - } - } - // Record batch size in bytes and number of lines - conn.batchSizeBytes.Record(context.Background(), float64(conn.batchSizes[sessionID])) - conn.batchSizeLines.Record(context.Background(), float64(b.Len())) - - start := time.Now() - isFailed := false - - // Send batch to db and execute - br := conn.c.SendBatch(b) - l := b.Len() - for i := 0; i < l; i++ { - if ct, err := br.Exec(); err != nil { - log.Printf("Error in PG batch (command tag %s, session: %d): %v \n", ct.String(), sessionID, err) - failedSql := conn.rawBatches[sessionID][i] - query := strings.ReplaceAll(failedSql.query, "\n", " ") - log.Println("failed sql req:", query, failedSql.arguments) - isFailed = true - } - } - br.Close() - - conn.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", "batch"), attribute.Bool("failed", isFailed)) - conn.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", "batch"), attribute.Bool("failed", isFailed)) - - // Clean batch info - delete(conn.batches, sessionID) - delete(conn.batchSizes, sessionID) - delete(conn.rawBatches, sessionID) - delete(conn.sessionUpdates, sessionID) + conn.batches.updateBatchSize(sessionID, reqSize) } diff --git a/backend/pkg/db/postgres/messages-common.go b/backend/pkg/db/postgres/messages-common.go index fca11ce88..aab74dc6b 100644 --- a/backend/pkg/db/postgres/messages-common.go +++ b/backend/pkg/db/postgres/messages-common.go @@ -37,7 +37,7 @@ func (conn *Conn) InsertSessionStart(sessionID uint64, s *types.Session) error { $11, $12, $13, NULLIF($14, ''), NULLIF($15, ''), NULLIF($16, ''), NULLIF($17, 0), NULLIF($18, 0::bigint), - NULLIF($19, '') + NULLIF(LEFT($19, 8000), '') )`, sessionID, s.ProjectID, s.Timestamp, s.UserUUID, s.UserDevice, s.UserDeviceType, s.UserCountry, @@ -104,14 +104,14 @@ func (conn *Conn) HandleSessionEnd(sessionID uint64) error { } func (conn *Conn) InsertRequest(sessionID uint64, timestamp uint64, index uint32, url string, duration uint64, success bool) error { - if err := conn.requests.Append(sessionID, timestamp, index, url, duration, success); err != nil { + if err := conn.bulks.Get("requests").Append(sessionID, timestamp, index, url, duration, success); err != nil { return fmt.Errorf("insert request in bulk err: %s", err) } return nil } func (conn *Conn) InsertCustomEvent(sessionID uint64, timestamp uint64, index uint32, name string, payload string) error { - if err := conn.customEvents.Append(sessionID, timestamp, index, name, payload); err != nil { + if err := conn.bulks.Get("customEvents").Append(sessionID, timestamp, index, name, payload); err != nil { return fmt.Errorf("insert custom event in bulk err: %s", err) } return nil @@ -119,7 +119,7 @@ func (conn *Conn) InsertCustomEvent(sessionID uint64, timestamp uint64, index ui func (conn *Conn) InsertUserID(sessionID uint64, userID string) error { sqlRequest := ` - UPDATE sessions SET user_id = $1 + UPDATE sessions SET user_id = LEFT($1, 8000) WHERE session_id = $2` conn.batchQueue(sessionID, sqlRequest, userID, sessionID) @@ -141,75 +141,29 @@ func (conn *Conn) InsertUserAnonymousID(sessionID uint64, userAnonymousID string func (conn *Conn) InsertMetadata(sessionID uint64, keyNo uint, value string) error { sqlRequest := ` - UPDATE sessions SET metadata_%v = $1 + UPDATE sessions SET metadata_%v = LEFT($1, 8000) WHERE session_id = $2` return conn.c.Exec(fmt.Sprintf(sqlRequest, keyNo), value, sessionID) } -func (conn *Conn) InsertIssueEvent(sessionID uint64, projectID uint32, e *messages.IssueEvent) (err error) { - tx, err := conn.c.Begin() - if err != nil { - return err - } - defer func() { - if err != nil { - if rollbackErr := tx.rollback(); rollbackErr != nil { - log.Printf("rollback err: %s", rollbackErr) - } - } - }() +func (conn *Conn) InsertIssueEvent(sessionID uint64, projectID uint32, e *messages.IssueEvent) error { issueID := hashid.IssueID(projectID, e) - - // TEMP. TODO: nullable & json message field type payload := &e.Payload if *payload == "" || *payload == "{}" { payload = nil } - if err = tx.exec(` - INSERT INTO issues ( - project_id, issue_id, type, context_string - ) (SELECT - project_id, $2, $3, $4 - FROM sessions - WHERE session_id = $1 - )ON CONFLICT DO NOTHING`, - sessionID, issueID, e.Type, e.ContextString, - ); err != nil { - return err + if err := conn.bulks.Get("webIssues").Append(projectID, issueID, e.Type, e.ContextString); err != nil { + log.Printf("insert web issue err: %s", err) } - if err = tx.exec(` - INSERT INTO events_common.issues ( - session_id, issue_id, timestamp, seq_index, payload - ) VALUES ( - $1, $2, $3, $4, CAST($5 AS jsonb) - )`, - sessionID, issueID, e.Timestamp, - truncSqIdx(e.MessageID), - payload, - ); err != nil { - return err + if err := conn.bulks.Get("webIssueEvents").Append(sessionID, issueID, e.Timestamp, truncSqIdx(e.MessageID), payload); err != nil { + log.Printf("insert web issue event err: %s", err) } - if err = tx.exec(` - UPDATE sessions SET issue_score = issue_score + $2 - WHERE session_id = $1`, - sessionID, getIssueScore(e), - ); err != nil { - return err - } - // TODO: no redundancy. Deliver to UI in a different way + conn.updateSessionIssues(sessionID, 0, getIssueScore(e)) if e.Type == "custom" { - if err = tx.exec(` - INSERT INTO events_common.customs - (session_id, seq_index, timestamp, name, payload, level) - VALUES - ($1, $2, $3, left($4, 2700), $5, 'error') - `, - sessionID, truncSqIdx(e.MessageID), e.Timestamp, e.ContextString, e.Payload, - ); err != nil { - return err + if err := conn.bulks.Get("webCustomEvents").Append(sessionID, truncSqIdx(e.MessageID), e.Timestamp, e.ContextString, e.Payload, "error"); err != nil { + log.Printf("insert web custom event err: %s", err) } } - err = tx.commit() - return + return nil } diff --git a/backend/pkg/db/postgres/messages-web-stats.go b/backend/pkg/db/postgres/messages-web-stats.go index 719fe0d61..42458a497 100644 --- a/backend/pkg/db/postgres/messages-web-stats.go +++ b/backend/pkg/db/postgres/messages-web-stats.go @@ -52,7 +52,7 @@ func (conn *Conn) InsertWebStatsResourceEvent(sessionID uint64, e *ResourceEvent ) VALUES ( $1, $2, $3, $4, - left($5, 2700), $6, $7, + LEFT($5, 8000), LEFT($6, 300), LEFT($7, 2000), $8, $9, NULLIF($10, '')::events.resource_method, NULLIF($11, 0), NULLIF($12, 0), NULLIF($13, 0), NULLIF($14, 0), NULLIF($15, 0) diff --git a/backend/pkg/db/postgres/messages-web.go b/backend/pkg/db/postgres/messages-web.go index 10cfad409..08db4491e 100644 --- a/backend/pkg/db/postgres/messages-web.go +++ b/backend/pkg/db/postgres/messages-web.go @@ -9,9 +9,13 @@ import ( ) func (conn *Conn) InsertWebCustomEvent(sessionID uint64, projectID uint32, e *CustomEvent) error { - err := conn.InsertCustomEvent(sessionID, e.Timestamp, - truncSqIdx(e.MessageID), - e.Name, e.Payload) + err := conn.InsertCustomEvent( + sessionID, + uint64(e.Meta().Timestamp), + truncSqIdx(e.Meta().Index), + e.Name, + e.Payload, + ) if err == nil { conn.insertAutocompleteValue(sessionID, projectID, "CUSTOM", e.Name) } @@ -40,7 +44,7 @@ func (conn *Conn) InsertWebPageEvent(sessionID uint64, projectID uint32, e *Page return err } // base_path is deprecated - if err = conn.webPageEvents.Append(sessionID, truncSqIdx(e.MessageID), e.Timestamp, e.Referrer, url.DiscardURLQuery(e.Referrer), + if err = conn.bulks.Get("webPageEvents").Append(sessionID, truncSqIdx(e.MessageID), e.Timestamp, e.Referrer, url.DiscardURLQuery(e.Referrer), host, path, query, e.DomContentLoadedEventEnd, e.LoadEventEnd, e.ResponseEnd, e.FirstPaint, e.FirstContentfulPaint, e.SpeedIndex, e.VisuallyComplete, e.TimeToInteractive, calcResponseTime(e), calcDomBuildingTime(e)); err != nil { log.Printf("insert web page event in bulk err: %s", err) @@ -54,16 +58,11 @@ func (conn *Conn) InsertWebPageEvent(sessionID uint64, projectID uint32, e *Page } func (conn *Conn) InsertWebClickEvent(sessionID uint64, projectID uint32, e *ClickEvent) error { - sqlRequest := ` - INSERT INTO events.clicks - (session_id, message_id, timestamp, label, selector, url) - (SELECT - $1, $2, $3, NULLIF($4, ''), $5, host || path - FROM events.pages - WHERE session_id = $1 AND timestamp <= $3 ORDER BY timestamp DESC LIMIT 1 - ) - ` - conn.batchQueue(sessionID, sqlRequest, sessionID, truncSqIdx(e.MessageID), e.Timestamp, e.Label, e.Selector) + var host, path string + host, path, _, _ = url.GetURLParts(e.Url) + if err := conn.bulks.Get("webClickEvents").Append(sessionID, truncSqIdx(e.MessageID), e.Timestamp, e.Label, e.Selector, host+path, path); err != nil { + log.Printf("insert web click err: %s", err) + } // Accumulate session updates and exec inside batch with another sql commands conn.updateSessionEvents(sessionID, 1, 0) // Add new value set to autocomplete bulk @@ -79,7 +78,7 @@ func (conn *Conn) InsertWebInputEvent(sessionID uint64, projectID uint32, e *Inp if e.ValueMasked { value = nil } - if err := conn.webInputEvents.Append(sessionID, truncSqIdx(e.MessageID), e.Timestamp, value, e.Label); err != nil { + if err := conn.bulks.Get("webInputEvents").Append(sessionID, truncSqIdx(e.MessageID), e.Timestamp, value, e.Label); err != nil { log.Printf("insert web input event err: %s", err) } conn.updateSessionEvents(sessionID, 1, 0) @@ -87,64 +86,24 @@ func (conn *Conn) InsertWebInputEvent(sessionID uint64, projectID uint32, e *Inp return nil } -func (conn *Conn) InsertWebErrorEvent(sessionID uint64, projectID uint32, e *types.ErrorEvent) (err error) { - tx, err := conn.c.Begin() - if err != nil { - return err - } - defer func() { - if err != nil { - if rollbackErr := tx.rollback(); rollbackErr != nil { - log.Printf("rollback err: %s", rollbackErr) - } - } - }() +func (conn *Conn) InsertWebErrorEvent(sessionID uint64, projectID uint32, e *types.ErrorEvent) error { errorID := e.ID(projectID) - - if err = tx.exec(` - INSERT INTO errors - (error_id, project_id, source, name, message, payload) - VALUES - ($1, $2, $3, $4, $5, $6::jsonb) - ON CONFLICT DO NOTHING`, - errorID, projectID, e.Source, e.Name, e.Message, e.Payload, - ); err != nil { - return err + if err := conn.bulks.Get("webErrors").Append(errorID, projectID, e.Source, e.Name, e.Message, e.Payload); err != nil { + log.Printf("insert web error err: %s", err) } - if err = tx.exec(` - INSERT INTO events.errors - (session_id, message_id, timestamp, error_id) - VALUES - ($1, $2, $3, $4) - `, - sessionID, truncSqIdx(e.MessageID), e.Timestamp, errorID, - ); err != nil { - return err + if err := conn.bulks.Get("webErrorEvents").Append(sessionID, truncSqIdx(e.MessageID), e.Timestamp, errorID); err != nil { + log.Printf("insert web error event err: %s", err) } - if err = tx.exec(` - UPDATE sessions SET errors_count = errors_count + 1, issue_score = issue_score + 1000 - WHERE session_id = $1`, - sessionID, - ); err != nil { - return err - } - err = tx.commit() - - // Insert tags - sqlRequest := ` - INSERT INTO public.errors_tags ( - session_id, message_id, error_id, key, value - ) VALUES ( - $1, $2, $3, $4, $5 - ) ON CONFLICT DO NOTHING` + conn.updateSessionIssues(sessionID, 1, 1000) for key, value := range e.Tags { - conn.batchQueue(sessionID, sqlRequest, sessionID, truncSqIdx(e.MessageID), errorID, key, value) + if err := conn.bulks.Get("webErrorTags").Append(sessionID, truncSqIdx(e.MessageID), errorID, key, value); err != nil { + log.Printf("insert web error token err: %s", err) + } } - - return + return nil } -func (conn *Conn) InsertWebFetchEvent(sessionID uint64, projectID uint32, savePayload bool, e *FetchEvent) error { +func (conn *Conn) InsertWebNetworkRequest(sessionID uint64, projectID uint32, savePayload bool, e *NetworkRequest) error { var request, response *string if savePayload { request = &e.Request @@ -155,39 +114,18 @@ func (conn *Conn) InsertWebFetchEvent(sessionID uint64, projectID uint32, savePa if err != nil { return err } - - sqlRequest := ` - INSERT INTO events_common.requests ( - session_id, timestamp, seq_index, - url, host, path, query, - request_body, response_body, status_code, method, - duration, success - ) VALUES ( - $1, $2, $3, - left($4, 2700), $5, $6, $7, - $8, $9, $10::smallint, NULLIF($11, '')::http_method, - $12, $13 - ) ON CONFLICT DO NOTHING` - conn.batchQueue(sessionID, sqlRequest, - sessionID, e.Timestamp, truncSqIdx(e.MessageID), - e.URL, host, path, query, - request, response, e.Status, url.EnsureMethod(e.Method), - e.Duration, e.Status < 400, - ) - - // Record approximate message size - conn.updateBatchSize(sessionID, len(sqlRequest)+len(e.URL)+len(host)+len(path)+len(query)+ - len(e.Request)+len(e.Response)+len(url.EnsureMethod(e.Method))+8*5+1) + conn.bulks.Get("webNetworkRequest").Append(sessionID, e.Meta().Timestamp, truncSqIdx(e.Meta().Index), e.URL, host, path, query, + request, response, e.Status, url.EnsureMethod(e.Method), e.Duration, e.Status < 400) return nil } -func (conn *Conn) InsertWebGraphQLEvent(sessionID uint64, projectID uint32, savePayload bool, e *GraphQLEvent) error { +func (conn *Conn) InsertWebGraphQL(sessionID uint64, projectID uint32, savePayload bool, e *GraphQL) error { var request, response *string if savePayload { request = &e.Variables response = &e.Response } - if err := conn.webGraphQLEvents.Append(sessionID, e.Timestamp, truncSqIdx(e.MessageID), e.OperationName, request, response); err != nil { + if err := conn.bulks.Get("webGraphQL").Append(sessionID, e.Meta().Timestamp, truncSqIdx(e.Meta().Index), e.OperationName, request, response); err != nil { log.Printf("insert web graphQL event err: %s", err) } conn.insertAutocompleteValue(sessionID, projectID, "GRAPHQL", e.OperationName) @@ -200,7 +138,7 @@ func (conn *Conn) InsertSessionReferrer(sessionID uint64, referrer string) error } return conn.c.Exec(` UPDATE sessions - SET referrer = $1, base_referrer = $2 + SET referrer = LEFT($1, 8000), base_referrer = LEFT($2, 8000) WHERE session_id = $3 AND referrer IS NULL`, referrer, url.DiscardURLQuery(referrer), sessionID) } diff --git a/backend/pkg/db/postgres/pool.go b/backend/pkg/db/postgres/pool.go index 5f9cbaa29..5214be8d0 100644 --- a/backend/pkg/db/postgres/pool.go +++ b/backend/pkg/db/postgres/pool.go @@ -3,12 +3,12 @@ package postgres import ( "context" "errors" - "github.com/jackc/pgx/v4" - "github.com/jackc/pgx/v4/pgxpool" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" "strings" "time" + + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" + "openreplay/backend/pkg/metrics/database" ) // Pool is a pgx.Pool wrapper with metrics integration @@ -22,19 +22,15 @@ type Pool interface { } type poolImpl struct { - conn *pgxpool.Pool - sqlRequestTime syncfloat64.Histogram - sqlRequestCounter syncfloat64.Counter + conn *pgxpool.Pool } func (p *poolImpl) Query(sql string, args ...interface{}) (pgx.Rows, error) { start := time.Now() res, err := p.conn.Query(getTimeoutContext(), sql, args...) method, table := methodName(sql) - p.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", method), attribute.String("table", table)) - p.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", method), attribute.String("table", table)) + database.RecordRequestDuration(float64(time.Now().Sub(start).Milliseconds()), method, table) + database.IncreaseTotalRequests(method, table) return res, err } @@ -42,10 +38,8 @@ func (p *poolImpl) QueryRow(sql string, args ...interface{}) pgx.Row { start := time.Now() res := p.conn.QueryRow(getTimeoutContext(), sql, args...) method, table := methodName(sql) - p.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", method), attribute.String("table", table)) - p.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", method), attribute.String("table", table)) + database.RecordRequestDuration(float64(time.Now().Sub(start).Milliseconds()), method, table) + database.IncreaseTotalRequests(method, table) return res } @@ -53,45 +47,37 @@ func (p *poolImpl) Exec(sql string, arguments ...interface{}) error { start := time.Now() _, err := p.conn.Exec(getTimeoutContext(), sql, arguments...) method, table := methodName(sql) - p.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", method), attribute.String("table", table)) - p.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", method), attribute.String("table", table)) + database.RecordRequestDuration(float64(time.Now().Sub(start).Milliseconds()), method, table) + database.IncreaseTotalRequests(method, table) return err } func (p *poolImpl) SendBatch(b *pgx.Batch) pgx.BatchResults { start := time.Now() res := p.conn.SendBatch(getTimeoutContext(), b) - p.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", "sendBatch")) - p.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", "sendBatch")) + database.RecordRequestDuration(float64(time.Now().Sub(start).Milliseconds()), "sendBatch", "") + database.IncreaseTotalRequests("sendBatch", "") return res } func (p *poolImpl) Begin() (*_Tx, error) { start := time.Now() tx, err := p.conn.Begin(context.Background()) - p.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", "begin")) - p.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", "begin")) - return &_Tx{tx, p.sqlRequestTime, p.sqlRequestCounter}, err + database.RecordRequestDuration(float64(time.Now().Sub(start).Milliseconds()), "begin", "") + database.IncreaseTotalRequests("begin", "") + return &_Tx{tx}, err } func (p *poolImpl) Close() { p.conn.Close() } -func NewPool(conn *pgxpool.Pool, sqlRequestTime syncfloat64.Histogram, sqlRequestCounter syncfloat64.Counter) (Pool, error) { +func NewPool(conn *pgxpool.Pool) (Pool, error) { if conn == nil { return nil, errors.New("conn is empty") } return &poolImpl{ - conn: conn, - sqlRequestTime: sqlRequestTime, - sqlRequestCounter: sqlRequestCounter, + conn: conn, }, nil } @@ -99,38 +85,30 @@ func NewPool(conn *pgxpool.Pool, sqlRequestTime syncfloat64.Histogram, sqlReques type _Tx struct { pgx.Tx - sqlRequestTime syncfloat64.Histogram - sqlRequestCounter syncfloat64.Counter } func (tx *_Tx) exec(sql string, args ...interface{}) error { start := time.Now() _, err := tx.Exec(context.Background(), sql, args...) method, table := methodName(sql) - tx.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", method), attribute.String("table", table)) - tx.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", method), attribute.String("table", table)) + database.RecordRequestDuration(float64(time.Now().Sub(start).Milliseconds()), method, table) + database.IncreaseTotalRequests(method, table) return err } func (tx *_Tx) rollback() error { start := time.Now() err := tx.Rollback(context.Background()) - tx.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", "rollback")) - tx.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", "rollback")) + database.RecordRequestDuration(float64(time.Now().Sub(start).Milliseconds()), "rollback", "") + database.IncreaseTotalRequests("rollback", "") return err } func (tx *_Tx) commit() error { start := time.Now() err := tx.Commit(context.Background()) - tx.sqlRequestTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds()), - attribute.String("method", "commit")) - tx.sqlRequestCounter.Add(context.Background(), 1, - attribute.String("method", "commit")) + database.RecordRequestDuration(float64(time.Now().Sub(start).Milliseconds()), "commit", "") + database.IncreaseTotalRequests("commit", "") return err } @@ -169,7 +147,8 @@ func methodName(sql string) (string, string) { case "update": table = strings.TrimSpace(parts[1]) case "insert": - table = strings.TrimSpace(parts[2]) + tableNameParts := strings.Split(strings.TrimSpace(parts[2]), "(") + table = tableNameParts[0] } return cmd, table } diff --git a/backend/pkg/db/postgres/project.go b/backend/pkg/db/postgres/project.go index f38161885..3239aefb7 100644 --- a/backend/pkg/db/postgres/project.go +++ b/backend/pkg/db/postgres/project.go @@ -7,12 +7,12 @@ import ( func (conn *Conn) GetProjectByKey(projectKey string) (*Project, error) { p := &Project{ProjectKey: projectKey} if err := conn.c.QueryRow(` - SELECT max_session_duration, sample_rate, project_id + SELECT max_session_duration, sample_rate, project_id, beacon_size FROM projects WHERE project_key=$1 AND active = true `, projectKey, - ).Scan(&p.MaxSessionDuration, &p.SampleRate, &p.ProjectID); err != nil { + ).Scan(&p.MaxSessionDuration, &p.SampleRate, &p.ProjectID, &p.BeaconSize); err != nil { return nil, err } return p, nil diff --git a/backend/pkg/db/postgres/session-updates.go b/backend/pkg/db/postgres/session-updates.go index 47e374355..fbed2cdc7 100644 --- a/backend/pkg/db/postgres/session-updates.go +++ b/backend/pkg/db/postgres/session-updates.go @@ -1,12 +1,14 @@ package postgres // Mechanism of combination several session updates into one -const sessionUpdateReq = `UPDATE sessions SET pages_count = pages_count + $1, events_count = events_count + $2 WHERE session_id = $3` +const sessionUpdateReq = `UPDATE sessions SET pages_count = pages_count + $1, events_count = events_count + $2, errors_count = errors_count + $3, issue_score = issue_score + $4 WHERE session_id = $5` type sessionUpdates struct { sessionID uint64 pages int events int + errors int + issues int } func NewSessionUpdates(sessionID uint64) *sessionUpdates { @@ -14,17 +16,24 @@ func NewSessionUpdates(sessionID uint64) *sessionUpdates { sessionID: sessionID, pages: 0, events: 0, + errors: 0, + issues: 0, } } -func (su *sessionUpdates) add(pages, events int) { +func (su *sessionUpdates) addEvents(pages, events int) { su.pages += pages su.events += events } +func (su *sessionUpdates) addIssues(errors, issues int) { + su.errors += errors + su.issues += issues +} + func (su *sessionUpdates) request() (string, []interface{}) { if su.pages == 0 && su.events == 0 { return "", nil } - return sessionUpdateReq, []interface{}{su.pages, su.events, su.sessionID} + return sessionUpdateReq, []interface{}{su.pages, su.events, su.errors, su.issues, su.sessionID} } diff --git a/backend/pkg/db/types/project.go b/backend/pkg/db/types/project.go index a5bc2e82c..85ac960ae 100644 --- a/backend/pkg/db/types/project.go +++ b/backend/pkg/db/types/project.go @@ -8,6 +8,7 @@ type Project struct { MaxSessionDuration int64 SampleRate byte SaveRequestPayloads bool + BeaconSize int64 Metadata1 *string Metadata2 *string Metadata3 *string diff --git a/backend/pkg/handlers/custom/eventMapper.go b/backend/pkg/handlers/custom/eventMapper.go index 3280e7ebc..a85ebbdf0 100644 --- a/backend/pkg/handlers/custom/eventMapper.go +++ b/backend/pkg/handlers/custom/eventMapper.go @@ -69,13 +69,6 @@ func (b *EventMapper) Handle(message Message, messageID uint64, timestamp uint64 Type: getResourceType(msg.Initiator, msg.URL), Success: msg.Duration != 0, } - case *RawCustomEvent: - return &CustomEvent{ - MessageID: messageID, - Timestamp: timestamp, - Name: msg.Name, - Payload: msg.Payload, - } case *CustomIssue: return &IssueEvent{ Type: "custom", @@ -84,32 +77,6 @@ func (b *EventMapper) Handle(message Message, messageID uint64, timestamp uint64 ContextString: msg.Name, Payload: msg.Payload, } - case *Fetch: - return &FetchEvent{ - MessageID: messageID, - Timestamp: msg.Timestamp, - Method: msg.Method, - URL: msg.URL, - Request: msg.Request, - Response: msg.Response, - Status: msg.Status, - Duration: msg.Duration, - } - case *GraphQL: - return &GraphQLEvent{ - MessageID: messageID, - Timestamp: timestamp, - OperationKind: msg.OperationKind, - OperationName: msg.OperationName, - Variables: msg.Variables, - Response: msg.Response, - } - case *StateAction: - return &StateActionEvent{ - MessageID: messageID, - Timestamp: timestamp, - Type: msg.Type, - } } return nil } diff --git a/backend/pkg/handlers/web/clickRage.go b/backend/pkg/handlers/web/clickRage.go index e22eb6454..6974ee1b0 100644 --- a/backend/pkg/handlers/web/clickRage.go +++ b/backend/pkg/handlers/web/clickRage.go @@ -22,6 +22,7 @@ type ClickRageDetector struct { firstInARawTimestamp uint64 firstInARawMessageId uint64 countsInARow int + url string } func (crd *ClickRageDetector) reset() { @@ -30,6 +31,7 @@ func (crd *ClickRageDetector) reset() { crd.firstInARawTimestamp = 0 crd.firstInARawMessageId = 0 crd.countsInARow = 0 + crd.url = "" } func (crd *ClickRageDetector) Build() Message { @@ -45,6 +47,7 @@ func (crd *ClickRageDetector) Build() Message { Payload: string(payload), Timestamp: crd.firstInARawTimestamp, MessageID: crd.firstInARawMessageId, + URL: crd.url, } return event } @@ -54,6 +57,9 @@ func (crd *ClickRageDetector) Build() Message { func (crd *ClickRageDetector) Handle(message Message, messageID uint64, timestamp uint64) Message { switch msg := message.(type) { case *MouseClick: + if crd.url == "" && msg.Url != "" { + crd.url = msg.Url + } // TODO: check if we it is ok to capture clickRage event without the connected ClickEvent in db. if msg.Label == "" { return crd.Build() @@ -69,6 +75,9 @@ func (crd *ClickRageDetector) Handle(message Message, messageID uint64, timestam crd.firstInARawTimestamp = timestamp crd.firstInARawMessageId = messageID crd.countsInARow = 1 + if crd.url == "" && msg.Url != "" { + crd.url = msg.Url + } return event } return nil diff --git a/backend/pkg/handlers/web/deadClick.go b/backend/pkg/handlers/web/deadClick.go index 6377b074e..434e6c1ce 100644 --- a/backend/pkg/handlers/web/deadClick.go +++ b/backend/pkg/handlers/web/deadClick.go @@ -21,7 +21,7 @@ import ( Output event: IssueEvent */ -const CLICK_RELATION_TIME = 1400 +const CLICK_RELATION_TIME = 1234 type DeadClickDetector struct { lastTimestamp uint64 diff --git a/backend/pkg/handlers/web/networkIssue.go b/backend/pkg/handlers/web/networkIssue.go index 67522c850..20ef412dd 100644 --- a/backend/pkg/handlers/web/networkIssue.go +++ b/backend/pkg/handlers/web/networkIssue.go @@ -7,7 +7,7 @@ import ( /* Handler name: NetworkIssue Input events: ResourceTiming, - Fetch + NetworkRequest Output event: IssueEvent */ @@ -19,21 +19,7 @@ func (f *NetworkIssueDetector) Build() Message { func (f *NetworkIssueDetector) Handle(message Message, messageID uint64, timestamp uint64) Message { switch msg := message.(type) { - // case *ResourceTiming: - // success := msg.Duration != 0 // The only available way here - // if !success { - // issueType := "missing_resource" - // if msg.Initiator == "fetch" || msg.Initiator == "xmlhttprequest" { - // issueType = "bad_request" - // } - // return &IssueEvent{ - // Type: issueType, - // MessageID: messageID, - // Timestamp: msg.Timestamp, - // ContextString: msg.URL, - // } - // } - case *Fetch: + case *NetworkRequest: if msg.Status >= 400 { return &IssueEvent{ Type: "bad_request", diff --git a/backend/pkg/log/queue.go b/backend/pkg/log/queue.go deleted file mode 100644 index e9ac5dc1f..000000000 --- a/backend/pkg/log/queue.go +++ /dev/null @@ -1,79 +0,0 @@ -package log - -import ( - "fmt" - "log" - "time" - - "openreplay/backend/pkg/messages" -) - -type partitionStats struct { - maxts int64 - mints int64 - lastts int64 - lastID uint64 - count int -} - -// Update partition statistic -func (prt *partitionStats) update(m *messages.BatchInfo) { - if prt.maxts < m.Timestamp() { - prt.maxts = m.Timestamp() - } - if prt.mints > m.Timestamp() || prt.mints == 0 { - prt.mints = m.Timestamp() - } - prt.lastts = m.Timestamp() - prt.lastID = m.ID() - prt.count += 1 -} - -type queueStats struct { - prts map[int32]*partitionStats - tick <-chan time.Time -} - -type QueueStats interface { - Collect(msg messages.Message) -} - -func NewQueueStats(sec int) *queueStats { - return &queueStats{ - prts: make(map[int32]*partitionStats), - tick: time.Tick(time.Duration(sec) * time.Second), - } -} - -// Collect writes new data to partition statistic -func (qs *queueStats) Collect(msg messages.Message) { - prti := int32(msg.SessionID() % 16) // TODO use GetKeyPartition from kafka/key.go - prt, ok := qs.prts[prti] - if !ok { - qs.prts[prti] = &partitionStats{} - prt = qs.prts[prti] - } - prt.update(msg.Meta().Batch()) - - select { - case <-qs.tick: - qs.log() - qs.reset() - default: - } -} - -// Print to console collected statistics -func (qs *queueStats) log() { - s := "Queue Statistics: " - for i, p := range qs.prts { - s = fmt.Sprintf("%v | %v:: lastTS %v, lastID %v, count %v, maxTS %v, minTS %v", - s, i, p.lastts, p.lastID, p.count, p.maxts, p.mints) - } - log.Println(s) -} - -// Clear all queue partitions -func (qs *queueStats) reset() { - qs.prts = make(map[int32]*partitionStats) -} diff --git a/backend/pkg/messages/bytes.go b/backend/pkg/messages/bytes.go new file mode 100644 index 000000000..00d161d97 --- /dev/null +++ b/backend/pkg/messages/bytes.go @@ -0,0 +1,130 @@ +package messages + +import ( + "encoding/binary" + "errors" + "fmt" + "io" +) + +type BytesReader interface { + ReadSize() (uint64, error) + ReadByte() (byte, error) + ReadUint() (uint64, error) + ReadInt() (int64, error) + ReadBoolean() (bool, error) + ReadString() (string, error) + ReadIndex() (uint64, error) + Data() []byte + Pointer() int64 + SetPointer(p int64) +} + +type bytesReaderImpl struct { + data []byte + curr int64 +} + +func NewBytesReader(data []byte) BytesReader { + return &bytesReaderImpl{ + data: data, + } +} + +func (m *bytesReaderImpl) ReadSize() (uint64, error) { + if len(m.data)-int(m.curr) < 3 { + return 0, fmt.Errorf("out of range") + } + var size uint64 + for i, b := range m.data[m.curr : m.curr+3] { + size += uint64(b) << (8 * i) + } + m.curr += 3 + return size, nil +} + +func (m *bytesReaderImpl) ReadByte() (byte, error) { + if int(m.curr) >= len(m.data) { + return 0, io.EOF + } + m.curr++ + return m.data[m.curr-1], nil +} + +func (m *bytesReaderImpl) ReadUint() (uint64, error) { + var x uint64 + var s uint + i := 0 + for { + b, err := m.ReadByte() + if err != nil { + return x, err + } + if b < 0x80 { + if i > 9 || i == 9 && b > 1 { + return x, errors.New("uint overflow") + } + return x | uint64(b)<> 1) + if err != nil { + return x, err + } + if ux&1 != 0 { + x = ^x + } + return x, err +} + +func (m *bytesReaderImpl) ReadBoolean() (bool, error) { + val, err := m.ReadByte() + if err != nil { + return false, err + } + return val == 1, nil +} + +func (m *bytesReaderImpl) ReadString() (string, error) { + l, err := m.ReadUint() + if err != nil { + return "", err + } + if l > 10e6 { + return "", errors.New("too long string") + } + if len(m.data)-int(m.curr) < int(l) { + return "", fmt.Errorf("out of range") + } + str := string(m.data[m.curr : int(m.curr)+int(l)]) + m.curr += int64(l) + return str, nil +} + +func (m *bytesReaderImpl) ReadIndex() (uint64, error) { + if len(m.data)-int(m.curr) < 8 { + return 0, fmt.Errorf("out of range") + } + size := binary.LittleEndian.Uint64(m.data[m.curr : m.curr+8]) + m.curr += 8 + return size, nil +} + +func (m *bytesReaderImpl) Data() []byte { + return m.data +} + +func (m *bytesReaderImpl) Pointer() int64 { + return m.curr +} + +func (m *bytesReaderImpl) SetPointer(p int64) { + m.curr = p +} diff --git a/backend/pkg/messages/cache.go b/backend/pkg/messages/cache.go new file mode 100644 index 000000000..b3c83a0e3 --- /dev/null +++ b/backend/pkg/messages/cache.go @@ -0,0 +1,22 @@ +package messages + +type pageLocations struct { + urls map[uint64]string +} + +func NewPageLocations() *pageLocations { + return &pageLocations{urls: make(map[uint64]string)} +} + +func (p *pageLocations) Set(sessID uint64, url string) { + p.urls[sessID] = url +} + +func (p *pageLocations) Get(sessID uint64) string { + url := p.urls[sessID] + return url +} + +func (p *pageLocations) Delete(sessID uint64) { + delete(p.urls, sessID) +} diff --git a/backend/pkg/messages/filters.go b/backend/pkg/messages/filters.go index 399c1863c..30e266194 100644 --- a/backend/pkg/messages/filters.go +++ b/backend/pkg/messages/filters.go @@ -2,7 +2,7 @@ package messages func IsReplayerType(id int) bool { - return 80 != id && 81 != id && 82 != id && 1 != id && 3 != id && 17 != id && 23 != id && 24 != id && 25 != id && 26 != id && 27 != id && 28 != id && 29 != id && 30 != id && 31 != id && 32 != id && 33 != id && 35 != id && 36 != id && 42 != id && 43 != id && 50 != id && 51 != id && 52 != id && 53 != id && 56 != id && 62 != id && 63 != id && 64 != id && 66 != id && 78 != id && 126 != id && 127 != id && 107 != id && 91 != id && 92 != id && 94 != id && 95 != id && 97 != id && 98 != id && 99 != id && 101 != id && 104 != id && 110 != id && 111 != id + return 1 != id && 3 != id && 17 != id && 23 != id && 24 != id && 25 != id && 26 != id && 27 != id && 28 != id && 29 != id && 30 != id && 31 != id && 32 != id && 33 != id && 35 != id && 42 != id && 52 != id && 56 != id && 62 != id && 63 != id && 64 != id && 66 != id && 78 != id && 80 != id && 81 != id && 82 != id && 125 != id && 126 != id && 127 != id && 107 != id && 91 != id && 92 != id && 94 != id && 95 != id && 97 != id && 98 != id && 99 != id && 101 != id && 104 != id && 110 != id && 111 != id } func IsIOSType(id int) bool { @@ -10,5 +10,5 @@ func IsIOSType(id int) bool { } func IsDOMType(id int) bool { - return 0 == id || 4 == id || 5 == id || 6 == id || 7 == id || 8 == id || 9 == id || 10 == id || 11 == id || 12 == id || 13 == id || 14 == id || 15 == id || 16 == id || 18 == id || 19 == id || 20 == id || 37 == id || 38 == id || 49 == id || 54 == id || 55 == id || 57 == id || 58 == id || 59 == id || 60 == id || 61 == id || 67 == id || 69 == id || 70 == id || 71 == id || 72 == id || 73 == id || 74 == id || 75 == id || 76 == id || 77 == id || 90 == id || 93 == id || 96 == id || 100 == id || 102 == id || 103 == id || 105 == id + return 0 == id || 4 == id || 5 == id || 6 == id || 7 == id || 8 == id || 9 == id || 10 == id || 11 == id || 12 == id || 13 == id || 14 == id || 15 == id || 16 == id || 18 == id || 19 == id || 20 == id || 37 == id || 38 == id || 49 == id || 50 == id || 51 == id || 54 == id || 55 == id || 57 == id || 58 == id || 59 == id || 60 == id || 61 == id || 67 == id || 69 == id || 70 == id || 71 == id || 72 == id || 73 == id || 74 == id || 75 == id || 76 == id || 77 == id || 90 == id || 93 == id || 96 == id || 100 == id || 102 == id || 103 == id || 105 == id } diff --git a/backend/pkg/messages/iterator-ender.go b/backend/pkg/messages/iterator-ender.go new file mode 100644 index 000000000..6ca7db034 --- /dev/null +++ b/backend/pkg/messages/iterator-ender.go @@ -0,0 +1,179 @@ +package messages + +import ( + "fmt" + "log" +) + +type enderMessageIteratorImpl struct { + filter map[int]struct{} + preFilter map[int]struct{} + handler MessageHandler + autoDecode bool + version uint64 + size uint64 + canSkip bool + broken bool + messageInfo *message + batchInfo *BatchInfo + urls *pageLocations +} + +func NewEnderMessageIterator(messageHandler MessageHandler, messageFilter []int, autoDecode bool) MessageIterator { + iter := &enderMessageIteratorImpl{ + handler: messageHandler, + autoDecode: autoDecode, + urls: NewPageLocations(), + } + if len(messageFilter) != 0 { + filter := make(map[int]struct{}, len(messageFilter)) + for _, msgType := range messageFilter { + filter[msgType] = struct{}{} + } + iter.filter = filter + } + iter.preFilter = map[int]struct{}{ + MsgBatchMetadata: {}, MsgBatchMeta: {}, MsgTimestamp: {}, + MsgSessionStart: {}, MsgSessionEnd: {}, MsgSetPageLocation: {}, + } + return iter +} + +func (i *enderMessageIteratorImpl) prepareVars(batchInfo *BatchInfo) { + i.batchInfo = batchInfo + i.messageInfo = &message{batch: batchInfo} + i.version = 0 + i.canSkip = false + i.broken = false + i.size = 0 +} + +func (i *enderMessageIteratorImpl) Iterate(batchData []byte, batchInfo *BatchInfo) { + // Create new message reader + reader := NewMessageReader(batchData) + + // Pre-decode batch data + if err := reader.Parse(); err != nil { + log.Printf("pre-decode batch err: %s, info: %s", err, batchInfo.Info()) + return + } + + // Prepare iterator before processing messages in batch + i.prepareVars(batchInfo) + + // Store last timestamp message here + var lastMessage Message + + for reader.Next() { + // Increase message index (can be overwritten by batch info message) + i.messageInfo.Index++ + + msg := reader.Message() + + // Preprocess "system" messages + if _, ok := i.preFilter[msg.TypeID()]; ok { + msg = msg.Decode() + if msg == nil { + log.Printf("decode error, type: %d, info: %s", msg.TypeID(), i.batchInfo.Info()) + return + } + msg = transformDeprecated(msg) + if err := i.preprocessing(msg); err != nil { + log.Printf("message preprocessing err: %s", err) + return + } + } + + // Skip messages we don't have in filter + if i.filter != nil { + if _, ok := i.filter[msg.TypeID()]; !ok { + continue + } + } + + if i.autoDecode { + msg = msg.Decode() + if msg == nil { + log.Printf("decode error, type: %d, info: %s", msg.TypeID(), i.batchInfo.Info()) + return + } + } + + // Set meta information for message + msg.Meta().SetMeta(i.messageInfo) + + // Update last timestamp message + lastMessage = msg + } + + if lastMessage != nil { + i.handler(lastMessage) + } + +} + +func (i *enderMessageIteratorImpl) zeroTsLog(msgType string) { + log.Printf("zero timestamp in %s, info: %s", msgType, i.batchInfo.Info()) +} + +func (i *enderMessageIteratorImpl) preprocessing(msg Message) error { + switch m := msg.(type) { + case *BatchMetadata: + if i.messageInfo.Index > 1 { // Might be several 0-0 BatchMeta in a row without an error though + return fmt.Errorf("batchMetadata found at the end of the batch, info: %s", i.batchInfo.Info()) + } + if m.Version > 1 { + return fmt.Errorf("incorrect batch version: %d, skip current batch, info: %s", i.version, i.batchInfo.Info()) + } + i.messageInfo.Index = m.PageNo<<32 + m.FirstIndex // 2^32 is the maximum count of messages per page (ha-ha) + i.messageInfo.Timestamp = m.Timestamp + if m.Timestamp == 0 { + i.zeroTsLog("BatchMetadata") + } + i.messageInfo.Url = m.Location + i.version = m.Version + i.batchInfo.version = m.Version + + case *BatchMeta: // Is not required to be present in batch since IOS doesn't have it (though we might change it) + if i.messageInfo.Index > 1 { // Might be several 0-0 BatchMeta in a row without an error though + return fmt.Errorf("batchMeta found at the end of the batch, info: %s", i.batchInfo.Info()) + } + i.messageInfo.Index = m.PageNo<<32 + m.FirstIndex // 2^32 is the maximum count of messages per page (ha-ha) + i.messageInfo.Timestamp = m.Timestamp + if m.Timestamp == 0 { + i.zeroTsLog("BatchMeta") + } + // Try to get saved session's page url + if savedURL := i.urls.Get(i.messageInfo.batch.sessionID); savedURL != "" { + i.messageInfo.Url = savedURL + } + + case *Timestamp: + i.messageInfo.Timestamp = int64(m.Timestamp) + if m.Timestamp == 0 { + i.zeroTsLog("Timestamp") + } + + case *SessionStart: + i.messageInfo.Timestamp = int64(m.Timestamp) + if m.Timestamp == 0 { + i.zeroTsLog("SessionStart") + log.Printf("zero session start, project: %d, UA: %s, tracker: %s, info: %s", + m.ProjectID, m.UserAgent, m.TrackerVersion, i.batchInfo.Info()) + } + + case *SessionEnd: + i.messageInfo.Timestamp = int64(m.Timestamp) + if m.Timestamp == 0 { + i.zeroTsLog("SessionEnd") + } + // Delete session from urls cache layer + i.urls.Delete(i.messageInfo.batch.sessionID) + + case *SetPageLocation: + i.messageInfo.Url = m.URL + // Save session page url in cache for using in next batches + i.urls.Set(i.messageInfo.batch.sessionID, m.URL) + } + return nil +} diff --git a/backend/pkg/messages/iterator-sink.go b/backend/pkg/messages/iterator-sink.go new file mode 100644 index 000000000..be12b63eb --- /dev/null +++ b/backend/pkg/messages/iterator-sink.go @@ -0,0 +1,181 @@ +package messages + +import ( + "fmt" + "log" + "openreplay/backend/pkg/metrics/sink" +) + +type sinkMessageIteratorImpl struct { + filter map[int]struct{} + preFilter map[int]struct{} + handler MessageHandler + autoDecode bool + version uint64 + size uint64 + canSkip bool + broken bool + messageInfo *message + batchInfo *BatchInfo + urls *pageLocations +} + +func NewSinkMessageIterator(messageHandler MessageHandler, messageFilter []int, autoDecode bool) MessageIterator { + iter := &sinkMessageIteratorImpl{ + handler: messageHandler, + autoDecode: autoDecode, + urls: NewPageLocations(), + } + if len(messageFilter) != 0 { + filter := make(map[int]struct{}, len(messageFilter)) + for _, msgType := range messageFilter { + filter[msgType] = struct{}{} + } + iter.filter = filter + } + iter.preFilter = map[int]struct{}{ + MsgBatchMetadata: {}, MsgBatchMeta: {}, MsgTimestamp: {}, + MsgSessionStart: {}, MsgSessionEnd: {}, MsgSetPageLocation: {}, + } + return iter +} + +func (i *sinkMessageIteratorImpl) prepareVars(batchInfo *BatchInfo) { + i.batchInfo = batchInfo + i.messageInfo = &message{batch: batchInfo} + i.version = 0 + i.canSkip = false + i.broken = false + i.size = 0 +} + +func (i *sinkMessageIteratorImpl) sendBatchEnd() { + i.handler(nil) +} + +func (i *sinkMessageIteratorImpl) Iterate(batchData []byte, batchInfo *BatchInfo) { + sink.RecordBatchSize(float64(len(batchData))) + sink.IncreaseTotalBatches() + // Create new message reader + reader := NewMessageReader(batchData) + + // Pre-decode batch data + if err := reader.Parse(); err != nil { + log.Printf("pre-decode batch err: %s, info: %s", err, batchInfo.Info()) + return + } + + // Prepare iterator before processing messages in batch + i.prepareVars(batchInfo) + + for reader.Next() { + // Increase message index (can be overwritten by batch info message) + i.messageInfo.Index++ + + msg := reader.Message() + + // Preprocess "system" messages + if _, ok := i.preFilter[msg.TypeID()]; ok { + msg = msg.Decode() + if msg == nil { + log.Printf("decode error, type: %d, info: %s", msg.TypeID(), i.batchInfo.Info()) + break + } + msg = transformDeprecated(msg) + if err := i.preprocessing(msg); err != nil { + log.Printf("message preprocessing err: %s", err) + break + } + } + + // Skip messages we don't have in filter + if i.filter != nil { + if _, ok := i.filter[msg.TypeID()]; !ok { + continue + } + } + + if i.autoDecode { + msg = msg.Decode() + if msg == nil { + log.Printf("decode error, type: %d, info: %s", msg.TypeID(), i.batchInfo.Info()) + break + } + } + + // Set meta information for message + msg.Meta().SetMeta(i.messageInfo) + + // Process message + i.handler(msg) + } + + // Inform sink about end of batch + i.sendBatchEnd() +} + +func (i *sinkMessageIteratorImpl) zeroTsLog(msgType string) { + log.Printf("zero timestamp in %s, info: %s", msgType, i.batchInfo.Info()) +} + +func (i *sinkMessageIteratorImpl) preprocessing(msg Message) error { + switch m := msg.(type) { + case *BatchMetadata: + if i.messageInfo.Index > 1 { // Might be several 0-0 BatchMeta in a row without an error though + return fmt.Errorf("batchMetadata found at the end of the batch, info: %s", i.batchInfo.Info()) + } + if m.Version > 1 { + return fmt.Errorf("incorrect batch version: %d, skip current batch, info: %s", i.version, i.batchInfo.Info()) + } + i.messageInfo.Index = m.PageNo<<32 + m.FirstIndex // 2^32 is the maximum count of messages per page (ha-ha) + i.messageInfo.Timestamp = m.Timestamp + if m.Timestamp == 0 { + i.zeroTsLog("BatchMetadata") + } + i.messageInfo.Url = m.Location + i.version = m.Version + i.batchInfo.version = m.Version + + case *BatchMeta: // Is not required to be present in batch since IOS doesn't have it (though we might change it) + if i.messageInfo.Index > 1 { // Might be several 0-0 BatchMeta in a row without an error though + return fmt.Errorf("batchMeta found at the end of the batch, info: %s", i.batchInfo.Info()) + } + i.messageInfo.Index = m.PageNo<<32 + m.FirstIndex // 2^32 is the maximum count of messages per page (ha-ha) + i.messageInfo.Timestamp = m.Timestamp + if m.Timestamp == 0 { + i.zeroTsLog("BatchMeta") + } + // Try to get saved session's page url + if savedURL := i.urls.Get(i.messageInfo.batch.sessionID); savedURL != "" { + i.messageInfo.Url = savedURL + } + + case *Timestamp: + i.messageInfo.Timestamp = int64(m.Timestamp) + if m.Timestamp == 0 { + i.zeroTsLog("Timestamp") + } + + case *SessionStart: + i.messageInfo.Timestamp = int64(m.Timestamp) + if m.Timestamp == 0 { + i.zeroTsLog("SessionStart") + log.Printf("zero session start, project: %d, UA: %s, tracker: %s, info: %s", + m.ProjectID, m.UserAgent, m.TrackerVersion, i.batchInfo.Info()) + } + + case *SessionEnd: + i.messageInfo.Timestamp = int64(m.Timestamp) + if m.Timestamp == 0 { + i.zeroTsLog("SessionEnd") + } + // Delete session from urls cache layer + i.urls.Delete(i.messageInfo.batch.sessionID) + + case *SetPageLocation: + i.messageInfo.Url = m.URL + // Save session page url in cache for using in next batches + i.urls.Set(i.messageInfo.batch.sessionID, m.URL) + } + return nil +} diff --git a/backend/pkg/messages/iterator.go b/backend/pkg/messages/iterator.go index 7b7991b19..f7b014d30 100644 --- a/backend/pkg/messages/iterator.go +++ b/backend/pkg/messages/iterator.go @@ -1,9 +1,7 @@ package messages import ( - "bytes" "fmt" - "io" "log" ) @@ -26,10 +24,15 @@ type messageIteratorImpl struct { broken bool messageInfo *message batchInfo *BatchInfo + urls *pageLocations } func NewMessageIterator(messageHandler MessageHandler, messageFilter []int, autoDecode bool) MessageIterator { - iter := &messageIteratorImpl{handler: messageHandler, autoDecode: autoDecode} + iter := &messageIteratorImpl{ + handler: messageHandler, + autoDecode: autoDecode, + urls: NewPageLocations(), + } if len(messageFilter) != 0 { filter := make(map[int]struct{}, len(messageFilter)) for _, msgType := range messageFilter { @@ -40,7 +43,7 @@ func NewMessageIterator(messageHandler MessageHandler, messageFilter []int, auto iter.preFilter = map[int]struct{}{ MsgBatchMetadata: {}, MsgBatchMeta: {}, MsgTimestamp: {}, MsgSessionStart: {}, MsgSessionEnd: {}, MsgSetPageLocation: {}, - MsgSessionEndDeprecated: {}} + } return iter } @@ -54,68 +57,24 @@ func (i *messageIteratorImpl) prepareVars(batchInfo *BatchInfo) { } func (i *messageIteratorImpl) Iterate(batchData []byte, batchInfo *BatchInfo) { + // Create new message reader + reader := NewMessageReader(batchData) + + // Pre-decode batch data + if err := reader.Parse(); err != nil { + log.Printf("pre-decode batch err: %s, info: %s", err, batchInfo.Info()) + return + } + // Prepare iterator before processing messages in batch i.prepareVars(batchInfo) - // Initialize batch reader - reader := bytes.NewReader(batchData) - - // Process until end of batch or parsing error - for { + for reader.Next() { // Increase message index (can be overwritten by batch info message) i.messageInfo.Index++ - if i.broken { - log.Printf("skipping broken batch, info: %s", i.batchInfo.Info()) - return - } - - if i.canSkip { - if _, err := reader.Seek(int64(i.size), io.SeekCurrent); err != nil { - log.Printf("can't skip message: %s, info: %s", err, i.batchInfo.Info()) - return - } - } - i.canSkip = false - - // Read message type - msgType, err := ReadUint(reader) - if err != nil { - if err != io.EOF { - log.Printf("can't read message type: %s, info: %s", err, i.batchInfo.Info()) - } - return - } - - var msg Message - // Read message body (and decode if protocol version less than 1) - if i.version > 0 && messageHasSize(msgType) { - // Read message size if it is a new protocol version - i.size, err = ReadSize(reader) - if err != nil { - log.Printf("can't read message size: %s, info: %s", err, i.batchInfo.Info()) - return - } - msg = &RawMessage{ - tp: msgType, - size: i.size, - reader: reader, - raw: batchData, - skipped: &i.canSkip, - broken: &i.broken, - meta: i.messageInfo, - } - i.canSkip = true - } else { - msg, err = ReadMessage(msgType, reader) - if err != nil { - if err != io.EOF { - log.Printf("can't read message body: %s, info: %s", err, i.batchInfo.Info()) - } - return - } - msg = transformDeprecated(msg) - } + msg := reader.Message() + msgType := msg.TypeID() // Preprocess "system" messages if _, ok := i.preFilter[msg.TypeID()]; ok { @@ -124,6 +83,7 @@ func (i *messageIteratorImpl) Iterate(batchData []byte, batchInfo *BatchInfo) { log.Printf("decode error, type: %d, info: %s", msgType, i.batchInfo.Info()) return } + msg = transformDeprecated(msg) if err := i.preprocessing(msg); err != nil { log.Printf("message preprocessing err: %s", err) return @@ -171,7 +131,7 @@ func (i *messageIteratorImpl) preprocessing(msg Message) error { if m.Timestamp == 0 { i.zeroTsLog("BatchMetadata") } - i.messageInfo.Url = m.Url + i.messageInfo.Url = m.Location i.version = m.Version i.batchInfo.version = m.Version @@ -184,6 +144,10 @@ func (i *messageIteratorImpl) preprocessing(msg Message) error { if m.Timestamp == 0 { i.zeroTsLog("BatchMeta") } + // Try to get saved session's page url + if savedURL := i.urls.Get(i.messageInfo.batch.sessionID); savedURL != "" { + i.messageInfo.Url = savedURL + } case *Timestamp: i.messageInfo.Timestamp = int64(m.Timestamp) @@ -204,9 +168,13 @@ func (i *messageIteratorImpl) preprocessing(msg Message) error { if m.Timestamp == 0 { i.zeroTsLog("SessionEnd") } + // Delete session from urls cache layer + i.urls.Delete(i.messageInfo.batch.sessionID) case *SetPageLocation: i.messageInfo.Url = m.URL + // Save session page url in cache for using in next batches + i.urls.Set(i.messageInfo.batch.sessionID, m.URL) } return nil } diff --git a/backend/pkg/messages/legacy-message-transform.go b/backend/pkg/messages/legacy-message-transform.go index 5e2bd3ed7..223178d15 100644 --- a/backend/pkg/messages/legacy-message-transform.go +++ b/backend/pkg/messages/legacy-message-transform.go @@ -9,10 +9,26 @@ func transformDeprecated(msg Message) Message { Payload: m.Payload, Metadata: "{}", } - case *SessionEndDeprecated: - return &SessionEnd{ + case *Fetch: + return &NetworkRequest{ + Type: "fetch", + Method: m.Method, + URL: m.URL, + Request: m.Request, + Response: m.Response, + Status: m.Status, + Timestamp: m.Timestamp, + Duration: m.Duration, + } + case *IssueEventDeprecated: + return &IssueEvent{ + MessageID: m.MessageID, Timestamp: m.Timestamp, - EncryptionKey: "", + Type: m.Type, + ContextString: m.ContextString, + Context: m.Context, + Payload: m.Payload, + URL: "", } } return msg diff --git a/backend/pkg/messages/message.go b/backend/pkg/messages/message.go index 7bb2572eb..3a8e029d5 100644 --- a/backend/pkg/messages/message.go +++ b/backend/pkg/messages/message.go @@ -4,7 +4,6 @@ import "fmt" type Message interface { Encode() []byte - EncodeWithIndex() []byte Decode() Message TypeID() int Meta() *message diff --git a/backend/pkg/messages/messages.go b/backend/pkg/messages/messages.go index 138f8dcb7..a96f98de8 100644 --- a/backend/pkg/messages/messages.go +++ b/backend/pkg/messages/messages.go @@ -1,318 +1,108 @@ // Auto-generated, do not edit package messages -import "encoding/binary" - const ( - MsgBatchMeta = 80 - - MsgBatchMetadata = 81 - - MsgPartitionedMessage = 82 - - MsgTimestamp = 0 - - MsgSessionStart = 1 - - MsgSessionEndDeprecated = 3 - - MsgSetPageLocation = 4 - - MsgSetViewportSize = 5 - - MsgSetViewportScroll = 6 - - MsgCreateDocument = 7 - - MsgCreateElementNode = 8 - - MsgCreateTextNode = 9 - - MsgMoveNode = 10 - - MsgRemoveNode = 11 - - MsgSetNodeAttribute = 12 - - MsgRemoveNodeAttribute = 13 - - MsgSetNodeData = 14 - - MsgSetCSSData = 15 - - MsgSetNodeScroll = 16 - - MsgSetInputTarget = 17 - - MsgSetInputValue = 18 - - MsgSetInputChecked = 19 - - MsgMouseMove = 20 - - MsgConsoleLog = 22 - - MsgPageLoadTiming = 23 - - MsgPageRenderTiming = 24 - - MsgJSExceptionDeprecated = 25 - - MsgIntegrationEvent = 26 - - MsgRawCustomEvent = 27 - - MsgUserID = 28 - - MsgUserAnonymousID = 29 - - MsgMetadata = 30 - - MsgPageEvent = 31 - - MsgInputEvent = 32 - - MsgClickEvent = 33 - - MsgResourceEvent = 35 - - MsgCustomEvent = 36 - - MsgCSSInsertRule = 37 - - MsgCSSDeleteRule = 38 - - MsgFetch = 39 - - MsgProfiler = 40 - - MsgOTable = 41 - - MsgStateAction = 42 - - MsgStateActionEvent = 43 - - MsgRedux = 44 - - MsgVuex = 45 - - MsgMobX = 46 - - MsgNgRx = 47 - - MsgGraphQL = 48 - - MsgPerformanceTrack = 49 - - MsgGraphQLEvent = 50 - - MsgFetchEvent = 51 - - MsgDOMDrop = 52 - - MsgResourceTiming = 53 - - MsgConnectionInformation = 54 - - MsgSetPageVisibility = 55 - - MsgPerformanceTrackAggr = 56 - - MsgLoadFontFace = 57 - - MsgSetNodeFocus = 58 - - MsgLongTask = 59 - - MsgSetNodeAttributeURLBased = 60 - - MsgSetCSSDataURLBased = 61 - - MsgIssueEvent = 62 - - MsgTechnicalInfo = 63 - - MsgCustomIssue = 64 - - MsgAssetCache = 66 - - MsgCSSInsertRuleURLBased = 67 - - MsgMouseClick = 69 - - MsgCreateIFrameDocument = 70 - - MsgAdoptedSSReplaceURLBased = 71 - - MsgAdoptedSSReplace = 72 - + MsgTimestamp = 0 + MsgSessionStart = 1 + MsgSessionEndDeprecated = 3 + MsgSetPageLocation = 4 + MsgSetViewportSize = 5 + MsgSetViewportScroll = 6 + MsgCreateDocument = 7 + MsgCreateElementNode = 8 + MsgCreateTextNode = 9 + MsgMoveNode = 10 + MsgRemoveNode = 11 + MsgSetNodeAttribute = 12 + MsgRemoveNodeAttribute = 13 + MsgSetNodeData = 14 + MsgSetCSSData = 15 + MsgSetNodeScroll = 16 + MsgSetInputTarget = 17 + MsgSetInputValue = 18 + MsgSetInputChecked = 19 + MsgMouseMove = 20 + MsgNetworkRequest = 21 + MsgConsoleLog = 22 + MsgPageLoadTiming = 23 + MsgPageRenderTiming = 24 + MsgJSExceptionDeprecated = 25 + MsgIntegrationEvent = 26 + MsgCustomEvent = 27 + MsgUserID = 28 + MsgUserAnonymousID = 29 + MsgMetadata = 30 + MsgPageEvent = 31 + MsgInputEvent = 32 + MsgClickEvent = 33 + MsgResourceEvent = 35 + MsgCSSInsertRule = 37 + MsgCSSDeleteRule = 38 + MsgFetch = 39 + MsgProfiler = 40 + MsgOTable = 41 + MsgStateAction = 42 + MsgRedux = 44 + MsgVuex = 45 + MsgMobX = 46 + MsgNgRx = 47 + MsgGraphQL = 48 + MsgPerformanceTrack = 49 + MsgStringDict = 50 + MsgSetNodeAttributeDict = 51 + MsgDOMDrop = 52 + MsgResourceTiming = 53 + MsgConnectionInformation = 54 + MsgSetPageVisibility = 55 + MsgPerformanceTrackAggr = 56 + MsgLoadFontFace = 57 + MsgSetNodeFocus = 58 + MsgLongTask = 59 + MsgSetNodeAttributeURLBased = 60 + MsgSetCSSDataURLBased = 61 + MsgIssueEventDeprecated = 62 + MsgTechnicalInfo = 63 + MsgCustomIssue = 64 + MsgAssetCache = 66 + MsgCSSInsertRuleURLBased = 67 + MsgMouseClick = 69 + MsgCreateIFrameDocument = 70 + MsgAdoptedSSReplaceURLBased = 71 + MsgAdoptedSSReplace = 72 MsgAdoptedSSInsertRuleURLBased = 73 - - MsgAdoptedSSInsertRule = 74 - - MsgAdoptedSSDeleteRule = 75 - - MsgAdoptedSSAddOwner = 76 - - MsgAdoptedSSRemoveOwner = 77 - - MsgZustand = 79 - - MsgJSException = 78 - - MsgSessionEnd = 126 - - MsgSessionSearch = 127 - - MsgIOSBatchMeta = 107 - - MsgIOSSessionStart = 90 - - MsgIOSSessionEnd = 91 - - MsgIOSMetadata = 92 - - MsgIOSCustomEvent = 93 - - MsgIOSUserID = 94 - - MsgIOSUserAnonymousID = 95 - - MsgIOSScreenChanges = 96 - - MsgIOSCrash = 97 - - MsgIOSScreenEnter = 98 - - MsgIOSScreenLeave = 99 - - MsgIOSClickEvent = 100 - - MsgIOSInputEvent = 101 - - MsgIOSPerformanceEvent = 102 - - MsgIOSLog = 103 - - MsgIOSInternalError = 104 - - MsgIOSNetworkCall = 105 - - MsgIOSPerformanceAggregated = 110 - - MsgIOSIssueEvent = 111 + MsgAdoptedSSInsertRule = 74 + MsgAdoptedSSDeleteRule = 75 + MsgAdoptedSSAddOwner = 76 + MsgAdoptedSSRemoveOwner = 77 + MsgJSException = 78 + MsgZustand = 79 + MsgBatchMeta = 80 + MsgBatchMetadata = 81 + MsgPartitionedMessage = 82 + MsgIssueEvent = 125 + MsgSessionEnd = 126 + MsgSessionSearch = 127 + MsgIOSBatchMeta = 107 + MsgIOSSessionStart = 90 + MsgIOSSessionEnd = 91 + MsgIOSMetadata = 92 + MsgIOSCustomEvent = 93 + MsgIOSUserID = 94 + MsgIOSUserAnonymousID = 95 + MsgIOSScreenChanges = 96 + MsgIOSCrash = 97 + MsgIOSScreenEnter = 98 + MsgIOSScreenLeave = 99 + MsgIOSClickEvent = 100 + MsgIOSInputEvent = 101 + MsgIOSPerformanceEvent = 102 + MsgIOSLog = 103 + MsgIOSInternalError = 104 + MsgIOSNetworkCall = 105 + MsgIOSPerformanceAggregated = 110 + MsgIOSIssueEvent = 111 ) -type BatchMeta struct { - message - PageNo uint64 - FirstIndex uint64 - Timestamp int64 -} - -func (msg *BatchMeta) Encode() []byte { - buf := make([]byte, 31) - buf[0] = 80 - p := 1 - p = WriteUint(msg.PageNo, buf, p) - p = WriteUint(msg.FirstIndex, buf, p) - p = WriteInt(msg.Timestamp, buf, p) - return buf[:p] -} - -func (msg *BatchMeta) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - -func (msg *BatchMeta) Decode() Message { - return msg -} - -func (msg *BatchMeta) TypeID() int { - return 80 -} - -type BatchMetadata struct { - message - Version uint64 - PageNo uint64 - FirstIndex uint64 - Timestamp int64 - Location string -} - -func (msg *BatchMetadata) Encode() []byte { - buf := make([]byte, 51+len(msg.Location)) - buf[0] = 81 - p := 1 - p = WriteUint(msg.Version, buf, p) - p = WriteUint(msg.PageNo, buf, p) - p = WriteUint(msg.FirstIndex, buf, p) - p = WriteInt(msg.Timestamp, buf, p) - p = WriteString(msg.Location, buf, p) - return buf[:p] -} - -func (msg *BatchMetadata) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - -func (msg *BatchMetadata) Decode() Message { - return msg -} - -func (msg *BatchMetadata) TypeID() int { - return 81 -} - -type PartitionedMessage struct { - message - PartNo uint64 - PartTotal uint64 -} - -func (msg *PartitionedMessage) Encode() []byte { - buf := make([]byte, 21) - buf[0] = 82 - p := 1 - p = WriteUint(msg.PartNo, buf, p) - p = WriteUint(msg.PartTotal, buf, p) - return buf[:p] -} - -func (msg *PartitionedMessage) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - -func (msg *PartitionedMessage) Decode() Message { - return msg -} - -func (msg *PartitionedMessage) TypeID() int { - return 82 -} - type Timestamp struct { message Timestamp uint64 @@ -326,17 +116,6 @@ func (msg *Timestamp) Encode() []byte { return buf[:p] } -func (msg *Timestamp) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *Timestamp) Decode() Message { return msg } @@ -388,17 +167,6 @@ func (msg *SessionStart) Encode() []byte { return buf[:p] } -func (msg *SessionStart) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SessionStart) Decode() Message { return msg } @@ -420,17 +188,6 @@ func (msg *SessionEndDeprecated) Encode() []byte { return buf[:p] } -func (msg *SessionEndDeprecated) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SessionEndDeprecated) Decode() Message { return msg } @@ -456,17 +213,6 @@ func (msg *SetPageLocation) Encode() []byte { return buf[:p] } -func (msg *SetPageLocation) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SetPageLocation) Decode() Message { return msg } @@ -490,17 +236,6 @@ func (msg *SetViewportSize) Encode() []byte { return buf[:p] } -func (msg *SetViewportSize) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SetViewportSize) Decode() Message { return msg } @@ -524,17 +259,6 @@ func (msg *SetViewportScroll) Encode() []byte { return buf[:p] } -func (msg *SetViewportScroll) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SetViewportScroll) Decode() Message { return msg } @@ -555,17 +279,6 @@ func (msg *CreateDocument) Encode() []byte { return buf[:p] } -func (msg *CreateDocument) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *CreateDocument) Decode() Message { return msg } @@ -595,17 +308,6 @@ func (msg *CreateElementNode) Encode() []byte { return buf[:p] } -func (msg *CreateElementNode) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *CreateElementNode) Decode() Message { return msg } @@ -631,17 +333,6 @@ func (msg *CreateTextNode) Encode() []byte { return buf[:p] } -func (msg *CreateTextNode) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *CreateTextNode) Decode() Message { return msg } @@ -667,17 +358,6 @@ func (msg *MoveNode) Encode() []byte { return buf[:p] } -func (msg *MoveNode) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *MoveNode) Decode() Message { return msg } @@ -699,17 +379,6 @@ func (msg *RemoveNode) Encode() []byte { return buf[:p] } -func (msg *RemoveNode) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *RemoveNode) Decode() Message { return msg } @@ -735,17 +404,6 @@ func (msg *SetNodeAttribute) Encode() []byte { return buf[:p] } -func (msg *SetNodeAttribute) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SetNodeAttribute) Decode() Message { return msg } @@ -769,17 +427,6 @@ func (msg *RemoveNodeAttribute) Encode() []byte { return buf[:p] } -func (msg *RemoveNodeAttribute) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *RemoveNodeAttribute) Decode() Message { return msg } @@ -803,17 +450,6 @@ func (msg *SetNodeData) Encode() []byte { return buf[:p] } -func (msg *SetNodeData) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SetNodeData) Decode() Message { return msg } @@ -837,17 +473,6 @@ func (msg *SetCSSData) Encode() []byte { return buf[:p] } -func (msg *SetCSSData) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SetCSSData) Decode() Message { return msg } @@ -873,17 +498,6 @@ func (msg *SetNodeScroll) Encode() []byte { return buf[:p] } -func (msg *SetNodeScroll) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SetNodeScroll) Decode() Message { return msg } @@ -907,17 +521,6 @@ func (msg *SetInputTarget) Encode() []byte { return buf[:p] } -func (msg *SetInputTarget) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SetInputTarget) Decode() Message { return msg } @@ -943,17 +546,6 @@ func (msg *SetInputValue) Encode() []byte { return buf[:p] } -func (msg *SetInputValue) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SetInputValue) Decode() Message { return msg } @@ -977,17 +569,6 @@ func (msg *SetInputChecked) Encode() []byte { return buf[:p] } -func (msg *SetInputChecked) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SetInputChecked) Decode() Message { return msg } @@ -1011,17 +592,6 @@ func (msg *MouseMove) Encode() []byte { return buf[:p] } -func (msg *MouseMove) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *MouseMove) Decode() Message { return msg } @@ -1030,6 +600,41 @@ func (msg *MouseMove) TypeID() int { return 20 } +type NetworkRequest struct { + message + Type string + Method string + URL string + Request string + Response string + Status uint64 + Timestamp uint64 + Duration uint64 +} + +func (msg *NetworkRequest) Encode() []byte { + buf := make([]byte, 81+len(msg.Type)+len(msg.Method)+len(msg.URL)+len(msg.Request)+len(msg.Response)) + buf[0] = 21 + p := 1 + p = WriteString(msg.Type, buf, p) + p = WriteString(msg.Method, buf, p) + p = WriteString(msg.URL, buf, p) + p = WriteString(msg.Request, buf, p) + p = WriteString(msg.Response, buf, p) + p = WriteUint(msg.Status, buf, p) + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Duration, buf, p) + return buf[:p] +} + +func (msg *NetworkRequest) Decode() Message { + return msg +} + +func (msg *NetworkRequest) TypeID() int { + return 21 +} + type ConsoleLog struct { message Level string @@ -1045,17 +650,6 @@ func (msg *ConsoleLog) Encode() []byte { return buf[:p] } -func (msg *ConsoleLog) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *ConsoleLog) Decode() Message { return msg } @@ -1093,17 +687,6 @@ func (msg *PageLoadTiming) Encode() []byte { return buf[:p] } -func (msg *PageLoadTiming) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *PageLoadTiming) Decode() Message { return msg } @@ -1129,17 +712,6 @@ func (msg *PageRenderTiming) Encode() []byte { return buf[:p] } -func (msg *PageRenderTiming) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *PageRenderTiming) Decode() Message { return msg } @@ -1165,17 +737,6 @@ func (msg *JSExceptionDeprecated) Encode() []byte { return buf[:p] } -func (msg *JSExceptionDeprecated) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *JSExceptionDeprecated) Decode() Message { return msg } @@ -1205,17 +766,6 @@ func (msg *IntegrationEvent) Encode() []byte { return buf[:p] } -func (msg *IntegrationEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IntegrationEvent) Decode() Message { return msg } @@ -1224,13 +774,13 @@ func (msg *IntegrationEvent) TypeID() int { return 26 } -type RawCustomEvent struct { +type CustomEvent struct { message Name string Payload string } -func (msg *RawCustomEvent) Encode() []byte { +func (msg *CustomEvent) Encode() []byte { buf := make([]byte, 21+len(msg.Name)+len(msg.Payload)) buf[0] = 27 p := 1 @@ -1239,22 +789,11 @@ func (msg *RawCustomEvent) Encode() []byte { return buf[:p] } -func (msg *RawCustomEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - -func (msg *RawCustomEvent) Decode() Message { +func (msg *CustomEvent) Decode() Message { return msg } -func (msg *RawCustomEvent) TypeID() int { +func (msg *CustomEvent) TypeID() int { return 27 } @@ -1271,17 +810,6 @@ func (msg *UserID) Encode() []byte { return buf[:p] } -func (msg *UserID) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *UserID) Decode() Message { return msg } @@ -1303,17 +831,6 @@ func (msg *UserAnonymousID) Encode() []byte { return buf[:p] } -func (msg *UserAnonymousID) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *UserAnonymousID) Decode() Message { return msg } @@ -1337,17 +854,6 @@ func (msg *Metadata) Encode() []byte { return buf[:p] } -func (msg *Metadata) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *Metadata) Decode() Message { return msg } @@ -1401,17 +907,6 @@ func (msg *PageEvent) Encode() []byte { return buf[:p] } -func (msg *PageEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *PageEvent) Decode() Message { return msg } @@ -1441,17 +936,6 @@ func (msg *InputEvent) Encode() []byte { return buf[:p] } -func (msg *InputEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *InputEvent) Decode() Message { return msg } @@ -1481,17 +965,6 @@ func (msg *ClickEvent) Encode() []byte { return buf[:p] } -func (msg *ClickEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *ClickEvent) Decode() Message { return msg } @@ -1535,17 +1008,6 @@ func (msg *ResourceEvent) Encode() []byte { return buf[:p] } -func (msg *ResourceEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *ResourceEvent) Decode() Message { return msg } @@ -1554,44 +1016,6 @@ func (msg *ResourceEvent) TypeID() int { return 35 } -type CustomEvent struct { - message - MessageID uint64 - Timestamp uint64 - Name string - Payload string -} - -func (msg *CustomEvent) Encode() []byte { - buf := make([]byte, 41+len(msg.Name)+len(msg.Payload)) - buf[0] = 36 - p := 1 - p = WriteUint(msg.MessageID, buf, p) - p = WriteUint(msg.Timestamp, buf, p) - p = WriteString(msg.Name, buf, p) - p = WriteString(msg.Payload, buf, p) - return buf[:p] -} - -func (msg *CustomEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - -func (msg *CustomEvent) Decode() Message { - return msg -} - -func (msg *CustomEvent) TypeID() int { - return 36 -} - type CSSInsertRule struct { message ID uint64 @@ -1609,17 +1033,6 @@ func (msg *CSSInsertRule) Encode() []byte { return buf[:p] } -func (msg *CSSInsertRule) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *CSSInsertRule) Decode() Message { return msg } @@ -1643,17 +1056,6 @@ func (msg *CSSDeleteRule) Encode() []byte { return buf[:p] } -func (msg *CSSDeleteRule) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *CSSDeleteRule) Decode() Message { return msg } @@ -1687,17 +1089,6 @@ func (msg *Fetch) Encode() []byte { return buf[:p] } -func (msg *Fetch) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *Fetch) Decode() Message { return msg } @@ -1725,17 +1116,6 @@ func (msg *Profiler) Encode() []byte { return buf[:p] } -func (msg *Profiler) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *Profiler) Decode() Message { return msg } @@ -1759,17 +1139,6 @@ func (msg *OTable) Encode() []byte { return buf[:p] } -func (msg *OTable) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *OTable) Decode() Message { return msg } @@ -1791,17 +1160,6 @@ func (msg *StateAction) Encode() []byte { return buf[:p] } -func (msg *StateAction) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *StateAction) Decode() Message { return msg } @@ -1810,42 +1168,6 @@ func (msg *StateAction) TypeID() int { return 42 } -type StateActionEvent struct { - message - MessageID uint64 - Timestamp uint64 - Type string -} - -func (msg *StateActionEvent) Encode() []byte { - buf := make([]byte, 31+len(msg.Type)) - buf[0] = 43 - p := 1 - p = WriteUint(msg.MessageID, buf, p) - p = WriteUint(msg.Timestamp, buf, p) - p = WriteString(msg.Type, buf, p) - return buf[:p] -} - -func (msg *StateActionEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - -func (msg *StateActionEvent) Decode() Message { - return msg -} - -func (msg *StateActionEvent) TypeID() int { - return 43 -} - type Redux struct { message Action string @@ -1863,17 +1185,6 @@ func (msg *Redux) Encode() []byte { return buf[:p] } -func (msg *Redux) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *Redux) Decode() Message { return msg } @@ -1897,17 +1208,6 @@ func (msg *Vuex) Encode() []byte { return buf[:p] } -func (msg *Vuex) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *Vuex) Decode() Message { return msg } @@ -1931,17 +1231,6 @@ func (msg *MobX) Encode() []byte { return buf[:p] } -func (msg *MobX) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *MobX) Decode() Message { return msg } @@ -1967,17 +1256,6 @@ func (msg *NgRx) Encode() []byte { return buf[:p] } -func (msg *NgRx) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *NgRx) Decode() Message { return msg } @@ -2005,17 +1283,6 @@ func (msg *GraphQL) Encode() []byte { return buf[:p] } -func (msg *GraphQL) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *GraphQL) Decode() Message { return msg } @@ -2043,17 +1310,6 @@ func (msg *PerformanceTrack) Encode() []byte { return buf[:p] } -func (msg *PerformanceTrack) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *PerformanceTrack) Decode() Message { return msg } @@ -2062,91 +1318,51 @@ func (msg *PerformanceTrack) TypeID() int { return 49 } -type GraphQLEvent struct { +type StringDict struct { message - MessageID uint64 - Timestamp uint64 - OperationKind string - OperationName string - Variables string - Response string + Key uint64 + Value string } -func (msg *GraphQLEvent) Encode() []byte { - buf := make([]byte, 61+len(msg.OperationKind)+len(msg.OperationName)+len(msg.Variables)+len(msg.Response)) +func (msg *StringDict) Encode() []byte { + buf := make([]byte, 21+len(msg.Value)) buf[0] = 50 p := 1 - p = WriteUint(msg.MessageID, buf, p) - p = WriteUint(msg.Timestamp, buf, p) - p = WriteString(msg.OperationKind, buf, p) - p = WriteString(msg.OperationName, buf, p) - p = WriteString(msg.Variables, buf, p) - p = WriteString(msg.Response, buf, p) + p = WriteUint(msg.Key, buf, p) + p = WriteString(msg.Value, buf, p) return buf[:p] } -func (msg *GraphQLEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - -func (msg *GraphQLEvent) Decode() Message { +func (msg *StringDict) Decode() Message { return msg } -func (msg *GraphQLEvent) TypeID() int { +func (msg *StringDict) TypeID() int { return 50 } -type FetchEvent struct { +type SetNodeAttributeDict struct { message - MessageID uint64 - Timestamp uint64 - Method string - URL string - Request string - Response string - Status uint64 - Duration uint64 + ID uint64 + NameKey uint64 + ValueKey uint64 } -func (msg *FetchEvent) Encode() []byte { - buf := make([]byte, 81+len(msg.Method)+len(msg.URL)+len(msg.Request)+len(msg.Response)) +func (msg *SetNodeAttributeDict) Encode() []byte { + buf := make([]byte, 31) buf[0] = 51 p := 1 - p = WriteUint(msg.MessageID, buf, p) - p = WriteUint(msg.Timestamp, buf, p) - p = WriteString(msg.Method, buf, p) - p = WriteString(msg.URL, buf, p) - p = WriteString(msg.Request, buf, p) - p = WriteString(msg.Response, buf, p) - p = WriteUint(msg.Status, buf, p) - p = WriteUint(msg.Duration, buf, p) + p = WriteUint(msg.ID, buf, p) + p = WriteUint(msg.NameKey, buf, p) + p = WriteUint(msg.ValueKey, buf, p) return buf[:p] } -func (msg *FetchEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - -func (msg *FetchEvent) Decode() Message { +func (msg *SetNodeAttributeDict) Decode() Message { return msg } -func (msg *FetchEvent) TypeID() int { +func (msg *SetNodeAttributeDict) TypeID() int { return 51 } @@ -2163,17 +1379,6 @@ func (msg *DOMDrop) Encode() []byte { return buf[:p] } -func (msg *DOMDrop) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *DOMDrop) Decode() Message { return msg } @@ -2209,17 +1414,6 @@ func (msg *ResourceTiming) Encode() []byte { return buf[:p] } -func (msg *ResourceTiming) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *ResourceTiming) Decode() Message { return msg } @@ -2243,17 +1437,6 @@ func (msg *ConnectionInformation) Encode() []byte { return buf[:p] } -func (msg *ConnectionInformation) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *ConnectionInformation) Decode() Message { return msg } @@ -2275,17 +1458,6 @@ func (msg *SetPageVisibility) Encode() []byte { return buf[:p] } -func (msg *SetPageVisibility) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SetPageVisibility) Decode() Message { return msg } @@ -2333,17 +1505,6 @@ func (msg *PerformanceTrackAggr) Encode() []byte { return buf[:p] } -func (msg *PerformanceTrackAggr) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *PerformanceTrackAggr) Decode() Message { return msg } @@ -2371,17 +1532,6 @@ func (msg *LoadFontFace) Encode() []byte { return buf[:p] } -func (msg *LoadFontFace) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *LoadFontFace) Decode() Message { return msg } @@ -2403,17 +1553,6 @@ func (msg *SetNodeFocus) Encode() []byte { return buf[:p] } -func (msg *SetNodeFocus) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SetNodeFocus) Decode() Message { return msg } @@ -2447,17 +1586,6 @@ func (msg *LongTask) Encode() []byte { return buf[:p] } -func (msg *LongTask) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *LongTask) Decode() Message { return msg } @@ -2485,17 +1613,6 @@ func (msg *SetNodeAttributeURLBased) Encode() []byte { return buf[:p] } -func (msg *SetNodeAttributeURLBased) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SetNodeAttributeURLBased) Decode() Message { return msg } @@ -2521,17 +1638,6 @@ func (msg *SetCSSDataURLBased) Encode() []byte { return buf[:p] } -func (msg *SetCSSDataURLBased) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SetCSSDataURLBased) Decode() Message { return msg } @@ -2540,7 +1646,7 @@ func (msg *SetCSSDataURLBased) TypeID() int { return 61 } -type IssueEvent struct { +type IssueEventDeprecated struct { message MessageID uint64 Timestamp uint64 @@ -2550,7 +1656,7 @@ type IssueEvent struct { Payload string } -func (msg *IssueEvent) Encode() []byte { +func (msg *IssueEventDeprecated) Encode() []byte { buf := make([]byte, 61+len(msg.Type)+len(msg.ContextString)+len(msg.Context)+len(msg.Payload)) buf[0] = 62 p := 1 @@ -2563,22 +1669,11 @@ func (msg *IssueEvent) Encode() []byte { return buf[:p] } -func (msg *IssueEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - -func (msg *IssueEvent) Decode() Message { +func (msg *IssueEventDeprecated) Decode() Message { return msg } -func (msg *IssueEvent) TypeID() int { +func (msg *IssueEventDeprecated) TypeID() int { return 62 } @@ -2597,17 +1692,6 @@ func (msg *TechnicalInfo) Encode() []byte { return buf[:p] } -func (msg *TechnicalInfo) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *TechnicalInfo) Decode() Message { return msg } @@ -2631,17 +1715,6 @@ func (msg *CustomIssue) Encode() []byte { return buf[:p] } -func (msg *CustomIssue) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *CustomIssue) Decode() Message { return msg } @@ -2663,17 +1736,6 @@ func (msg *AssetCache) Encode() []byte { return buf[:p] } -func (msg *AssetCache) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *AssetCache) Decode() Message { return msg } @@ -2701,17 +1763,6 @@ func (msg *CSSInsertRuleURLBased) Encode() []byte { return buf[:p] } -func (msg *CSSInsertRuleURLBased) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *CSSInsertRuleURLBased) Decode() Message { return msg } @@ -2739,17 +1790,6 @@ func (msg *MouseClick) Encode() []byte { return buf[:p] } -func (msg *MouseClick) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *MouseClick) Decode() Message { return msg } @@ -2773,17 +1813,6 @@ func (msg *CreateIFrameDocument) Encode() []byte { return buf[:p] } -func (msg *CreateIFrameDocument) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *CreateIFrameDocument) Decode() Message { return msg } @@ -2809,17 +1838,6 @@ func (msg *AdoptedSSReplaceURLBased) Encode() []byte { return buf[:p] } -func (msg *AdoptedSSReplaceURLBased) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *AdoptedSSReplaceURLBased) Decode() Message { return msg } @@ -2843,17 +1861,6 @@ func (msg *AdoptedSSReplace) Encode() []byte { return buf[:p] } -func (msg *AdoptedSSReplace) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *AdoptedSSReplace) Decode() Message { return msg } @@ -2881,17 +1888,6 @@ func (msg *AdoptedSSInsertRuleURLBased) Encode() []byte { return buf[:p] } -func (msg *AdoptedSSInsertRuleURLBased) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *AdoptedSSInsertRuleURLBased) Decode() Message { return msg } @@ -2917,17 +1913,6 @@ func (msg *AdoptedSSInsertRule) Encode() []byte { return buf[:p] } -func (msg *AdoptedSSInsertRule) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *AdoptedSSInsertRule) Decode() Message { return msg } @@ -2951,17 +1936,6 @@ func (msg *AdoptedSSDeleteRule) Encode() []byte { return buf[:p] } -func (msg *AdoptedSSDeleteRule) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *AdoptedSSDeleteRule) Decode() Message { return msg } @@ -2985,17 +1959,6 @@ func (msg *AdoptedSSAddOwner) Encode() []byte { return buf[:p] } -func (msg *AdoptedSSAddOwner) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *AdoptedSSAddOwner) Decode() Message { return msg } @@ -3019,17 +1982,6 @@ func (msg *AdoptedSSRemoveOwner) Encode() []byte { return buf[:p] } -func (msg *AdoptedSSRemoveOwner) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *AdoptedSSRemoveOwner) Decode() Message { return msg } @@ -3038,40 +1990,6 @@ func (msg *AdoptedSSRemoveOwner) TypeID() int { return 77 } -type Zustand struct { - message - Mutation string - State string -} - -func (msg *Zustand) Encode() []byte { - buf := make([]byte, 21+len(msg.Mutation)+len(msg.State)) - buf[0] = 79 - p := 1 - p = WriteString(msg.Mutation, buf, p) - p = WriteString(msg.State, buf, p) - return buf[:p] -} - -func (msg *Zustand) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - -func (msg *Zustand) Decode() Message { - return msg -} - -func (msg *Zustand) TypeID() int { - return 79 -} - type JSException struct { message Name string @@ -3091,17 +2009,6 @@ func (msg *JSException) Encode() []byte { return buf[:p] } -func (msg *JSException) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *JSException) Decode() Message { return msg } @@ -3110,6 +2017,139 @@ func (msg *JSException) TypeID() int { return 78 } +type Zustand struct { + message + Mutation string + State string +} + +func (msg *Zustand) Encode() []byte { + buf := make([]byte, 21+len(msg.Mutation)+len(msg.State)) + buf[0] = 79 + p := 1 + p = WriteString(msg.Mutation, buf, p) + p = WriteString(msg.State, buf, p) + return buf[:p] +} + +func (msg *Zustand) Decode() Message { + return msg +} + +func (msg *Zustand) TypeID() int { + return 79 +} + +type BatchMeta struct { + message + PageNo uint64 + FirstIndex uint64 + Timestamp int64 +} + +func (msg *BatchMeta) Encode() []byte { + buf := make([]byte, 31) + buf[0] = 80 + p := 1 + p = WriteUint(msg.PageNo, buf, p) + p = WriteUint(msg.FirstIndex, buf, p) + p = WriteInt(msg.Timestamp, buf, p) + return buf[:p] +} + +func (msg *BatchMeta) Decode() Message { + return msg +} + +func (msg *BatchMeta) TypeID() int { + return 80 +} + +type BatchMetadata struct { + message + Version uint64 + PageNo uint64 + FirstIndex uint64 + Timestamp int64 + Location string +} + +func (msg *BatchMetadata) Encode() []byte { + buf := make([]byte, 51+len(msg.Location)) + buf[0] = 81 + p := 1 + p = WriteUint(msg.Version, buf, p) + p = WriteUint(msg.PageNo, buf, p) + p = WriteUint(msg.FirstIndex, buf, p) + p = WriteInt(msg.Timestamp, buf, p) + p = WriteString(msg.Location, buf, p) + return buf[:p] +} + +func (msg *BatchMetadata) Decode() Message { + return msg +} + +func (msg *BatchMetadata) TypeID() int { + return 81 +} + +type PartitionedMessage struct { + message + PartNo uint64 + PartTotal uint64 +} + +func (msg *PartitionedMessage) Encode() []byte { + buf := make([]byte, 21) + buf[0] = 82 + p := 1 + p = WriteUint(msg.PartNo, buf, p) + p = WriteUint(msg.PartTotal, buf, p) + return buf[:p] +} + +func (msg *PartitionedMessage) Decode() Message { + return msg +} + +func (msg *PartitionedMessage) TypeID() int { + return 82 +} + +type IssueEvent struct { + message + MessageID uint64 + Timestamp uint64 + Type string + ContextString string + Context string + Payload string + URL string +} + +func (msg *IssueEvent) Encode() []byte { + buf := make([]byte, 71+len(msg.Type)+len(msg.ContextString)+len(msg.Context)+len(msg.Payload)+len(msg.URL)) + buf[0] = 125 + p := 1 + p = WriteUint(msg.MessageID, buf, p) + p = WriteUint(msg.Timestamp, buf, p) + p = WriteString(msg.Type, buf, p) + p = WriteString(msg.ContextString, buf, p) + p = WriteString(msg.Context, buf, p) + p = WriteString(msg.Payload, buf, p) + p = WriteString(msg.URL, buf, p) + return buf[:p] +} + +func (msg *IssueEvent) Decode() Message { + return msg +} + +func (msg *IssueEvent) TypeID() int { + return 125 +} + type SessionEnd struct { message Timestamp uint64 @@ -3125,17 +2165,6 @@ func (msg *SessionEnd) Encode() []byte { return buf[:p] } -func (msg *SessionEnd) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SessionEnd) Decode() Message { return msg } @@ -3159,17 +2188,6 @@ func (msg *SessionSearch) Encode() []byte { return buf[:p] } -func (msg *SessionSearch) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *SessionSearch) Decode() Message { return msg } @@ -3195,17 +2213,6 @@ func (msg *IOSBatchMeta) Encode() []byte { return buf[:p] } -func (msg *IOSBatchMeta) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSBatchMeta) Decode() Message { return msg } @@ -3245,17 +2252,6 @@ func (msg *IOSSessionStart) Encode() []byte { return buf[:p] } -func (msg *IOSSessionStart) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSSessionStart) Decode() Message { return msg } @@ -3277,17 +2273,6 @@ func (msg *IOSSessionEnd) Encode() []byte { return buf[:p] } -func (msg *IOSSessionEnd) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSSessionEnd) Decode() Message { return msg } @@ -3315,17 +2300,6 @@ func (msg *IOSMetadata) Encode() []byte { return buf[:p] } -func (msg *IOSMetadata) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSMetadata) Decode() Message { return msg } @@ -3353,17 +2327,6 @@ func (msg *IOSCustomEvent) Encode() []byte { return buf[:p] } -func (msg *IOSCustomEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSCustomEvent) Decode() Message { return msg } @@ -3389,17 +2352,6 @@ func (msg *IOSUserID) Encode() []byte { return buf[:p] } -func (msg *IOSUserID) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSUserID) Decode() Message { return msg } @@ -3425,17 +2377,6 @@ func (msg *IOSUserAnonymousID) Encode() []byte { return buf[:p] } -func (msg *IOSUserAnonymousID) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSUserAnonymousID) Decode() Message { return msg } @@ -3467,17 +2408,6 @@ func (msg *IOSScreenChanges) Encode() []byte { return buf[:p] } -func (msg *IOSScreenChanges) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSScreenChanges) Decode() Message { return msg } @@ -3507,17 +2437,6 @@ func (msg *IOSCrash) Encode() []byte { return buf[:p] } -func (msg *IOSCrash) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSCrash) Decode() Message { return msg } @@ -3545,17 +2464,6 @@ func (msg *IOSScreenEnter) Encode() []byte { return buf[:p] } -func (msg *IOSScreenEnter) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSScreenEnter) Decode() Message { return msg } @@ -3583,17 +2491,6 @@ func (msg *IOSScreenLeave) Encode() []byte { return buf[:p] } -func (msg *IOSScreenLeave) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSScreenLeave) Decode() Message { return msg } @@ -3623,17 +2520,6 @@ func (msg *IOSClickEvent) Encode() []byte { return buf[:p] } -func (msg *IOSClickEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSClickEvent) Decode() Message { return msg } @@ -3663,17 +2549,6 @@ func (msg *IOSInputEvent) Encode() []byte { return buf[:p] } -func (msg *IOSInputEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSInputEvent) Decode() Message { return msg } @@ -3701,17 +2576,6 @@ func (msg *IOSPerformanceEvent) Encode() []byte { return buf[:p] } -func (msg *IOSPerformanceEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSPerformanceEvent) Decode() Message { return msg } @@ -3739,17 +2603,6 @@ func (msg *IOSLog) Encode() []byte { return buf[:p] } -func (msg *IOSLog) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSLog) Decode() Message { return msg } @@ -3775,17 +2628,6 @@ func (msg *IOSInternalError) Encode() []byte { return buf[:p] } -func (msg *IOSInternalError) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSInternalError) Decode() Message { return msg } @@ -3823,17 +2665,6 @@ func (msg *IOSNetworkCall) Encode() []byte { return buf[:p] } -func (msg *IOSNetworkCall) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSNetworkCall) Decode() Message { return msg } @@ -3881,17 +2712,6 @@ func (msg *IOSPerformanceAggregated) Encode() []byte { return buf[:p] } -func (msg *IOSPerformanceAggregated) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSPerformanceAggregated) Decode() Message { return msg } @@ -3921,17 +2741,6 @@ func (msg *IOSIssueEvent) Encode() []byte { return buf[:p] } -func (msg *IOSIssueEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *IOSIssueEvent) Decode() Message { return msg } diff --git a/backend/pkg/messages/primitives.go b/backend/pkg/messages/primitives.go index 1d3d2410b..3e47a3943 100644 --- a/backend/pkg/messages/primitives.go +++ b/backend/pkg/messages/primitives.go @@ -1,11 +1,9 @@ package messages import ( - "encoding/json" "errors" "fmt" "io" - "log" ) var ( @@ -21,19 +19,6 @@ func ReadByte(reader io.Reader) (byte, error) { return one[0], nil } -func ReadData(reader io.Reader) ([]byte, error) { - n, err := ReadUint(reader) - if err != nil { - return nil, err - } - p := make([]byte, n) - _, err = io.ReadFull(reader, p) - if err != nil { - return nil, err - } - return p, nil -} - func ReadUint(reader io.Reader) (uint64, error) { var x uint64 var s uint @@ -55,6 +40,16 @@ func ReadUint(reader io.Reader) (uint64, error) { } } +func WriteUint(v uint64, buf []byte, p int) int { + for v >= 0x80 { + buf[p] = byte(v) | 0x80 + v >>= 7 + p++ + } + buf[p] = byte(v) + return p + 1 +} + func ReadInt(reader io.Reader) (int64, error) { ux, err := ReadUint(reader) x := int64(ux >> 1) @@ -67,6 +62,14 @@ func ReadInt(reader io.Reader) (int64, error) { return x, err } +func WriteInt(v int64, buf []byte, p int) int { + uv := uint64(v) << 1 + if v < 0 { + uv = ^uv + } + return WriteUint(uv, buf, p) +} + func ReadBoolean(reader io.Reader) (bool, error) { p := make([]byte, 1) _, err := io.ReadFull(reader, p) @@ -76,6 +79,15 @@ func ReadBoolean(reader io.Reader) (bool, error) { return p[0] == 1, nil } +func WriteBoolean(v bool, buf []byte, p int) int { + if v { + buf[p] = 1 + } else { + buf[p] = 0 + } + return p + 1 +} + func ReadString(reader io.Reader) (string, error) { l, err := ReadUint(reader) if err != nil { @@ -92,73 +104,11 @@ func ReadString(reader io.Reader) (string, error) { return string(buf), nil } -func ReadJson(reader io.Reader) (interface{}, error) { - jsonData, err := ReadData(reader) - if err != nil { - return nil, err - } - var v interface{} - if err = json.Unmarshal(jsonData, &v); err != nil { - return nil, err - } - return v, nil -} - -func WriteUint(v uint64, buf []byte, p int) int { - for v >= 0x80 { - buf[p] = byte(v) | 0x80 - v >>= 7 - p++ - } - buf[p] = byte(v) - return p + 1 -} - -func WriteInt(v int64, buf []byte, p int) int { - uv := uint64(v) << 1 - if v < 0 { - uv = ^uv - } - return WriteUint(uv, buf, p) -} - -func WriteBoolean(v bool, buf []byte, p int) int { - if v { - buf[p] = 1 - } else { - buf[p] = 0 - } - return p + 1 -} - func WriteString(str string, buf []byte, p int) int { p = WriteUint(uint64(len(str)), buf, p) return p + copy(buf[p:], str) } -func WriteData(data []byte, buf []byte, p int) int { - p = WriteUint(uint64(len(data)), buf, p) - return p + copy(buf[p:], data) -} - -func WriteJson(v interface{}, buf []byte, p int) int { - data, err := json.Marshal(v) - if err != nil { - log.Printf("JSON encoding error: %v", err) - return WriteString("null", buf, p) - } - return WriteData(data, buf, p) -} - -func WriteSize(size uint64, buf []byte, p int) { - var m uint64 = 255 - for i := 0; i < 3; i++ { - buf[p+i] = byte(size & m) - size = size >> 8 - } - fmt.Println(buf) -} - func ReadSize(reader io.Reader) (uint64, error) { n, err := io.ReadFull(reader, three) if err != nil { diff --git a/backend/pkg/messages/raw.go b/backend/pkg/messages/raw.go index dbc71f4e6..44f666c69 100644 --- a/backend/pkg/messages/raw.go +++ b/backend/pkg/messages/raw.go @@ -1,75 +1,23 @@ package messages import ( - "bytes" - "encoding/binary" - "io" "log" ) // RawMessage is a not decoded message type RawMessage struct { - tp uint64 - size uint64 - data []byte - reader *bytes.Reader - raw []byte - meta *message - encoded bool - skipped *bool - broken *bool + tp uint64 + data []byte + broken *bool + meta *message } func (m *RawMessage) Encode() []byte { - if m.encoded { - return m.data - } - // Try to avoid EOF error - if m.reader.Len() < int(m.size) { - return nil - } - // Get current batch position - currPos, err := m.reader.Seek(0, io.SeekCurrent) - if err != nil { - log.Printf("can't get current batch position: %s", err) - return nil - } - // "Move" message type - if currPos == 0 { - log.Printf("can't move message type, curr position = %d", currPos) - return nil - } - // Dirty hack to avoid extra memory allocation - m.raw[currPos-1] = uint8(m.tp) - m.data = m.raw[currPos-1 : currPos+int64(m.size)] - m.encoded = true return m.data } -func (m *RawMessage) EncodeWithIndex() []byte { - if !m.encoded { - if m.Encode() == nil { - *m.broken = true - return nil - } - } - if IsIOSType(int(m.tp)) { - return m.data - } - data := make([]byte, len(m.data)+8) - copy(data[8:], m.data[:]) - binary.LittleEndian.PutUint64(data[0:], m.Meta().Index) - return data -} - func (m *RawMessage) Decode() Message { - if !m.encoded { - if m.Encode() == nil { - *m.broken = true - return nil - } - } - msg, err := ReadMessage(m.tp, bytes.NewReader(m.data[1:])) + msg, err := ReadMessage(m.tp, NewBytesReader(m.data[1:])) if err != nil { log.Printf("decode err: %s", err) *m.broken = true diff --git a/backend/pkg/messages/read-message.go b/backend/pkg/messages/read-message.go index 5988ebe62..ecc00183f 100644 --- a/backend/pkg/messages/read-message.go +++ b/backend/pkg/messages/read-message.go @@ -3,2053 +3,1949 @@ package messages import ( "fmt" - "io" ) -func DecodeBatchMeta(reader io.Reader) (Message, error) { - var err error = nil - msg := &BatchMeta{} - if msg.PageNo, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.FirstIndex, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadInt(reader); err != nil { - return nil, err - } - return msg, err -} - -func DecodeBatchMetadata(reader io.Reader) (Message, error) { - var err error = nil - msg := &BatchMetadata{} - if msg.Version, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.PageNo, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.FirstIndex, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadInt(reader); err != nil { - return nil, err - } - if msg.Location, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err -} - -func DecodePartitionedMessage(reader io.Reader) (Message, error) { - var err error = nil - msg := &PartitionedMessage{} - if msg.PartNo, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.PartTotal, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err -} - -func DecodeTimestamp(reader io.Reader) (Message, error) { +func DecodeTimestamp(reader BytesReader) (Message, error) { var err error = nil msg := &Timestamp{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeSessionStart(reader io.Reader) (Message, error) { +func DecodeSessionStart(reader BytesReader) (Message, error) { var err error = nil msg := &SessionStart{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.ProjectID, err = ReadUint(reader); err != nil { + if msg.ProjectID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.TrackerVersion, err = ReadString(reader); err != nil { + if msg.TrackerVersion, err = reader.ReadString(); err != nil { return nil, err } - if msg.RevID, err = ReadString(reader); err != nil { + if msg.RevID, err = reader.ReadString(); err != nil { return nil, err } - if msg.UserUUID, err = ReadString(reader); err != nil { + if msg.UserUUID, err = reader.ReadString(); err != nil { return nil, err } - if msg.UserAgent, err = ReadString(reader); err != nil { + if msg.UserAgent, err = reader.ReadString(); err != nil { return nil, err } - if msg.UserOS, err = ReadString(reader); err != nil { + if msg.UserOS, err = reader.ReadString(); err != nil { return nil, err } - if msg.UserOSVersion, err = ReadString(reader); err != nil { + if msg.UserOSVersion, err = reader.ReadString(); err != nil { return nil, err } - if msg.UserBrowser, err = ReadString(reader); err != nil { + if msg.UserBrowser, err = reader.ReadString(); err != nil { return nil, err } - if msg.UserBrowserVersion, err = ReadString(reader); err != nil { + if msg.UserBrowserVersion, err = reader.ReadString(); err != nil { return nil, err } - if msg.UserDevice, err = ReadString(reader); err != nil { + if msg.UserDevice, err = reader.ReadString(); err != nil { return nil, err } - if msg.UserDeviceType, err = ReadString(reader); err != nil { + if msg.UserDeviceType, err = reader.ReadString(); err != nil { return nil, err } - if msg.UserDeviceMemorySize, err = ReadUint(reader); err != nil { + if msg.UserDeviceMemorySize, err = reader.ReadUint(); err != nil { return nil, err } - if msg.UserDeviceHeapSize, err = ReadUint(reader); err != nil { + if msg.UserDeviceHeapSize, err = reader.ReadUint(); err != nil { return nil, err } - if msg.UserCountry, err = ReadString(reader); err != nil { + if msg.UserCountry, err = reader.ReadString(); err != nil { return nil, err } - if msg.UserID, err = ReadString(reader); err != nil { + if msg.UserID, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeSessionEndDeprecated(reader io.Reader) (Message, error) { +func DecodeSessionEndDeprecated(reader BytesReader) (Message, error) { var err error = nil msg := &SessionEndDeprecated{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeSetPageLocation(reader io.Reader) (Message, error) { +func DecodeSetPageLocation(reader BytesReader) (Message, error) { var err error = nil msg := &SetPageLocation{} - if msg.URL, err = ReadString(reader); err != nil { + if msg.URL, err = reader.ReadString(); err != nil { return nil, err } - if msg.Referrer, err = ReadString(reader); err != nil { + if msg.Referrer, err = reader.ReadString(); err != nil { return nil, err } - if msg.NavigationStart, err = ReadUint(reader); err != nil { + if msg.NavigationStart, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeSetViewportSize(reader io.Reader) (Message, error) { +func DecodeSetViewportSize(reader BytesReader) (Message, error) { var err error = nil msg := &SetViewportSize{} - if msg.Width, err = ReadUint(reader); err != nil { + if msg.Width, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Height, err = ReadUint(reader); err != nil { + if msg.Height, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeSetViewportScroll(reader io.Reader) (Message, error) { +func DecodeSetViewportScroll(reader BytesReader) (Message, error) { var err error = nil msg := &SetViewportScroll{} - if msg.X, err = ReadInt(reader); err != nil { + if msg.X, err = reader.ReadInt(); err != nil { return nil, err } - if msg.Y, err = ReadInt(reader); err != nil { + if msg.Y, err = reader.ReadInt(); err != nil { return nil, err } return msg, err } -func DecodeCreateDocument(reader io.Reader) (Message, error) { +func DecodeCreateDocument(reader BytesReader) (Message, error) { var err error = nil msg := &CreateDocument{} return msg, err } -func DecodeCreateElementNode(reader io.Reader) (Message, error) { +func DecodeCreateElementNode(reader BytesReader) (Message, error) { var err error = nil msg := &CreateElementNode{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.ParentID, err = ReadUint(reader); err != nil { + if msg.ParentID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.index, err = ReadUint(reader); err != nil { + if msg.index, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Tag, err = ReadString(reader); err != nil { + if msg.Tag, err = reader.ReadString(); err != nil { return nil, err } - if msg.SVG, err = ReadBoolean(reader); err != nil { + if msg.SVG, err = reader.ReadBoolean(); err != nil { return nil, err } return msg, err } -func DecodeCreateTextNode(reader io.Reader) (Message, error) { +func DecodeCreateTextNode(reader BytesReader) (Message, error) { var err error = nil msg := &CreateTextNode{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.ParentID, err = ReadUint(reader); err != nil { + if msg.ParentID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Index, err = ReadUint(reader); err != nil { + if msg.Index, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeMoveNode(reader io.Reader) (Message, error) { +func DecodeMoveNode(reader BytesReader) (Message, error) { var err error = nil msg := &MoveNode{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.ParentID, err = ReadUint(reader); err != nil { + if msg.ParentID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Index, err = ReadUint(reader); err != nil { + if msg.Index, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeRemoveNode(reader io.Reader) (Message, error) { +func DecodeRemoveNode(reader BytesReader) (Message, error) { var err error = nil msg := &RemoveNode{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeSetNodeAttribute(reader io.Reader) (Message, error) { +func DecodeSetNodeAttribute(reader BytesReader) (Message, error) { var err error = nil msg := &SetNodeAttribute{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Name, err = ReadString(reader); err != nil { + if msg.Name, err = reader.ReadString(); err != nil { return nil, err } - if msg.Value, err = ReadString(reader); err != nil { + if msg.Value, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeRemoveNodeAttribute(reader io.Reader) (Message, error) { +func DecodeRemoveNodeAttribute(reader BytesReader) (Message, error) { var err error = nil msg := &RemoveNodeAttribute{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Name, err = ReadString(reader); err != nil { + if msg.Name, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeSetNodeData(reader io.Reader) (Message, error) { +func DecodeSetNodeData(reader BytesReader) (Message, error) { var err error = nil msg := &SetNodeData{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Data, err = ReadString(reader); err != nil { + if msg.Data, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeSetCSSData(reader io.Reader) (Message, error) { +func DecodeSetCSSData(reader BytesReader) (Message, error) { var err error = nil msg := &SetCSSData{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Data, err = ReadString(reader); err != nil { + if msg.Data, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeSetNodeScroll(reader io.Reader) (Message, error) { +func DecodeSetNodeScroll(reader BytesReader) (Message, error) { var err error = nil msg := &SetNodeScroll{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.X, err = ReadInt(reader); err != nil { + if msg.X, err = reader.ReadInt(); err != nil { return nil, err } - if msg.Y, err = ReadInt(reader); err != nil { + if msg.Y, err = reader.ReadInt(); err != nil { return nil, err } return msg, err } -func DecodeSetInputTarget(reader io.Reader) (Message, error) { +func DecodeSetInputTarget(reader BytesReader) (Message, error) { var err error = nil msg := &SetInputTarget{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Label, err = ReadString(reader); err != nil { + if msg.Label, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeSetInputValue(reader io.Reader) (Message, error) { +func DecodeSetInputValue(reader BytesReader) (Message, error) { var err error = nil msg := &SetInputValue{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Value, err = ReadString(reader); err != nil { + if msg.Value, err = reader.ReadString(); err != nil { return nil, err } - if msg.Mask, err = ReadInt(reader); err != nil { + if msg.Mask, err = reader.ReadInt(); err != nil { return nil, err } return msg, err } -func DecodeSetInputChecked(reader io.Reader) (Message, error) { +func DecodeSetInputChecked(reader BytesReader) (Message, error) { var err error = nil msg := &SetInputChecked{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Checked, err = ReadBoolean(reader); err != nil { + if msg.Checked, err = reader.ReadBoolean(); err != nil { return nil, err } return msg, err } -func DecodeMouseMove(reader io.Reader) (Message, error) { +func DecodeMouseMove(reader BytesReader) (Message, error) { var err error = nil msg := &MouseMove{} - if msg.X, err = ReadUint(reader); err != nil { + if msg.X, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Y, err = ReadUint(reader); err != nil { + if msg.Y, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeConsoleLog(reader io.Reader) (Message, error) { +func DecodeNetworkRequest(reader BytesReader) (Message, error) { + var err error = nil + msg := &NetworkRequest{} + if msg.Type, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Method, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.URL, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Request, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Response, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Status, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Duration, err = reader.ReadUint(); err != nil { + return nil, err + } + return msg, err +} + +func DecodeConsoleLog(reader BytesReader) (Message, error) { var err error = nil msg := &ConsoleLog{} - if msg.Level, err = ReadString(reader); err != nil { + if msg.Level, err = reader.ReadString(); err != nil { return nil, err } - if msg.Value, err = ReadString(reader); err != nil { + if msg.Value, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodePageLoadTiming(reader io.Reader) (Message, error) { +func DecodePageLoadTiming(reader BytesReader) (Message, error) { var err error = nil msg := &PageLoadTiming{} - if msg.RequestStart, err = ReadUint(reader); err != nil { + if msg.RequestStart, err = reader.ReadUint(); err != nil { return nil, err } - if msg.ResponseStart, err = ReadUint(reader); err != nil { + if msg.ResponseStart, err = reader.ReadUint(); err != nil { return nil, err } - if msg.ResponseEnd, err = ReadUint(reader); err != nil { + if msg.ResponseEnd, err = reader.ReadUint(); err != nil { return nil, err } - if msg.DomContentLoadedEventStart, err = ReadUint(reader); err != nil { + if msg.DomContentLoadedEventStart, err = reader.ReadUint(); err != nil { return nil, err } - if msg.DomContentLoadedEventEnd, err = ReadUint(reader); err != nil { + if msg.DomContentLoadedEventEnd, err = reader.ReadUint(); err != nil { return nil, err } - if msg.LoadEventStart, err = ReadUint(reader); err != nil { + if msg.LoadEventStart, err = reader.ReadUint(); err != nil { return nil, err } - if msg.LoadEventEnd, err = ReadUint(reader); err != nil { + if msg.LoadEventEnd, err = reader.ReadUint(); err != nil { return nil, err } - if msg.FirstPaint, err = ReadUint(reader); err != nil { + if msg.FirstPaint, err = reader.ReadUint(); err != nil { return nil, err } - if msg.FirstContentfulPaint, err = ReadUint(reader); err != nil { + if msg.FirstContentfulPaint, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodePageRenderTiming(reader io.Reader) (Message, error) { +func DecodePageRenderTiming(reader BytesReader) (Message, error) { var err error = nil msg := &PageRenderTiming{} - if msg.SpeedIndex, err = ReadUint(reader); err != nil { + if msg.SpeedIndex, err = reader.ReadUint(); err != nil { return nil, err } - if msg.VisuallyComplete, err = ReadUint(reader); err != nil { + if msg.VisuallyComplete, err = reader.ReadUint(); err != nil { return nil, err } - if msg.TimeToInteractive, err = ReadUint(reader); err != nil { + if msg.TimeToInteractive, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeJSExceptionDeprecated(reader io.Reader) (Message, error) { +func DecodeJSExceptionDeprecated(reader BytesReader) (Message, error) { var err error = nil msg := &JSExceptionDeprecated{} - if msg.Name, err = ReadString(reader); err != nil { + if msg.Name, err = reader.ReadString(); err != nil { return nil, err } - if msg.Message, err = ReadString(reader); err != nil { + if msg.Message, err = reader.ReadString(); err != nil { return nil, err } - if msg.Payload, err = ReadString(reader); err != nil { + if msg.Payload, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeIntegrationEvent(reader io.Reader) (Message, error) { +func DecodeIntegrationEvent(reader BytesReader) (Message, error) { var err error = nil msg := &IntegrationEvent{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Source, err = ReadString(reader); err != nil { + if msg.Source, err = reader.ReadString(); err != nil { return nil, err } - if msg.Name, err = ReadString(reader); err != nil { + if msg.Name, err = reader.ReadString(); err != nil { return nil, err } - if msg.Message, err = ReadString(reader); err != nil { + if msg.Message, err = reader.ReadString(); err != nil { return nil, err } - if msg.Payload, err = ReadString(reader); err != nil { + if msg.Payload, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeRawCustomEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &RawCustomEvent{} - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Payload, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err -} - -func DecodeUserID(reader io.Reader) (Message, error) { - var err error = nil - msg := &UserID{} - if msg.ID, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err -} - -func DecodeUserAnonymousID(reader io.Reader) (Message, error) { - var err error = nil - msg := &UserAnonymousID{} - if msg.ID, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err -} - -func DecodeMetadata(reader io.Reader) (Message, error) { - var err error = nil - msg := &Metadata{} - if msg.Key, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err -} - -func DecodePageEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &PageEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.URL, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Referrer, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Loaded, err = ReadBoolean(reader); err != nil { - return nil, err - } - if msg.RequestStart, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ResponseStart, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ResponseEnd, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.DomContentLoadedEventStart, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.DomContentLoadedEventEnd, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.LoadEventStart, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.LoadEventEnd, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.FirstPaint, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.FirstContentfulPaint, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.SpeedIndex, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.VisuallyComplete, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.TimeToInteractive, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err -} - -func DecodeInputEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &InputEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - if msg.ValueMasked, err = ReadBoolean(reader); err != nil { - return nil, err - } - if msg.Label, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err -} - -func DecodeClickEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &ClickEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.HesitationTime, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Label, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Selector, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err -} - -func DecodeResourceEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &ResourceEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Duration, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.TTFB, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.HeaderSize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.EncodedBodySize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.DecodedBodySize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.URL, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Type, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Success, err = ReadBoolean(reader); err != nil { - return nil, err - } - if msg.Method, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Status, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err -} - -func DecodeCustomEvent(reader io.Reader) (Message, error) { +func DecodeCustomEvent(reader BytesReader) (Message, error) { var err error = nil msg := &CustomEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { + if msg.Name, err = reader.ReadString(); err != nil { return nil, err } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Payload, err = ReadString(reader); err != nil { + if msg.Payload, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeCSSInsertRule(reader io.Reader) (Message, error) { +func DecodeUserID(reader BytesReader) (Message, error) { + var err error = nil + msg := &UserID{} + if msg.ID, err = reader.ReadString(); err != nil { + return nil, err + } + return msg, err +} + +func DecodeUserAnonymousID(reader BytesReader) (Message, error) { + var err error = nil + msg := &UserAnonymousID{} + if msg.ID, err = reader.ReadString(); err != nil { + return nil, err + } + return msg, err +} + +func DecodeMetadata(reader BytesReader) (Message, error) { + var err error = nil + msg := &Metadata{} + if msg.Key, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Value, err = reader.ReadString(); err != nil { + return nil, err + } + return msg, err +} + +func DecodePageEvent(reader BytesReader) (Message, error) { + var err error = nil + msg := &PageEvent{} + if msg.MessageID, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.URL, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Referrer, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Loaded, err = reader.ReadBoolean(); err != nil { + return nil, err + } + if msg.RequestStart, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.ResponseStart, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.ResponseEnd, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.DomContentLoadedEventStart, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.DomContentLoadedEventEnd, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.LoadEventStart, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.LoadEventEnd, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.FirstPaint, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.FirstContentfulPaint, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.SpeedIndex, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.VisuallyComplete, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.TimeToInteractive, err = reader.ReadUint(); err != nil { + return nil, err + } + return msg, err +} + +func DecodeInputEvent(reader BytesReader) (Message, error) { + var err error = nil + msg := &InputEvent{} + if msg.MessageID, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Value, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.ValueMasked, err = reader.ReadBoolean(); err != nil { + return nil, err + } + if msg.Label, err = reader.ReadString(); err != nil { + return nil, err + } + return msg, err +} + +func DecodeClickEvent(reader BytesReader) (Message, error) { + var err error = nil + msg := &ClickEvent{} + if msg.MessageID, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.HesitationTime, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Label, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Selector, err = reader.ReadString(); err != nil { + return nil, err + } + return msg, err +} + +func DecodeResourceEvent(reader BytesReader) (Message, error) { + var err error = nil + msg := &ResourceEvent{} + if msg.MessageID, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Duration, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.TTFB, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.HeaderSize, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.EncodedBodySize, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.DecodedBodySize, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.URL, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Type, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Success, err = reader.ReadBoolean(); err != nil { + return nil, err + } + if msg.Method, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Status, err = reader.ReadUint(); err != nil { + return nil, err + } + return msg, err +} + +func DecodeCSSInsertRule(reader BytesReader) (Message, error) { var err error = nil msg := &CSSInsertRule{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Rule, err = ReadString(reader); err != nil { + if msg.Rule, err = reader.ReadString(); err != nil { return nil, err } - if msg.Index, err = ReadUint(reader); err != nil { + if msg.Index, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeCSSDeleteRule(reader io.Reader) (Message, error) { +func DecodeCSSDeleteRule(reader BytesReader) (Message, error) { var err error = nil msg := &CSSDeleteRule{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Index, err = ReadUint(reader); err != nil { + if msg.Index, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeFetch(reader io.Reader) (Message, error) { +func DecodeFetch(reader BytesReader) (Message, error) { var err error = nil msg := &Fetch{} - if msg.Method, err = ReadString(reader); err != nil { + if msg.Method, err = reader.ReadString(); err != nil { return nil, err } - if msg.URL, err = ReadString(reader); err != nil { + if msg.URL, err = reader.ReadString(); err != nil { return nil, err } - if msg.Request, err = ReadString(reader); err != nil { + if msg.Request, err = reader.ReadString(); err != nil { return nil, err } - if msg.Response, err = ReadString(reader); err != nil { + if msg.Response, err = reader.ReadString(); err != nil { return nil, err } - if msg.Status, err = ReadUint(reader); err != nil { + if msg.Status, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Duration, err = ReadUint(reader); err != nil { + if msg.Duration, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeProfiler(reader io.Reader) (Message, error) { +func DecodeProfiler(reader BytesReader) (Message, error) { var err error = nil msg := &Profiler{} - if msg.Name, err = ReadString(reader); err != nil { + if msg.Name, err = reader.ReadString(); err != nil { return nil, err } - if msg.Duration, err = ReadUint(reader); err != nil { + if msg.Duration, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Args, err = ReadString(reader); err != nil { + if msg.Args, err = reader.ReadString(); err != nil { return nil, err } - if msg.Result, err = ReadString(reader); err != nil { + if msg.Result, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeOTable(reader io.Reader) (Message, error) { +func DecodeOTable(reader BytesReader) (Message, error) { var err error = nil msg := &OTable{} - if msg.Key, err = ReadString(reader); err != nil { + if msg.Key, err = reader.ReadString(); err != nil { return nil, err } - if msg.Value, err = ReadString(reader); err != nil { + if msg.Value, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeStateAction(reader io.Reader) (Message, error) { +func DecodeStateAction(reader BytesReader) (Message, error) { var err error = nil msg := &StateAction{} - if msg.Type, err = ReadString(reader); err != nil { + if msg.Type, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeStateActionEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &StateActionEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Type, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err -} - -func DecodeRedux(reader io.Reader) (Message, error) { +func DecodeRedux(reader BytesReader) (Message, error) { var err error = nil msg := &Redux{} - if msg.Action, err = ReadString(reader); err != nil { + if msg.Action, err = reader.ReadString(); err != nil { return nil, err } - if msg.State, err = ReadString(reader); err != nil { + if msg.State, err = reader.ReadString(); err != nil { return nil, err } - if msg.Duration, err = ReadUint(reader); err != nil { + if msg.Duration, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeVuex(reader io.Reader) (Message, error) { +func DecodeVuex(reader BytesReader) (Message, error) { var err error = nil msg := &Vuex{} - if msg.Mutation, err = ReadString(reader); err != nil { + if msg.Mutation, err = reader.ReadString(); err != nil { return nil, err } - if msg.State, err = ReadString(reader); err != nil { + if msg.State, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeMobX(reader io.Reader) (Message, error) { +func DecodeMobX(reader BytesReader) (Message, error) { var err error = nil msg := &MobX{} - if msg.Type, err = ReadString(reader); err != nil { + if msg.Type, err = reader.ReadString(); err != nil { return nil, err } - if msg.Payload, err = ReadString(reader); err != nil { + if msg.Payload, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeNgRx(reader io.Reader) (Message, error) { +func DecodeNgRx(reader BytesReader) (Message, error) { var err error = nil msg := &NgRx{} - if msg.Action, err = ReadString(reader); err != nil { + if msg.Action, err = reader.ReadString(); err != nil { return nil, err } - if msg.State, err = ReadString(reader); err != nil { + if msg.State, err = reader.ReadString(); err != nil { return nil, err } - if msg.Duration, err = ReadUint(reader); err != nil { + if msg.Duration, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeGraphQL(reader io.Reader) (Message, error) { +func DecodeGraphQL(reader BytesReader) (Message, error) { var err error = nil msg := &GraphQL{} - if msg.OperationKind, err = ReadString(reader); err != nil { + if msg.OperationKind, err = reader.ReadString(); err != nil { return nil, err } - if msg.OperationName, err = ReadString(reader); err != nil { + if msg.OperationName, err = reader.ReadString(); err != nil { return nil, err } - if msg.Variables, err = ReadString(reader); err != nil { + if msg.Variables, err = reader.ReadString(); err != nil { return nil, err } - if msg.Response, err = ReadString(reader); err != nil { + if msg.Response, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodePerformanceTrack(reader io.Reader) (Message, error) { +func DecodePerformanceTrack(reader BytesReader) (Message, error) { var err error = nil msg := &PerformanceTrack{} - if msg.Frames, err = ReadInt(reader); err != nil { + if msg.Frames, err = reader.ReadInt(); err != nil { return nil, err } - if msg.Ticks, err = ReadInt(reader); err != nil { + if msg.Ticks, err = reader.ReadInt(); err != nil { return nil, err } - if msg.TotalJSHeapSize, err = ReadUint(reader); err != nil { + if msg.TotalJSHeapSize, err = reader.ReadUint(); err != nil { return nil, err } - if msg.UsedJSHeapSize, err = ReadUint(reader); err != nil { + if msg.UsedJSHeapSize, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeGraphQLEvent(reader io.Reader) (Message, error) { +func DecodeStringDict(reader BytesReader) (Message, error) { var err error = nil - msg := &GraphQLEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { + msg := &StringDict{} + if msg.Key, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.OperationKind, err = ReadString(reader); err != nil { - return nil, err - } - if msg.OperationName, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Variables, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Response, err = ReadString(reader); err != nil { + if msg.Value, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeFetchEvent(reader io.Reader) (Message, error) { +func DecodeSetNodeAttributeDict(reader BytesReader) (Message, error) { var err error = nil - msg := &FetchEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { + msg := &SetNodeAttributeDict{} + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.NameKey, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Method, err = ReadString(reader); err != nil { - return nil, err - } - if msg.URL, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Request, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Response, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Status, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Duration, err = ReadUint(reader); err != nil { + if msg.ValueKey, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeDOMDrop(reader io.Reader) (Message, error) { +func DecodeDOMDrop(reader BytesReader) (Message, error) { var err error = nil msg := &DOMDrop{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeResourceTiming(reader io.Reader) (Message, error) { +func DecodeResourceTiming(reader BytesReader) (Message, error) { var err error = nil msg := &ResourceTiming{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Duration, err = ReadUint(reader); err != nil { + if msg.Duration, err = reader.ReadUint(); err != nil { return nil, err } - if msg.TTFB, err = ReadUint(reader); err != nil { + if msg.TTFB, err = reader.ReadUint(); err != nil { return nil, err } - if msg.HeaderSize, err = ReadUint(reader); err != nil { + if msg.HeaderSize, err = reader.ReadUint(); err != nil { return nil, err } - if msg.EncodedBodySize, err = ReadUint(reader); err != nil { + if msg.EncodedBodySize, err = reader.ReadUint(); err != nil { return nil, err } - if msg.DecodedBodySize, err = ReadUint(reader); err != nil { + if msg.DecodedBodySize, err = reader.ReadUint(); err != nil { return nil, err } - if msg.URL, err = ReadString(reader); err != nil { + if msg.URL, err = reader.ReadString(); err != nil { return nil, err } - if msg.Initiator, err = ReadString(reader); err != nil { + if msg.Initiator, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeConnectionInformation(reader io.Reader) (Message, error) { +func DecodeConnectionInformation(reader BytesReader) (Message, error) { var err error = nil msg := &ConnectionInformation{} - if msg.Downlink, err = ReadUint(reader); err != nil { + if msg.Downlink, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Type, err = ReadString(reader); err != nil { + if msg.Type, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeSetPageVisibility(reader io.Reader) (Message, error) { +func DecodeSetPageVisibility(reader BytesReader) (Message, error) { var err error = nil msg := &SetPageVisibility{} - if msg.hidden, err = ReadBoolean(reader); err != nil { + if msg.hidden, err = reader.ReadBoolean(); err != nil { return nil, err } return msg, err } -func DecodePerformanceTrackAggr(reader io.Reader) (Message, error) { +func DecodePerformanceTrackAggr(reader BytesReader) (Message, error) { var err error = nil msg := &PerformanceTrackAggr{} - if msg.TimestampStart, err = ReadUint(reader); err != nil { + if msg.TimestampStart, err = reader.ReadUint(); err != nil { return nil, err } - if msg.TimestampEnd, err = ReadUint(reader); err != nil { + if msg.TimestampEnd, err = reader.ReadUint(); err != nil { return nil, err } - if msg.MinFPS, err = ReadUint(reader); err != nil { + if msg.MinFPS, err = reader.ReadUint(); err != nil { return nil, err } - if msg.AvgFPS, err = ReadUint(reader); err != nil { + if msg.AvgFPS, err = reader.ReadUint(); err != nil { return nil, err } - if msg.MaxFPS, err = ReadUint(reader); err != nil { + if msg.MaxFPS, err = reader.ReadUint(); err != nil { return nil, err } - if msg.MinCPU, err = ReadUint(reader); err != nil { + if msg.MinCPU, err = reader.ReadUint(); err != nil { return nil, err } - if msg.AvgCPU, err = ReadUint(reader); err != nil { + if msg.AvgCPU, err = reader.ReadUint(); err != nil { return nil, err } - if msg.MaxCPU, err = ReadUint(reader); err != nil { + if msg.MaxCPU, err = reader.ReadUint(); err != nil { return nil, err } - if msg.MinTotalJSHeapSize, err = ReadUint(reader); err != nil { + if msg.MinTotalJSHeapSize, err = reader.ReadUint(); err != nil { return nil, err } - if msg.AvgTotalJSHeapSize, err = ReadUint(reader); err != nil { + if msg.AvgTotalJSHeapSize, err = reader.ReadUint(); err != nil { return nil, err } - if msg.MaxTotalJSHeapSize, err = ReadUint(reader); err != nil { + if msg.MaxTotalJSHeapSize, err = reader.ReadUint(); err != nil { return nil, err } - if msg.MinUsedJSHeapSize, err = ReadUint(reader); err != nil { + if msg.MinUsedJSHeapSize, err = reader.ReadUint(); err != nil { return nil, err } - if msg.AvgUsedJSHeapSize, err = ReadUint(reader); err != nil { + if msg.AvgUsedJSHeapSize, err = reader.ReadUint(); err != nil { return nil, err } - if msg.MaxUsedJSHeapSize, err = ReadUint(reader); err != nil { + if msg.MaxUsedJSHeapSize, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeLoadFontFace(reader io.Reader) (Message, error) { +func DecodeLoadFontFace(reader BytesReader) (Message, error) { var err error = nil msg := &LoadFontFace{} - if msg.ParentID, err = ReadUint(reader); err != nil { + if msg.ParentID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Family, err = ReadString(reader); err != nil { + if msg.Family, err = reader.ReadString(); err != nil { return nil, err } - if msg.Source, err = ReadString(reader); err != nil { + if msg.Source, err = reader.ReadString(); err != nil { return nil, err } - if msg.Descriptors, err = ReadString(reader); err != nil { + if msg.Descriptors, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeSetNodeFocus(reader io.Reader) (Message, error) { +func DecodeSetNodeFocus(reader BytesReader) (Message, error) { var err error = nil msg := &SetNodeFocus{} - if msg.ID, err = ReadInt(reader); err != nil { + if msg.ID, err = reader.ReadInt(); err != nil { return nil, err } return msg, err } -func DecodeLongTask(reader io.Reader) (Message, error) { +func DecodeLongTask(reader BytesReader) (Message, error) { var err error = nil msg := &LongTask{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Duration, err = ReadUint(reader); err != nil { + if msg.Duration, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Context, err = ReadUint(reader); err != nil { + if msg.Context, err = reader.ReadUint(); err != nil { return nil, err } - if msg.ContainerType, err = ReadUint(reader); err != nil { + if msg.ContainerType, err = reader.ReadUint(); err != nil { return nil, err } - if msg.ContainerSrc, err = ReadString(reader); err != nil { + if msg.ContainerSrc, err = reader.ReadString(); err != nil { return nil, err } - if msg.ContainerId, err = ReadString(reader); err != nil { + if msg.ContainerId, err = reader.ReadString(); err != nil { return nil, err } - if msg.ContainerName, err = ReadString(reader); err != nil { + if msg.ContainerName, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeSetNodeAttributeURLBased(reader io.Reader) (Message, error) { +func DecodeSetNodeAttributeURLBased(reader BytesReader) (Message, error) { var err error = nil msg := &SetNodeAttributeURLBased{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Name, err = ReadString(reader); err != nil { + if msg.Name, err = reader.ReadString(); err != nil { return nil, err } - if msg.Value, err = ReadString(reader); err != nil { + if msg.Value, err = reader.ReadString(); err != nil { return nil, err } - if msg.BaseURL, err = ReadString(reader); err != nil { + if msg.BaseURL, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeSetCSSDataURLBased(reader io.Reader) (Message, error) { +func DecodeSetCSSDataURLBased(reader BytesReader) (Message, error) { var err error = nil msg := &SetCSSDataURLBased{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Data, err = ReadString(reader); err != nil { + if msg.Data, err = reader.ReadString(); err != nil { return nil, err } - if msg.BaseURL, err = ReadString(reader); err != nil { + if msg.BaseURL, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeIssueEvent(reader io.Reader) (Message, error) { +func DecodeIssueEventDeprecated(reader BytesReader) (Message, error) { var err error = nil - msg := &IssueEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { + msg := &IssueEventDeprecated{} + if msg.MessageID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Type, err = ReadString(reader); err != nil { + if msg.Type, err = reader.ReadString(); err != nil { return nil, err } - if msg.ContextString, err = ReadString(reader); err != nil { + if msg.ContextString, err = reader.ReadString(); err != nil { return nil, err } - if msg.Context, err = ReadString(reader); err != nil { + if msg.Context, err = reader.ReadString(); err != nil { return nil, err } - if msg.Payload, err = ReadString(reader); err != nil { + if msg.Payload, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeTechnicalInfo(reader io.Reader) (Message, error) { +func DecodeTechnicalInfo(reader BytesReader) (Message, error) { var err error = nil msg := &TechnicalInfo{} - if msg.Type, err = ReadString(reader); err != nil { + if msg.Type, err = reader.ReadString(); err != nil { return nil, err } - if msg.Value, err = ReadString(reader); err != nil { + if msg.Value, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeCustomIssue(reader io.Reader) (Message, error) { +func DecodeCustomIssue(reader BytesReader) (Message, error) { var err error = nil msg := &CustomIssue{} - if msg.Name, err = ReadString(reader); err != nil { + if msg.Name, err = reader.ReadString(); err != nil { return nil, err } - if msg.Payload, err = ReadString(reader); err != nil { + if msg.Payload, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeAssetCache(reader io.Reader) (Message, error) { +func DecodeAssetCache(reader BytesReader) (Message, error) { var err error = nil msg := &AssetCache{} - if msg.URL, err = ReadString(reader); err != nil { + if msg.URL, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeCSSInsertRuleURLBased(reader io.Reader) (Message, error) { +func DecodeCSSInsertRuleURLBased(reader BytesReader) (Message, error) { var err error = nil msg := &CSSInsertRuleURLBased{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Rule, err = ReadString(reader); err != nil { + if msg.Rule, err = reader.ReadString(); err != nil { return nil, err } - if msg.Index, err = ReadUint(reader); err != nil { + if msg.Index, err = reader.ReadUint(); err != nil { return nil, err } - if msg.BaseURL, err = ReadString(reader); err != nil { + if msg.BaseURL, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeMouseClick(reader io.Reader) (Message, error) { +func DecodeMouseClick(reader BytesReader) (Message, error) { var err error = nil msg := &MouseClick{} - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.HesitationTime, err = ReadUint(reader); err != nil { + if msg.HesitationTime, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Label, err = ReadString(reader); err != nil { + if msg.Label, err = reader.ReadString(); err != nil { return nil, err } - if msg.Selector, err = ReadString(reader); err != nil { + if msg.Selector, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeCreateIFrameDocument(reader io.Reader) (Message, error) { +func DecodeCreateIFrameDocument(reader BytesReader) (Message, error) { var err error = nil msg := &CreateIFrameDocument{} - if msg.FrameID, err = ReadUint(reader); err != nil { + if msg.FrameID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeAdoptedSSReplaceURLBased(reader io.Reader) (Message, error) { +func DecodeAdoptedSSReplaceURLBased(reader BytesReader) (Message, error) { var err error = nil msg := &AdoptedSSReplaceURLBased{} - if msg.SheetID, err = ReadUint(reader); err != nil { + if msg.SheetID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Text, err = ReadString(reader); err != nil { + if msg.Text, err = reader.ReadString(); err != nil { return nil, err } - if msg.BaseURL, err = ReadString(reader); err != nil { + if msg.BaseURL, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeAdoptedSSReplace(reader io.Reader) (Message, error) { +func DecodeAdoptedSSReplace(reader BytesReader) (Message, error) { var err error = nil msg := &AdoptedSSReplace{} - if msg.SheetID, err = ReadUint(reader); err != nil { + if msg.SheetID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Text, err = ReadString(reader); err != nil { + if msg.Text, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeAdoptedSSInsertRuleURLBased(reader io.Reader) (Message, error) { +func DecodeAdoptedSSInsertRuleURLBased(reader BytesReader) (Message, error) { var err error = nil msg := &AdoptedSSInsertRuleURLBased{} - if msg.SheetID, err = ReadUint(reader); err != nil { + if msg.SheetID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Rule, err = ReadString(reader); err != nil { + if msg.Rule, err = reader.ReadString(); err != nil { return nil, err } - if msg.Index, err = ReadUint(reader); err != nil { + if msg.Index, err = reader.ReadUint(); err != nil { return nil, err } - if msg.BaseURL, err = ReadString(reader); err != nil { + if msg.BaseURL, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeAdoptedSSInsertRule(reader io.Reader) (Message, error) { +func DecodeAdoptedSSInsertRule(reader BytesReader) (Message, error) { var err error = nil msg := &AdoptedSSInsertRule{} - if msg.SheetID, err = ReadUint(reader); err != nil { + if msg.SheetID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Rule, err = ReadString(reader); err != nil { + if msg.Rule, err = reader.ReadString(); err != nil { return nil, err } - if msg.Index, err = ReadUint(reader); err != nil { + if msg.Index, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeAdoptedSSDeleteRule(reader io.Reader) (Message, error) { +func DecodeAdoptedSSDeleteRule(reader BytesReader) (Message, error) { var err error = nil msg := &AdoptedSSDeleteRule{} - if msg.SheetID, err = ReadUint(reader); err != nil { + if msg.SheetID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Index, err = ReadUint(reader); err != nil { + if msg.Index, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeAdoptedSSAddOwner(reader io.Reader) (Message, error) { +func DecodeAdoptedSSAddOwner(reader BytesReader) (Message, error) { var err error = nil msg := &AdoptedSSAddOwner{} - if msg.SheetID, err = ReadUint(reader); err != nil { + if msg.SheetID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeAdoptedSSRemoveOwner(reader io.Reader) (Message, error) { +func DecodeAdoptedSSRemoveOwner(reader BytesReader) (Message, error) { var err error = nil msg := &AdoptedSSRemoveOwner{} - if msg.SheetID, err = ReadUint(reader); err != nil { + if msg.SheetID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.ID, err = ReadUint(reader); err != nil { + if msg.ID, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeZustand(reader io.Reader) (Message, error) { - var err error = nil - msg := &Zustand{} - if msg.Mutation, err = ReadString(reader); err != nil { - return nil, err - } - if msg.State, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err -} - -func DecodeJSException(reader io.Reader) (Message, error) { +func DecodeJSException(reader BytesReader) (Message, error) { var err error = nil msg := &JSException{} - if msg.Name, err = ReadString(reader); err != nil { + if msg.Name, err = reader.ReadString(); err != nil { return nil, err } - if msg.Message, err = ReadString(reader); err != nil { + if msg.Message, err = reader.ReadString(); err != nil { return nil, err } - if msg.Payload, err = ReadString(reader); err != nil { + if msg.Payload, err = reader.ReadString(); err != nil { return nil, err } - if msg.Metadata, err = ReadString(reader); err != nil { + if msg.Metadata, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeSessionEnd(reader io.Reader) (Message, error) { +func DecodeZustand(reader BytesReader) (Message, error) { + var err error = nil + msg := &Zustand{} + if msg.Mutation, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.State, err = reader.ReadString(); err != nil { + return nil, err + } + return msg, err +} + +func DecodeBatchMeta(reader BytesReader) (Message, error) { + var err error = nil + msg := &BatchMeta{} + if msg.PageNo, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.FirstIndex, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Timestamp, err = reader.ReadInt(); err != nil { + return nil, err + } + return msg, err +} + +func DecodeBatchMetadata(reader BytesReader) (Message, error) { + var err error = nil + msg := &BatchMetadata{} + if msg.Version, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.PageNo, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.FirstIndex, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Timestamp, err = reader.ReadInt(); err != nil { + return nil, err + } + if msg.Location, err = reader.ReadString(); err != nil { + return nil, err + } + return msg, err +} + +func DecodePartitionedMessage(reader BytesReader) (Message, error) { + var err error = nil + msg := &PartitionedMessage{} + if msg.PartNo, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.PartTotal, err = reader.ReadUint(); err != nil { + return nil, err + } + return msg, err +} + +func DecodeIssueEvent(reader BytesReader) (Message, error) { + var err error = nil + msg := &IssueEvent{} + if msg.MessageID, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Timestamp, err = reader.ReadUint(); err != nil { + return nil, err + } + if msg.Type, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.ContextString, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Context, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.Payload, err = reader.ReadString(); err != nil { + return nil, err + } + if msg.URL, err = reader.ReadString(); err != nil { + return nil, err + } + return msg, err +} + +func DecodeSessionEnd(reader BytesReader) (Message, error) { var err error = nil msg := &SessionEnd{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.EncryptionKey, err = ReadString(reader); err != nil { + if msg.EncryptionKey, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeSessionSearch(reader io.Reader) (Message, error) { +func DecodeSessionSearch(reader BytesReader) (Message, error) { var err error = nil msg := &SessionSearch{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Partition, err = ReadUint(reader); err != nil { + if msg.Partition, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeIOSBatchMeta(reader io.Reader) (Message, error) { +func DecodeIOSBatchMeta(reader BytesReader) (Message, error) { var err error = nil msg := &IOSBatchMeta{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Length, err = ReadUint(reader); err != nil { + if msg.Length, err = reader.ReadUint(); err != nil { return nil, err } - if msg.FirstIndex, err = ReadUint(reader); err != nil { + if msg.FirstIndex, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeIOSSessionStart(reader io.Reader) (Message, error) { +func DecodeIOSSessionStart(reader BytesReader) (Message, error) { var err error = nil msg := &IOSSessionStart{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.ProjectID, err = ReadUint(reader); err != nil { + if msg.ProjectID, err = reader.ReadUint(); err != nil { return nil, err } - if msg.TrackerVersion, err = ReadString(reader); err != nil { + if msg.TrackerVersion, err = reader.ReadString(); err != nil { return nil, err } - if msg.RevID, err = ReadString(reader); err != nil { + if msg.RevID, err = reader.ReadString(); err != nil { return nil, err } - if msg.UserUUID, err = ReadString(reader); err != nil { + if msg.UserUUID, err = reader.ReadString(); err != nil { return nil, err } - if msg.UserOS, err = ReadString(reader); err != nil { + if msg.UserOS, err = reader.ReadString(); err != nil { return nil, err } - if msg.UserOSVersion, err = ReadString(reader); err != nil { + if msg.UserOSVersion, err = reader.ReadString(); err != nil { return nil, err } - if msg.UserDevice, err = ReadString(reader); err != nil { + if msg.UserDevice, err = reader.ReadString(); err != nil { return nil, err } - if msg.UserDeviceType, err = ReadString(reader); err != nil { + if msg.UserDeviceType, err = reader.ReadString(); err != nil { return nil, err } - if msg.UserCountry, err = ReadString(reader); err != nil { + if msg.UserCountry, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeIOSSessionEnd(reader io.Reader) (Message, error) { +func DecodeIOSSessionEnd(reader BytesReader) (Message, error) { var err error = nil msg := &IOSSessionEnd{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeIOSMetadata(reader io.Reader) (Message, error) { +func DecodeIOSMetadata(reader BytesReader) (Message, error) { var err error = nil msg := &IOSMetadata{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Length, err = ReadUint(reader); err != nil { + if msg.Length, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Key, err = ReadString(reader); err != nil { + if msg.Key, err = reader.ReadString(); err != nil { return nil, err } - if msg.Value, err = ReadString(reader); err != nil { + if msg.Value, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeIOSCustomEvent(reader io.Reader) (Message, error) { +func DecodeIOSCustomEvent(reader BytesReader) (Message, error) { var err error = nil msg := &IOSCustomEvent{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Length, err = ReadUint(reader); err != nil { + if msg.Length, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Name, err = ReadString(reader); err != nil { + if msg.Name, err = reader.ReadString(); err != nil { return nil, err } - if msg.Payload, err = ReadString(reader); err != nil { + if msg.Payload, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeIOSUserID(reader io.Reader) (Message, error) { +func DecodeIOSUserID(reader BytesReader) (Message, error) { var err error = nil msg := &IOSUserID{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Length, err = ReadUint(reader); err != nil { + if msg.Length, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Value, err = ReadString(reader); err != nil { + if msg.Value, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeIOSUserAnonymousID(reader io.Reader) (Message, error) { +func DecodeIOSUserAnonymousID(reader BytesReader) (Message, error) { var err error = nil msg := &IOSUserAnonymousID{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Length, err = ReadUint(reader); err != nil { + if msg.Length, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Value, err = ReadString(reader); err != nil { + if msg.Value, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeIOSScreenChanges(reader io.Reader) (Message, error) { +func DecodeIOSScreenChanges(reader BytesReader) (Message, error) { var err error = nil msg := &IOSScreenChanges{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Length, err = ReadUint(reader); err != nil { + if msg.Length, err = reader.ReadUint(); err != nil { return nil, err } - if msg.X, err = ReadUint(reader); err != nil { + if msg.X, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Y, err = ReadUint(reader); err != nil { + if msg.Y, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Width, err = ReadUint(reader); err != nil { + if msg.Width, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Height, err = ReadUint(reader); err != nil { + if msg.Height, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeIOSCrash(reader io.Reader) (Message, error) { +func DecodeIOSCrash(reader BytesReader) (Message, error) { var err error = nil msg := &IOSCrash{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Length, err = ReadUint(reader); err != nil { + if msg.Length, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Name, err = ReadString(reader); err != nil { + if msg.Name, err = reader.ReadString(); err != nil { return nil, err } - if msg.Reason, err = ReadString(reader); err != nil { + if msg.Reason, err = reader.ReadString(); err != nil { return nil, err } - if msg.Stacktrace, err = ReadString(reader); err != nil { + if msg.Stacktrace, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeIOSScreenEnter(reader io.Reader) (Message, error) { +func DecodeIOSScreenEnter(reader BytesReader) (Message, error) { var err error = nil msg := &IOSScreenEnter{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Length, err = ReadUint(reader); err != nil { + if msg.Length, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Title, err = ReadString(reader); err != nil { + if msg.Title, err = reader.ReadString(); err != nil { return nil, err } - if msg.ViewName, err = ReadString(reader); err != nil { + if msg.ViewName, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeIOSScreenLeave(reader io.Reader) (Message, error) { +func DecodeIOSScreenLeave(reader BytesReader) (Message, error) { var err error = nil msg := &IOSScreenLeave{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Length, err = ReadUint(reader); err != nil { + if msg.Length, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Title, err = ReadString(reader); err != nil { + if msg.Title, err = reader.ReadString(); err != nil { return nil, err } - if msg.ViewName, err = ReadString(reader); err != nil { + if msg.ViewName, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeIOSClickEvent(reader io.Reader) (Message, error) { +func DecodeIOSClickEvent(reader BytesReader) (Message, error) { var err error = nil msg := &IOSClickEvent{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Length, err = ReadUint(reader); err != nil { + if msg.Length, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Label, err = ReadString(reader); err != nil { + if msg.Label, err = reader.ReadString(); err != nil { return nil, err } - if msg.X, err = ReadUint(reader); err != nil { + if msg.X, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Y, err = ReadUint(reader); err != nil { + if msg.Y, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeIOSInputEvent(reader io.Reader) (Message, error) { +func DecodeIOSInputEvent(reader BytesReader) (Message, error) { var err error = nil msg := &IOSInputEvent{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Length, err = ReadUint(reader); err != nil { + if msg.Length, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Value, err = ReadString(reader); err != nil { + if msg.Value, err = reader.ReadString(); err != nil { return nil, err } - if msg.ValueMasked, err = ReadBoolean(reader); err != nil { + if msg.ValueMasked, err = reader.ReadBoolean(); err != nil { return nil, err } - if msg.Label, err = ReadString(reader); err != nil { + if msg.Label, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeIOSPerformanceEvent(reader io.Reader) (Message, error) { +func DecodeIOSPerformanceEvent(reader BytesReader) (Message, error) { var err error = nil msg := &IOSPerformanceEvent{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Length, err = ReadUint(reader); err != nil { + if msg.Length, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Name, err = ReadString(reader); err != nil { + if msg.Name, err = reader.ReadString(); err != nil { return nil, err } - if msg.Value, err = ReadUint(reader); err != nil { + if msg.Value, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeIOSLog(reader io.Reader) (Message, error) { +func DecodeIOSLog(reader BytesReader) (Message, error) { var err error = nil msg := &IOSLog{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Length, err = ReadUint(reader); err != nil { + if msg.Length, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Severity, err = ReadString(reader); err != nil { + if msg.Severity, err = reader.ReadString(); err != nil { return nil, err } - if msg.Content, err = ReadString(reader); err != nil { + if msg.Content, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeIOSInternalError(reader io.Reader) (Message, error) { +func DecodeIOSInternalError(reader BytesReader) (Message, error) { var err error = nil msg := &IOSInternalError{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Length, err = ReadUint(reader); err != nil { + if msg.Length, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Content, err = ReadString(reader); err != nil { + if msg.Content, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func DecodeIOSNetworkCall(reader io.Reader) (Message, error) { +func DecodeIOSNetworkCall(reader BytesReader) (Message, error) { var err error = nil msg := &IOSNetworkCall{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Length, err = ReadUint(reader); err != nil { + if msg.Length, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Duration, err = ReadUint(reader); err != nil { + if msg.Duration, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Headers, err = ReadString(reader); err != nil { + if msg.Headers, err = reader.ReadString(); err != nil { return nil, err } - if msg.Body, err = ReadString(reader); err != nil { + if msg.Body, err = reader.ReadString(); err != nil { return nil, err } - if msg.URL, err = ReadString(reader); err != nil { + if msg.URL, err = reader.ReadString(); err != nil { return nil, err } - if msg.Success, err = ReadBoolean(reader); err != nil { + if msg.Success, err = reader.ReadBoolean(); err != nil { return nil, err } - if msg.Method, err = ReadString(reader); err != nil { + if msg.Method, err = reader.ReadString(); err != nil { return nil, err } - if msg.Status, err = ReadUint(reader); err != nil { + if msg.Status, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeIOSPerformanceAggregated(reader io.Reader) (Message, error) { +func DecodeIOSPerformanceAggregated(reader BytesReader) (Message, error) { var err error = nil msg := &IOSPerformanceAggregated{} - if msg.TimestampStart, err = ReadUint(reader); err != nil { + if msg.TimestampStart, err = reader.ReadUint(); err != nil { return nil, err } - if msg.TimestampEnd, err = ReadUint(reader); err != nil { + if msg.TimestampEnd, err = reader.ReadUint(); err != nil { return nil, err } - if msg.MinFPS, err = ReadUint(reader); err != nil { + if msg.MinFPS, err = reader.ReadUint(); err != nil { return nil, err } - if msg.AvgFPS, err = ReadUint(reader); err != nil { + if msg.AvgFPS, err = reader.ReadUint(); err != nil { return nil, err } - if msg.MaxFPS, err = ReadUint(reader); err != nil { + if msg.MaxFPS, err = reader.ReadUint(); err != nil { return nil, err } - if msg.MinCPU, err = ReadUint(reader); err != nil { + if msg.MinCPU, err = reader.ReadUint(); err != nil { return nil, err } - if msg.AvgCPU, err = ReadUint(reader); err != nil { + if msg.AvgCPU, err = reader.ReadUint(); err != nil { return nil, err } - if msg.MaxCPU, err = ReadUint(reader); err != nil { + if msg.MaxCPU, err = reader.ReadUint(); err != nil { return nil, err } - if msg.MinMemory, err = ReadUint(reader); err != nil { + if msg.MinMemory, err = reader.ReadUint(); err != nil { return nil, err } - if msg.AvgMemory, err = ReadUint(reader); err != nil { + if msg.AvgMemory, err = reader.ReadUint(); err != nil { return nil, err } - if msg.MaxMemory, err = ReadUint(reader); err != nil { + if msg.MaxMemory, err = reader.ReadUint(); err != nil { return nil, err } - if msg.MinBattery, err = ReadUint(reader); err != nil { + if msg.MinBattery, err = reader.ReadUint(); err != nil { return nil, err } - if msg.AvgBattery, err = ReadUint(reader); err != nil { + if msg.AvgBattery, err = reader.ReadUint(); err != nil { return nil, err } - if msg.MaxBattery, err = ReadUint(reader); err != nil { + if msg.MaxBattery, err = reader.ReadUint(); err != nil { return nil, err } return msg, err } -func DecodeIOSIssueEvent(reader io.Reader) (Message, error) { +func DecodeIOSIssueEvent(reader BytesReader) (Message, error) { var err error = nil msg := &IOSIssueEvent{} - if msg.Timestamp, err = ReadUint(reader); err != nil { + if msg.Timestamp, err = reader.ReadUint(); err != nil { return nil, err } - if msg.Type, err = ReadString(reader); err != nil { + if msg.Type, err = reader.ReadString(); err != nil { return nil, err } - if msg.ContextString, err = ReadString(reader); err != nil { + if msg.ContextString, err = reader.ReadString(); err != nil { return nil, err } - if msg.Context, err = ReadString(reader); err != nil { + if msg.Context, err = reader.ReadString(); err != nil { return nil, err } - if msg.Payload, err = ReadString(reader); err != nil { + if msg.Payload, err = reader.ReadString(); err != nil { return nil, err } return msg, err } -func ReadMessage(t uint64, reader io.Reader) (Message, error) { +func ReadMessage(t uint64, reader BytesReader) (Message, error) { switch t { - - case 80: - return DecodeBatchMeta(reader) - - case 81: - return DecodeBatchMetadata(reader) - - case 82: - return DecodePartitionedMessage(reader) - case 0: return DecodeTimestamp(reader) - case 1: return DecodeSessionStart(reader) - case 3: return DecodeSessionEndDeprecated(reader) - case 4: return DecodeSetPageLocation(reader) - case 5: return DecodeSetViewportSize(reader) - case 6: return DecodeSetViewportScroll(reader) - case 7: return DecodeCreateDocument(reader) - case 8: return DecodeCreateElementNode(reader) - case 9: return DecodeCreateTextNode(reader) - case 10: return DecodeMoveNode(reader) - case 11: return DecodeRemoveNode(reader) - case 12: return DecodeSetNodeAttribute(reader) - case 13: return DecodeRemoveNodeAttribute(reader) - case 14: return DecodeSetNodeData(reader) - case 15: return DecodeSetCSSData(reader) - case 16: return DecodeSetNodeScroll(reader) - case 17: return DecodeSetInputTarget(reader) - case 18: return DecodeSetInputValue(reader) - case 19: return DecodeSetInputChecked(reader) - case 20: return DecodeMouseMove(reader) - + case 21: + return DecodeNetworkRequest(reader) case 22: return DecodeConsoleLog(reader) - case 23: return DecodePageLoadTiming(reader) - case 24: return DecodePageRenderTiming(reader) - case 25: return DecodeJSExceptionDeprecated(reader) - case 26: return DecodeIntegrationEvent(reader) - case 27: - return DecodeRawCustomEvent(reader) - + return DecodeCustomEvent(reader) case 28: return DecodeUserID(reader) - case 29: return DecodeUserAnonymousID(reader) - case 30: return DecodeMetadata(reader) - case 31: return DecodePageEvent(reader) - case 32: return DecodeInputEvent(reader) - case 33: return DecodeClickEvent(reader) - case 35: return DecodeResourceEvent(reader) - - case 36: - return DecodeCustomEvent(reader) - case 37: return DecodeCSSInsertRule(reader) - case 38: return DecodeCSSDeleteRule(reader) - case 39: return DecodeFetch(reader) - case 40: return DecodeProfiler(reader) - case 41: return DecodeOTable(reader) - case 42: return DecodeStateAction(reader) - - case 43: - return DecodeStateActionEvent(reader) - case 44: return DecodeRedux(reader) - case 45: return DecodeVuex(reader) - case 46: return DecodeMobX(reader) - case 47: return DecodeNgRx(reader) - case 48: return DecodeGraphQL(reader) - case 49: return DecodePerformanceTrack(reader) - case 50: - return DecodeGraphQLEvent(reader) - + return DecodeStringDict(reader) case 51: - return DecodeFetchEvent(reader) - + return DecodeSetNodeAttributeDict(reader) case 52: return DecodeDOMDrop(reader) - case 53: return DecodeResourceTiming(reader) - case 54: return DecodeConnectionInformation(reader) - case 55: return DecodeSetPageVisibility(reader) - case 56: return DecodePerformanceTrackAggr(reader) - case 57: return DecodeLoadFontFace(reader) - case 58: return DecodeSetNodeFocus(reader) - case 59: return DecodeLongTask(reader) - case 60: return DecodeSetNodeAttributeURLBased(reader) - case 61: return DecodeSetCSSDataURLBased(reader) - case 62: - return DecodeIssueEvent(reader) - + return DecodeIssueEventDeprecated(reader) case 63: return DecodeTechnicalInfo(reader) - case 64: return DecodeCustomIssue(reader) - case 66: return DecodeAssetCache(reader) - case 67: return DecodeCSSInsertRuleURLBased(reader) - case 69: return DecodeMouseClick(reader) - case 70: return DecodeCreateIFrameDocument(reader) - case 71: return DecodeAdoptedSSReplaceURLBased(reader) - case 72: return DecodeAdoptedSSReplace(reader) - case 73: return DecodeAdoptedSSInsertRuleURLBased(reader) - case 74: return DecodeAdoptedSSInsertRule(reader) - case 75: return DecodeAdoptedSSDeleteRule(reader) - case 76: return DecodeAdoptedSSAddOwner(reader) - case 77: return DecodeAdoptedSSRemoveOwner(reader) - - case 79: - return DecodeZustand(reader) - case 78: return DecodeJSException(reader) - + case 79: + return DecodeZustand(reader) + case 80: + return DecodeBatchMeta(reader) + case 81: + return DecodeBatchMetadata(reader) + case 82: + return DecodePartitionedMessage(reader) + case 125: + return DecodeIssueEvent(reader) case 126: return DecodeSessionEnd(reader) - case 127: return DecodeSessionSearch(reader) - case 107: return DecodeIOSBatchMeta(reader) - case 90: return DecodeIOSSessionStart(reader) - case 91: return DecodeIOSSessionEnd(reader) - case 92: return DecodeIOSMetadata(reader) - case 93: return DecodeIOSCustomEvent(reader) - case 94: return DecodeIOSUserID(reader) - case 95: return DecodeIOSUserAnonymousID(reader) - case 96: return DecodeIOSScreenChanges(reader) - case 97: return DecodeIOSCrash(reader) - case 98: return DecodeIOSScreenEnter(reader) - case 99: return DecodeIOSScreenLeave(reader) - case 100: return DecodeIOSClickEvent(reader) - case 101: return DecodeIOSInputEvent(reader) - case 102: return DecodeIOSPerformanceEvent(reader) - case 103: return DecodeIOSLog(reader) - case 104: return DecodeIOSInternalError(reader) - case 105: return DecodeIOSNetworkCall(reader) - case 110: return DecodeIOSPerformanceAggregated(reader) - case 111: return DecodeIOSIssueEvent(reader) - } return nil, fmt.Errorf("Unknown message code: %v", t) } diff --git a/backend/pkg/messages/reader.go b/backend/pkg/messages/reader.go new file mode 100644 index 000000000..1e9fa42db --- /dev/null +++ b/backend/pkg/messages/reader.go @@ -0,0 +1,166 @@ +package messages + +import ( + "fmt" + "io" +) + +type MessageReader interface { + Parse() (err error) + Next() bool + Message() Message +} + +func NewMessageReader(data []byte) MessageReader { + return &messageReaderImpl{ + data: data, + reader: NewBytesReader(data), + list: make([]*MessageMeta, 0, 1024), + } +} + +type MessageMeta struct { + msgType uint64 + msgSize uint64 + msgFrom uint64 +} + +type messageReaderImpl struct { + data []byte + reader BytesReader + msgType uint64 + msgSize uint64 + msgBody []byte + version int + broken bool + message Message + err error + list []*MessageMeta + listPtr int +} + +func (m *messageReaderImpl) Parse() (err error) { + m.listPtr = 0 + m.list = m.list[:0] + m.broken = false + for { + // Try to read and decode message type, message size and check range in + m.msgType, err = m.reader.ReadUint() + if err != nil { + if err != io.EOF { + return fmt.Errorf("read message type err: %s", err) + } + // Reached the end of batch + return nil + } + + // Read message body (and decode if protocol version less than 1) + if m.version > 0 && messageHasSize(m.msgType) { + // Read message size if it is a new protocol version + m.msgSize, err = m.reader.ReadSize() + if err != nil { + return fmt.Errorf("read message size err: %s", err) + } + + // Try to avoid EOF error + + curr := m.reader.Pointer() + if len(m.data)-int(curr) < int(m.msgSize) { + return fmt.Errorf("can't read message body") + } + + // Dirty hack to avoid extra memory allocation + m.data[curr-1] = uint8(m.msgType) + + // Add message meta to list + m.list = append(m.list, &MessageMeta{ + msgType: m.msgType, + msgSize: m.msgSize + 1, + msgFrom: uint64(curr - 1), + }) + + // Update data pointer + m.reader.SetPointer(curr + int64(m.msgSize)) + } else { + from := m.reader.Pointer() - 1 + msg, err := ReadMessage(m.msgType, m.reader) + if err != nil { + return fmt.Errorf("read message err: %s", err) + } + if m.msgType == MsgBatchMeta || m.msgType == MsgBatchMetadata { + if len(m.list) > 0 { + return fmt.Errorf("batch meta not at the start of batch") + } + switch message := msg.(type) { + case *BatchMetadata: + m.version = int(message.Version) + case *BatchMeta: + m.version = 0 + } + if m.version != 1 { + // Unsupported tracker version, reset reader + m.list = m.list[:0] + m.reader.SetPointer(0) + return nil + } + } + + // Add message meta to list + m.list = append(m.list, &MessageMeta{ + msgType: m.msgType, + msgSize: uint64(m.reader.Pointer() - from), + msgFrom: uint64(from), + }) + } + } +} + +func (m *messageReaderImpl) Next() bool { + if m.broken { + return false + } + + // For new version of tracker + if len(m.list) > 0 { + if m.listPtr >= len(m.list) { + return false + } + + meta := m.list[m.listPtr] + m.listPtr++ + m.message = &RawMessage{ + tp: meta.msgType, + data: m.data[meta.msgFrom : meta.msgFrom+meta.msgSize], + broken: &m.broken, + meta: &message{}, + } + return true + } + + // For prev version of tracker + var msg Message + var err error + + // Try to read and decode message type, message size and check range in + m.msgType, err = m.reader.ReadUint() + if err != nil { + if err != io.EOF { + m.err = fmt.Errorf("read message type err: %s", err) + } + // Reached the end of batch + return false + } + + // Read and decode message + msg, err = ReadMessage(m.msgType, m.reader) + if err != nil { + m.err = fmt.Errorf("read message err: %s", err) + return false + } + m.message = msg + return true +} + +func (m *messageReaderImpl) Message() Message { + return m.message +} diff --git a/backend/pkg/messages/session-iterator.go b/backend/pkg/messages/session-iterator.go new file mode 100644 index 000000000..45daae4b8 --- /dev/null +++ b/backend/pkg/messages/session-iterator.go @@ -0,0 +1,71 @@ +package messages + +import ( + "bytes" + "fmt" + "io" + "log" + "sort" +) + +type msgInfo struct { + index uint64 + start int64 + end int64 +} + +func SplitMessages(data []byte) ([]*msgInfo, error) { + messages := make([]*msgInfo, 0) + reader := NewBytesReader(data) + for { + // Get message start + msgStart := reader.Pointer() + if int(msgStart) >= len(data) { + return messages, nil + } + + // Read message index + msgIndex, err := reader.ReadIndex() + if err != nil { + if err != io.EOF { + log.Println(reader.Pointer(), msgStart) + return nil, fmt.Errorf("read message index err: %s", err) + } + return messages, nil + } + + // Read message type + msgType, err := reader.ReadUint() + if err != nil { + return nil, fmt.Errorf("read message type err: %s", err) + } + + // Read message body + _, err = ReadMessage(msgType, reader) + if err != nil { + return nil, fmt.Errorf("read message body err: %s", err) + } + + // Add new message info to messages slice + messages = append(messages, &msgInfo{ + index: msgIndex, + start: msgStart, + end: reader.Pointer(), + }) + } +} + +func SortMessages(messages []*msgInfo) []*msgInfo { + sort.SliceStable(messages, func(i, j int) bool { + return messages[i].index < messages[j].index + }) + return messages +} + +func MergeMessages(data []byte, messages []*msgInfo) []byte { + sortedSession := bytes.NewBuffer(make([]byte, 0, len(data))) + for _, info := range messages { + sortedSession.Write(data[info.start:info.end]) + } + return sortedSession.Bytes() +} diff --git a/backend/pkg/metrics/assets/metrics.go b/backend/pkg/metrics/assets/metrics.go new file mode 100644 index 000000000..44af0dfa9 --- /dev/null +++ b/backend/pkg/metrics/assets/metrics.go @@ -0,0 +1,72 @@ +package assets + +import ( + "github.com/prometheus/client_golang/prometheus" + "openreplay/backend/pkg/metrics/common" + "strconv" +) + +var assetsProcessedSessions = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: "assets", + Name: "processed_total", + Help: "A counter displaying the total count of processed assets.", + }, +) + +func IncreaseProcessesSessions() { + assetsProcessedSessions.Inc() +} + +var assetsSavedSessions = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: "assets", + Name: "saved_total", + Help: "A counter displaying the total number of cached assets.", + }, +) + +func IncreaseSavedSessions() { + assetsSavedSessions.Inc() +} + +var assetsDownloadDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "assets", + Name: "download_duration_seconds", + Help: "A histogram displaying the duration of downloading for each asset in seconds.", + Buckets: common.DefaultDurationBuckets, + }, + []string{"response_code"}, +) + +func RecordDownloadDuration(durMillis float64, code int) { + assetsDownloadDuration.WithLabelValues(strconv.Itoa(code)).Observe(durMillis / 1000.0) +} + +var assetsUploadDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "assets", + Name: "upload_s3_duration_seconds", + Help: "A histogram displaying the duration of uploading to s3 for each asset in seconds.", + Buckets: common.DefaultDurationBuckets, + }, + []string{"failed"}, +) + +func RecordUploadDuration(durMillis float64, isFailed bool) { + failed := "false" + if isFailed { + failed = "true" + } + assetsUploadDuration.WithLabelValues(failed).Observe(durMillis / 1000.0) +} + +func List() []prometheus.Collector { + return []prometheus.Collector{ + assetsProcessedSessions, + assetsSavedSessions, + assetsDownloadDuration, + assetsUploadDuration, + } +} diff --git a/backend/pkg/metrics/common/metrics.go b/backend/pkg/metrics/common/metrics.go new file mode 100644 index 000000000..85b66c713 --- /dev/null +++ b/backend/pkg/metrics/common/metrics.go @@ -0,0 +1,11 @@ +package common + +// DefaultDurationBuckets is a set of buckets from 5 milliseconds to 1000 seconds (16.6667 minutes) +var DefaultDurationBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, 25, 50, 100, 250, 500, 1000} + +// DefaultSizeBuckets is a set of buckets from 1 byte to 1_000_000_000 bytes (~1 Gb) +var DefaultSizeBuckets = []float64{1, 10, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100_000, 250_000, + 500_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000} + +// DefaultBuckets is a set of buckets from 1 to 1_000_000 elements +var DefaultBuckets = []float64{1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10_000, 50_000, 100_000, 1_000_000} diff --git a/backend/pkg/metrics/database/metrics.go b/backend/pkg/metrics/database/metrics.go new file mode 100644 index 000000000..a9f3990cd --- /dev/null +++ b/backend/pkg/metrics/database/metrics.go @@ -0,0 +1,127 @@ +package database + +import ( + "github.com/prometheus/client_golang/prometheus" + "openreplay/backend/pkg/metrics/common" +) + +var dbBatchSize = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "db", + Name: "batch_size_bytes", + Help: "A histogram displaying the batch size in bytes.", + Buckets: common.DefaultSizeBuckets, + }, +) + +func RecordBatchSize(size float64) { + dbBatchSize.Observe(size) +} + +var dbBatchElements = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "db", + Name: "batch_size_elements", + Help: "A histogram displaying the number of SQL commands in each batch.", + Buckets: common.DefaultBuckets, + }, +) + +func RecordBatchElements(number float64) { + dbBatchElements.Observe(number) +} + +var dbBatchInsertDuration = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "db", + Name: "batch_insert_duration_seconds", + Help: "A histogram displaying the duration of batch inserts in seconds.", + Buckets: common.DefaultDurationBuckets, + }, +) + +func RecordBatchInsertDuration(durMillis float64) { + dbBatchInsertDuration.Observe(durMillis / 1000.0) +} + +var dbBulkSize = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "db", + Name: "bulk_size_bytes", + Help: "A histogram displaying the bulk size in bytes.", + Buckets: common.DefaultSizeBuckets, + }, + []string{"db", "table"}, +) + +func RecordBulkSize(size float64, db, table string) { + dbBulkSize.WithLabelValues(db, table).Observe(size) +} + +var dbBulkElements = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "db", + Name: "bulk_size_elements", + Help: "A histogram displaying the size of data set in each bulk.", + Buckets: common.DefaultBuckets, + }, + []string{"db", "table"}, +) + +func RecordBulkElements(size float64, db, table string) { + dbBulkElements.WithLabelValues(db, table).Observe(size) +} + +var dbBulkInsertDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "db", + Name: "bulk_insert_duration_seconds", + Help: "A histogram displaying the duration of bulk inserts in seconds.", + Buckets: common.DefaultDurationBuckets, + }, + []string{"db", "table"}, +) + +func RecordBulkInsertDuration(durMillis float64, db, table string) { + dbBulkInsertDuration.WithLabelValues(db, table).Observe(durMillis / 1000.0) +} + +var dbRequestDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "db", + Name: "request_duration_seconds", + Help: "A histogram displaying the duration of each sql request in seconds.", + Buckets: common.DefaultDurationBuckets, + }, + []string{"method", "table"}, +) + +func RecordRequestDuration(durMillis float64, method, table string) { + dbRequestDuration.WithLabelValues(method, table).Observe(durMillis / 1000.0) +} + +var dbTotalRequests = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "db", + Name: "requests_total", + Help: "A counter showing the total number of all SQL requests.", + }, + []string{"method", "table"}, +) + +func IncreaseTotalRequests(method, table string) { + dbTotalRequests.WithLabelValues(method, table).Inc() +} + +func List() []prometheus.Collector { + return []prometheus.Collector{ + dbBatchSize, + dbBatchElements, + dbBatchInsertDuration, + dbBulkSize, + dbBulkElements, + dbBulkInsertDuration, + dbRequestDuration, + dbTotalRequests, + } +} diff --git a/backend/pkg/metrics/ender/metrics.go b/backend/pkg/metrics/ender/metrics.go new file mode 100644 index 000000000..5e3308554 --- /dev/null +++ b/backend/pkg/metrics/ender/metrics.go @@ -0,0 +1,51 @@ +package ender + +import "github.com/prometheus/client_golang/prometheus" + +var enderActiveSessions = prometheus.NewGauge( + prometheus.GaugeOpts{ + Namespace: "ender", + Name: "sessions_active", + Help: "A gauge displaying the number of active (live) sessions.", + }, +) + +func IncreaseActiveSessions() { + enderActiveSessions.Inc() +} + +func DecreaseActiveSessions() { + enderActiveSessions.Dec() +} + +var enderClosedSessions = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: "ender", + Name: "sessions_closed", + Help: "A counter displaying the number of closed sessions (sent SessionEnd).", + }, +) + +func IncreaseClosedSessions() { + enderClosedSessions.Inc() +} + +var enderTotalSessions = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: "ender", + Name: "sessions_total", + Help: "A counter displaying the number of all processed sessions.", + }, +) + +func IncreaseTotalSessions() { + enderTotalSessions.Inc() +} + +func List() []prometheus.Collector { + return []prometheus.Collector{ + enderActiveSessions, + enderClosedSessions, + enderTotalSessions, + } +} diff --git a/backend/pkg/metrics/http/metrics.go b/backend/pkg/metrics/http/metrics.go new file mode 100644 index 000000000..7a835d7f6 --- /dev/null +++ b/backend/pkg/metrics/http/metrics.go @@ -0,0 +1,55 @@ +package http + +import ( + "github.com/prometheus/client_golang/prometheus" + "openreplay/backend/pkg/metrics/common" + "strconv" +) + +var httpRequestSize = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "http", + Name: "request_size_bytes", + Help: "A histogram displaying the size of each HTTP request in bytes.", + Buckets: common.DefaultSizeBuckets, + }, + []string{"url", "response_code"}, +) + +func RecordRequestSize(size float64, url string, code int) { + httpRequestSize.WithLabelValues(url, strconv.Itoa(code)).Observe(size) +} + +var httpRequestDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "http", + Name: "request_duration_seconds", + Help: "A histogram displaying the duration of each HTTP request in seconds.", + Buckets: common.DefaultDurationBuckets, + }, + []string{"url", "response_code"}, +) + +func RecordRequestDuration(durMillis float64, url string, code int) { + httpRequestDuration.WithLabelValues(url, strconv.Itoa(code)).Observe(durMillis / 1000.0) +} + +var httpTotalRequests = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: "http", + Name: "requests_total", + Help: "A counter displaying the number all HTTP requests.", + }, +) + +func IncreaseTotalRequests() { + httpTotalRequests.Inc() +} + +func List() []prometheus.Collector { + return []prometheus.Collector{ + httpRequestSize, + httpRequestDuration, + httpTotalRequests, + } +} diff --git a/backend/pkg/metrics/server.go b/backend/pkg/metrics/server.go new file mode 100644 index 000000000..fb3be5afc --- /dev/null +++ b/backend/pkg/metrics/server.go @@ -0,0 +1,40 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/collectors" + "github.com/prometheus/client_golang/prometheus/promhttp" + "log" + "net/http" +) + +type MetricServer struct { + registry *prometheus.Registry +} + +func New() *MetricServer { + registry := prometheus.NewRegistry() + // Add go runtime metrics and process collectors. + registry.MustRegister( + collectors.NewGoCollector(), + collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), + ) + // Expose /metrics HTTP endpoint using the created custom registry. + http.Handle( + "/metrics", promhttp.HandlerFor( + registry, + promhttp.HandlerOpts{ + EnableOpenMetrics: true, + }), + ) + go func() { + log.Println(http.ListenAndServe(":8888", nil)) + }() + return &MetricServer{ + registry: registry, + } +} + +func (s *MetricServer) Register(cs []prometheus.Collector) { + s.registry.MustRegister(cs...) +} diff --git a/backend/pkg/metrics/sink/metrics.go b/backend/pkg/metrics/sink/metrics.go new file mode 100644 index 000000000..52cb73ba1 --- /dev/null +++ b/backend/pkg/metrics/sink/metrics.go @@ -0,0 +1,185 @@ +package sink + +import ( + "github.com/prometheus/client_golang/prometheus" + "openreplay/backend/pkg/metrics/common" +) + +var sinkMessageSize = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "sink", + Name: "message_size_bytes", + Help: "A histogram displaying the size of each message in bytes.", + Buckets: common.DefaultSizeBuckets, + }, +) + +func RecordMessageSize(size float64) { + sinkMessageSize.Observe(size) +} + +var sinkWrittenMessages = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: "sink", + Name: "messages_written", + Help: "A counter displaying the total number of all written messages.", + }, +) + +func IncreaseWrittenMessages() { + sinkWrittenMessages.Inc() +} + +var sinkTotalMessages = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: "sink", + Name: "messages_total", + Help: "A counter displaying the total number of all processed messages.", + }, +) + +func IncreaseTotalMessages() { + sinkTotalMessages.Inc() +} + +var sinkBatchSize = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "sink", + Name: "batch_size_bytes", + Help: "A histogram displaying the size of each batch in bytes.", + Buckets: common.DefaultSizeBuckets, + }, +) + +func RecordBatchSize(size float64) { + sinkBatchSize.Observe(size) +} + +var sinkTotalBatches = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: "sink", + Name: "batches_total", + Help: "A counter displaying the total number of all written batches.", + }, +) + +func IncreaseTotalBatches() { + sinkTotalBatches.Inc() +} + +var sinkWrittenBytes = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "sink", + Name: "written_bytes", + Help: "A histogram displaying the size of buffer in bytes written to session file.", + Buckets: common.DefaultSizeBuckets, + }, + []string{"file_type"}, +) + +func RecordWrittenBytes(size float64, fileType string) { + if size == 0 { + return + } + sinkWrittenBytes.WithLabelValues(fileType).Observe(size) + IncreaseTotalWrittenBytes(size, fileType) +} + +var sinkTotalWrittenBytes = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "sink", + Name: "written_bytes_total", + Help: "A counter displaying the total number of bytes written to all session files.", + }, + []string{"file_type"}, +) + +func IncreaseTotalWrittenBytes(size float64, fileType string) { + if size == 0 { + return + } + sinkTotalWrittenBytes.WithLabelValues(fileType).Add(size) +} + +var sinkCachedAssets = prometheus.NewGauge( + prometheus.GaugeOpts{ + Namespace: "sink", + Name: "assets_cached", + Help: "A gauge displaying the current number of cached assets.", + }, +) + +func IncreaseCachedAssets() { + sinkCachedAssets.Inc() +} + +func DecreaseCachedAssets() { + sinkCachedAssets.Dec() +} + +var sinkSkippedAssets = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: "sink", + Name: "assets_skipped", + Help: "A counter displaying the total number of all skipped assets.", + }, +) + +func IncreaseSkippedAssets() { + sinkSkippedAssets.Inc() +} + +var sinkTotalAssets = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: "sink", + Name: "assets_total", + Help: "A counter displaying the total number of all processed assets.", + }, +) + +func IncreaseTotalAssets() { + sinkTotalAssets.Inc() +} + +var sinkAssetSize = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "sink", + Name: "asset_size_bytes", + Help: "A histogram displaying the size of each asset in bytes.", + Buckets: common.DefaultSizeBuckets, + }, +) + +func RecordAssetSize(size float64) { + sinkAssetSize.Observe(size) +} + +var sinkProcessAssetDuration = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "sink", + Name: "asset_process_duration_seconds", + Help: "A histogram displaying the duration of processing for each asset in seconds.", + Buckets: common.DefaultDurationBuckets, + }, +) + +func RecordProcessAssetDuration(durMillis float64) { + sinkProcessAssetDuration.Observe(durMillis / 1000.0) +} + +func List() []prometheus.Collector { + return []prometheus.Collector{ + sinkMessageSize, + sinkWrittenMessages, + sinkTotalMessages, + sinkBatchSize, + sinkTotalBatches, + sinkWrittenBytes, + sinkTotalWrittenBytes, + sinkCachedAssets, + sinkSkippedAssets, + sinkTotalAssets, + sinkAssetSize, + sinkProcessAssetDuration, + } +} diff --git a/backend/pkg/metrics/storage/metrics.go b/backend/pkg/metrics/storage/metrics.go new file mode 100644 index 000000000..26459c90d --- /dev/null +++ b/backend/pkg/metrics/storage/metrics.go @@ -0,0 +1,114 @@ +package storage + +import ( + "github.com/prometheus/client_golang/prometheus" + "openreplay/backend/pkg/metrics/common" +) + +var storageSessionSize = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "storage", + Name: "session_size_bytes", + Help: "A histogram displaying the size of each session file in bytes prior to any manipulation.", + Buckets: common.DefaultSizeBuckets, + }, + []string{"file_type"}, +) + +func RecordSessionSize(fileSize float64, fileType string) { + storageSessionSize.WithLabelValues(fileType).Observe(fileSize) +} + +var storageTotalSessions = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: "storage", + Name: "sessions_total", + Help: "A counter displaying the total number of all processed sessions.", + }, +) + +func IncreaseStorageTotalSessions() { + storageTotalSessions.Inc() +} + +var storageSessionReadDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "storage", + Name: "read_duration_seconds", + Help: "A histogram displaying the duration of reading for each session in seconds.", + Buckets: common.DefaultDurationBuckets, + }, + []string{"file_type"}, +) + +func RecordSessionReadDuration(durMillis float64, fileType string) { + storageSessionReadDuration.WithLabelValues(fileType).Observe(durMillis / 1000.0) +} + +var storageSessionSortDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "storage", + Name: "sort_duration_seconds", + Help: "A histogram displaying the duration of sorting for each session in seconds.", + Buckets: common.DefaultDurationBuckets, + }, + []string{"file_type"}, +) + +func RecordSessionSortDuration(durMillis float64, fileType string) { + storageSessionSortDuration.WithLabelValues(fileType).Observe(durMillis / 1000.0) +} + +var storageSessionEncodeDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "storage", + Name: "encode_duration_seconds", + Help: "A histogram displaying the duration of encoding for each session in seconds.", + Buckets: common.DefaultDurationBuckets, + }, + []string{"file_type"}, +) + +func RecordSessionEncodeDuration(durMillis float64, fileType string) { + storageSessionEncodeDuration.WithLabelValues(fileType).Observe(durMillis / 1000.0) +} + +var storageSessionCompressDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "storage", + Name: "compress_duration_seconds", + Help: "A histogram displaying the duration of compressing for each session in seconds.", + Buckets: common.DefaultDurationBuckets, + }, + []string{"file_type"}, +) + +func RecordSessionCompressDuration(durMillis float64, fileType string) { + storageSessionCompressDuration.WithLabelValues(fileType).Observe(durMillis / 1000.0) +} + +var storageSessionUploadDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "storage", + Name: "upload_duration_seconds", + Help: "A histogram displaying the duration of uploading to s3 for each session in seconds.", + Buckets: common.DefaultDurationBuckets, + }, + []string{"file_type"}, +) + +func RecordSessionUploadDuration(durMillis float64, fileType string) { + storageSessionUploadDuration.WithLabelValues(fileType).Observe(durMillis / 1000.0) +} + +func List() []prometheus.Collector { + return []prometheus.Collector{ + storageSessionSize, + storageTotalSessions, + storageSessionReadDuration, + storageSessionSortDuration, + storageSessionEncodeDuration, + storageSessionCompressDuration, + storageSessionUploadDuration, + } +} diff --git a/backend/pkg/monitoring/metrics.go b/backend/pkg/monitoring/metrics.go deleted file mode 100644 index d3cd807c6..000000000 --- a/backend/pkg/monitoring/metrics.go +++ /dev/null @@ -1,138 +0,0 @@ -package monitoring - -import ( - "fmt" - "log" - "net/http" - - "go.opentelemetry.io/otel/exporters/prometheus" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" - "go.opentelemetry.io/otel/sdk/metric/aggregator/histogram" - controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" - "go.opentelemetry.io/otel/sdk/metric/export/aggregation" - processor "go.opentelemetry.io/otel/sdk/metric/processor/basic" - selector "go.opentelemetry.io/otel/sdk/metric/selector/simple" -) - -// Metrics stores all collected metrics -type Metrics struct { - meter metric.Meter - counters map[string]syncfloat64.Counter - upDownCounters map[string]syncfloat64.UpDownCounter - histograms map[string]syncfloat64.Histogram -} - -func New(name string) *Metrics { - m := &Metrics{ - counters: make(map[string]syncfloat64.Counter), - upDownCounters: make(map[string]syncfloat64.UpDownCounter), - histograms: make(map[string]syncfloat64.Histogram), - } - m.initPrometheusDataExporter() - m.initMetrics(name) - return m -} - -// initPrometheusDataExporter allows to use collected metrics in prometheus -func (m *Metrics) initPrometheusDataExporter() { - config := prometheus.Config{ - DefaultHistogramBoundaries: []float64{1, 2, 5, 10, 20, 50}, - } - c := controller.New( - processor.NewFactory( - selector.NewWithHistogramDistribution( - histogram.WithExplicitBoundaries(config.DefaultHistogramBoundaries), - ), - aggregation.CumulativeTemporalitySelector(), - processor.WithMemory(true), - ), - ) - exporter, err := prometheus.New(config, c) - if err != nil { - log.Panicf("failed to initialize prometheus exporter %v", err) - } - - global.SetMeterProvider(exporter.MeterProvider()) - - http.HandleFunc("/metrics", exporter.ServeHTTP) - go func() { - _ = http.ListenAndServe(":8888", nil) - }() - - fmt.Println("Prometheus server running on :8888") -} - -func (m *Metrics) initMetrics(name string) { - m.meter = global.Meter(name) -} - -/* -Counter is a synchronous instrument that measures additive non-decreasing values, for example, the number of: -- processed requests -- received bytes -- disk reads -*/ - -func (m *Metrics) RegisterCounter(name string) (syncfloat64.Counter, error) { - if _, ok := m.counters[name]; ok { - return nil, fmt.Errorf("counter %s already exists", name) - } - counter, err := m.meter.SyncFloat64().Counter(name) - if err != nil { - return nil, fmt.Errorf("failed to initialize counter: %v", err) - } - m.counters[name] = counter - return counter, nil -} - -func (m *Metrics) GetCounter(name string) syncfloat64.Counter { - return m.counters[name] -} - -/* -UpDownCounter is a synchronous instrument which measures additive values that increase or decrease with time, -for example, the number of: -- active requests -- open connections -- memory in use (megabytes) -*/ - -func (m *Metrics) RegisterUpDownCounter(name string) (syncfloat64.UpDownCounter, error) { - if _, ok := m.upDownCounters[name]; ok { - return nil, fmt.Errorf("upDownCounter %s already exists", name) - } - counter, err := m.meter.SyncFloat64().UpDownCounter(name) - if err != nil { - return nil, fmt.Errorf("failed to initialize upDownCounter: %v", err) - } - m.upDownCounters[name] = counter - return counter, nil -} - -func (m *Metrics) GetUpDownCounter(name string) syncfloat64.UpDownCounter { - return m.upDownCounters[name] -} - -/* -Histogram is a synchronous instrument that produces a histogram from recorded values, for example: -- request latency -- request size -*/ - -func (m *Metrics) RegisterHistogram(name string) (syncfloat64.Histogram, error) { - if _, ok := m.histograms[name]; ok { - return nil, fmt.Errorf("histogram %s already exists", name) - } - hist, err := m.meter.SyncFloat64().Histogram(name) - if err != nil { - return nil, fmt.Errorf("failed to initialize histogram: %v", err) - } - m.histograms[name] = hist - return hist, nil -} - -func (m *Metrics) GetHistogram(name string) syncfloat64.Histogram { - return m.histograms[name] -} diff --git a/backend/pkg/sessions/builderMap.go b/backend/pkg/sessions/builderMap.go index bdf8e8686..85e787929 100644 --- a/backend/pkg/sessions/builderMap.go +++ b/backend/pkg/sessions/builderMap.go @@ -1,6 +1,7 @@ package sessions import ( + "log" "openreplay/backend/pkg/handlers" "time" @@ -37,6 +38,21 @@ func (m *builderMap) HandleMessage(msg Message) { b.handleMessage(msg, messageID) } +func (m *builderMap) ClearOldSessions() { + deleted := 0 + now := time.Now() + for id, sess := range m.sessions { + if sess.lastSystemTime.Add(FORCE_DELETE_TIMEOUT).Before(now) { + // Should delete zombie session + delete(m.sessions, id) + deleted++ + } + } + if deleted > 0 { + log.Printf("deleted %d sessions from message builder", deleted) + } +} + func (m *builderMap) iterateSessionReadyMessages(sessionID uint64, b *builder, iter func(msg Message)) { if b.ended || b.lastSystemTime.Add(FORCE_DELETE_TIMEOUT).Before(time.Now()) { for _, p := range b.processors { diff --git a/backend/pkg/url/assets/url.go b/backend/pkg/url/assets/url.go index 83cdd5ac1..6fdc7cb92 100644 --- a/backend/pkg/url/assets/url.go +++ b/backend/pkg/url/assets/url.go @@ -24,8 +24,9 @@ func ResolveURL(baseurl string, rawurl string) string { if !isRelativeCachable(rawurl) { return rawurl } - base, _ := url.ParseRequestURI(baseurl) // fn Only for base urls - u, _ := url.Parse(rawurl) // TODO: handle errors ? + baseurl = strings.Split(baseurl, "#")[0] // remove #fragment suffix if present + base, _ := url.ParseRequestURI(baseurl) // fn Only for base urls + u, _ := url.Parse(rawurl) // TODO: handle errors ? if base == nil || u == nil { return rawurl } @@ -48,6 +49,7 @@ func isCachable(rawurl string) bool { } ext := filepath.Ext(u.Path) return ext == ".css" || + ext == ".ashx" || // ASP .NET ext == ".woff" || ext == ".woff2" || ext == ".ttf" || diff --git a/ee/api/.gitignore b/ee/api/.gitignore index 7fd0b58d0..79aec2ade 100644 --- a/ee/api/.gitignore +++ b/ee/api/.gitignore @@ -176,6 +176,7 @@ chalicelib/.config chalicelib/saas README/* Pipfile +Pipfile.lock .local/* @@ -183,13 +184,17 @@ Pipfile #exp /chalicelib/core/alerts_processor.py /chalicelib/core/announcements.py /chalicelib/core/autocomplete.py +/chalicelib/core/click_maps.py +/chalicelib/core/collaboration_base.py +/chalicelib/core/collaboration_msteams.py /chalicelib/core/collaboration_slack.py /chalicelib/core/countries.py +/chalicelib/core/dashboards.py #exp /chalicelib/core/errors.py /chalicelib/core/errors_favorite.py #exp /chalicelib/core/events.py /chalicelib/core/events_ios.py -#exp /chalicelib/core/funnels.py +/chalicelib/core/funnels.py /chalicelib/core/integration_base.py /chalicelib/core/integration_base_issue.py /chalicelib/core/integration_github.py @@ -214,7 +219,6 @@ Pipfile #exp /chalicelib/core/sessions_metas.py /chalicelib/core/sessions_mobs.py #exp /chalicelib/core/significance.py -/chalicelib/core/slack.py /chalicelib/core/socket_ios.py /chalicelib/core/sourcemaps.py /chalicelib/core/sourcemaps_parser.py @@ -226,6 +230,7 @@ Pipfile /chalicelib/utils/dev.py /chalicelib/utils/email_handler.py /chalicelib/utils/email_helper.py +/chalicelib/utils/errors_helper.py /chalicelib/utils/event_filter_definition.py /chalicelib/utils/github_client_v3.py /chalicelib/utils/helper.py @@ -234,6 +239,7 @@ Pipfile /chalicelib/utils/pg_client.py /chalicelib/utils/s3.py /chalicelib/utils/smtp.py +/chalicelib/utils/sql_helper.py /chalicelib/utils/strings.py /chalicelib/utils/TimeUTC.py /routers/app/__init__.py @@ -257,5 +263,6 @@ Pipfile /chalicelib/core/saved_search.py /app_alerts.py /build_alerts.sh +/build_crons.sh /routers/subs/v1_api.py #exp /chalicelib/core/dashboards.py diff --git a/ee/api/Dockerfile b/ee/api/Dockerfile index 3c041511b..3250baacc 100644 --- a/ee/api/Dockerfile +++ b/ee/api/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10-alpine +FROM python:3.11-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" RUN apk add --no-cache build-base libressl libffi-dev libressl-dev libxslt-dev libxml2-dev xmlsec-dev xmlsec tini diff --git a/ee/api/Dockerfile_alerts b/ee/api/Dockerfile_alerts index 488b68207..8e1eda530 100644 --- a/ee/api/Dockerfile_alerts +++ b/ee/api/Dockerfile_alerts @@ -1,4 +1,4 @@ -FROM python:3.10-alpine +FROM python:3.11-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" RUN apk add --no-cache build-base tini diff --git a/ee/api/Dockerfile_crons b/ee/api/Dockerfile_crons index 40763542c..686516b11 100644 --- a/ee/api/Dockerfile_crons +++ b/ee/api/Dockerfile_crons @@ -1,4 +1,4 @@ -FROM python:3.10-alpine +FROM python:3.11-alpine LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" RUN apk add --no-cache build-base tini diff --git a/ee/api/app.py b/ee/api/app.py index 055706792..a1e203005 100644 --- a/ee/api/app.py +++ b/ee/api/app.py @@ -12,10 +12,12 @@ from starlette.responses import StreamingResponse, JSONResponse from chalicelib.core import traces from chalicelib.utils import helper from chalicelib.utils import pg_client +from chalicelib.utils import events_queue from routers import core, core_dynamic, ee, saml from routers.crons import core_crons from routers.crons import core_dynamic_crons -from routers.subs import dashboard, insights, metrics, v1_api_ee +from routers.crons import ee_crons +from routers.subs import insights, metrics, v1_api_ee from routers.subs import v1_api app = FastAPI(root_path="/api", docs_url=config("docs_url", default=""), redoc_url=config("redoc_url", default="")) @@ -62,7 +64,6 @@ app.include_router(ee.app_apikey) app.include_router(saml.public_app) app.include_router(saml.app) app.include_router(saml.app_apikey) -app.include_router(dashboard.app) app.include_router(metrics.app) app.include_router(insights.app) app.include_router(v1_api.app_apikey) @@ -81,9 +82,10 @@ app.queue_system = queue.Queue() async def startup(): logging.info(">>>>> starting up <<<<<") await pg_client.init() + await events_queue.init() app.schedule.start() - for job in core_crons.cron_jobs + core_dynamic_crons.cron_jobs + traces.cron_jobs: + for job in core_crons.cron_jobs + core_dynamic_crons.cron_jobs + traces.cron_jobs + ee_crons.ee_cron_jobs: app.schedule.add_job(id=job["func"].__name__, **job) ap_logger.info(">Scheduled jobs:") @@ -96,6 +98,7 @@ async def shutdown(): logging.info(">>>>> shutting down <<<<<") app.schedule.shutdown(wait=True) await traces.process_traces_queue() + await events_queue.terminate() await pg_client.terminate() @@ -105,4 +108,3 @@ async def stop_server(): await shutdown() import os, signal os.kill(1, signal.SIGTERM) - diff --git a/ee/api/chalicelib/core/__init__.py b/ee/api/chalicelib/core/__init__.py index 41c437c93..62723d0f1 100644 --- a/ee/api/chalicelib/core/__init__.py +++ b/ee/api/chalicelib/core/__init__.py @@ -3,43 +3,44 @@ import logging logging.basicConfig(level=config("LOGLEVEL", default=logging.INFO)) +from . import sessions as sessions_legacy + if config("EXP_SESSIONS_SEARCH", cast=bool, default=False): - print(">>> Using experimental sessions search") - from . import sessions as sessions_legacy + logging.info(">>> Using experimental sessions search") from . import sessions_exp as sessions else: from . import sessions as sessions if config("EXP_AUTOCOMPLETE", cast=bool, default=False): - print(">>> Using experimental autocomplete") + logging.info(">>> Using experimental autocomplete") from . import autocomplete_exp as autocomplete else: from . import autocomplete as autocomplete if config("EXP_ERRORS_SEARCH", cast=bool, default=False): - print(">>> Using experimental error search") + logging.info(">>> Using experimental error search") from . import errors as errors_legacy from . import errors_exp as errors if config("EXP_ERRORS_GET", cast=bool, default=False): - print(">>> Using experimental error get") + logging.info(">>> Using experimental error get") else: from . import errors as errors if config("EXP_METRICS", cast=bool, default=False): - print(">>> Using experimental metrics") + logging.info(">>> Using experimental metrics") from . import metrics_exp as metrics else: from . import metrics as metrics if config("EXP_ALERTS", cast=bool, default=False): - print(">>> Using experimental alerts") + logging.info(">>> Using experimental alerts") from . import alerts_processor_exp as alerts_processor else: from . import alerts_processor as alerts_processor if config("EXP_FUNNELS", cast=bool, default=False): - print(">>> Using experimental funnels") + logging.info(">>> Using experimental funnels") if not config("EXP_SESSIONS_SEARCH", cast=bool, default=False): from . import sessions as sessions_legacy @@ -48,4 +49,4 @@ else: from . import significance as significance if config("EXP_RESOURCES", cast=bool, default=False): - print(">>> Using experimental resources for session-replay") + logging.info(">>> Using experimental resources for session-replay") diff --git a/ee/api/chalicelib/core/alerts_listener.py b/ee/api/chalicelib/core/alerts_listener.py index 6a97daf93..ebd9afa56 100644 --- a/ee/api/chalicelib/core/alerts_listener.py +++ b/ee/api/chalicelib/core/alerts_listener.py @@ -5,7 +5,7 @@ def get_all_alerts(): with pg_client.PostgresClient(long_query=True) as cur: query = """SELECT tenant_id, alert_id, - project_id, + projects.project_id, detection_method, query, options, @@ -13,10 +13,13 @@ def get_all_alerts(): alerts.name, alerts.series_id, filter, - change + change, + COALESCE(metrics.name || '.' || (COALESCE(metric_series.name, 'series ' || index)) || '.count', + query ->> 'left') AS series_name FROM public.alerts - LEFT JOIN metric_series USING (series_id) INNER JOIN projects USING (project_id) + LEFT JOIN metric_series USING (series_id) + LEFT JOIN metrics USING (metric_id) WHERE alerts.deleted_at ISNULL AND alerts.active AND projects.active diff --git a/ee/api/chalicelib/core/alerts_processor.py b/ee/api/chalicelib/core/alerts_processor.py index 326d17ffc..17e4d275f 100644 --- a/ee/api/chalicelib/core/alerts_processor.py +++ b/ee/api/chalicelib/core/alerts_processor.py @@ -54,10 +54,12 @@ LeftToDb = { schemas.AlertColumn.errors__4xx_5xx__count: { "table": "events.resources INNER JOIN public.sessions USING(session_id)", "formula": "COUNT(session_id)", "condition": "status/100!=2"}, - schemas.AlertColumn.errors__4xx__count: {"table": "events.resources INNER JOIN public.sessions USING(session_id)", - "formula": "COUNT(session_id)", "condition": "status/100=4"}, - schemas.AlertColumn.errors__5xx__count: {"table": "events.resources INNER JOIN public.sessions USING(session_id)", - "formula": "COUNT(session_id)", "condition": "status/100=5"}, + schemas.AlertColumn.errors__4xx__count: { + "table": "events.resources INNER JOIN public.sessions USING(session_id)", + "formula": "COUNT(session_id)", "condition": "status/100=4"}, + schemas.AlertColumn.errors__5xx__count: { + "table": "events.resources INNER JOIN public.sessions USING(session_id)", + "formula": "COUNT(session_id)", "condition": "status/100=5"}, schemas.AlertColumn.errors__javascript__impacted_sessions__count: { "table": "events.resources INNER JOIN public.sessions USING(session_id)", "formula": "COUNT(DISTINCT session_id)", "condition": "success= FALSE AND type='script'"}, @@ -100,7 +102,7 @@ def can_check(a) -> bool: a["options"].get("lastNotification") is None or a["options"]["lastNotification"] <= 0 or ((now - a["options"]["lastNotification"]) > a["options"]["renotifyInterval"] * 60 * 1000)) \ - and ((now - a["createdAt"]) % (TimeInterval[repetitionBase] * 60 * 1000)) < 60 * 1000 + and ((now - a["createdAt"]) % (TimeInterval[repetitionBase] * 60 * 1000)) < 60 * 1000 def Build(a): @@ -124,7 +126,7 @@ def Build(a): subQ = f"""SELECT {colDef["formula"]} AS value FROM {colDef["table"]} WHERE project_id = %(project_id)s - {"AND " + colDef["condition"] if colDef.get("condition") is not None else ""}""" + {"AND " + colDef["condition"] if colDef.get("condition") else ""}""" j_s = colDef.get("joinSessions", True) main_table = colDef["table"] is_ss = main_table == "public.sessions" @@ -147,8 +149,7 @@ def Build(a): "startDate": TimeUTC.now() - a["options"]["currentPeriod"] * 60 * 1000, "timestamp_sub2": TimeUTC.now() - 2 * a["options"]["currentPeriod"] * 60 * 1000} else: - sub1 = f"""{subQ} AND timestamp>=%(startDate)s - AND timestamp<=%(now)s + sub1 = f"""{subQ} {"AND timestamp >= %(startDate)s AND timestamp <= %(now)s" if not is_ss else ""} {"AND start_ts >= %(startDate)s AND start_ts <= %(now)s" if j_s else ""}""" params["startDate"] = TimeUTC.now() - a["options"]["currentPeriod"] * 60 * 1000 sub2 = f"""{subQ} {"AND timestamp < %(startDate)s AND timestamp >= %(timestamp_sub2)s" if not is_ss else ""} @@ -211,7 +212,7 @@ def process(): cur = cur.recreate(rollback=True) if len(notifications) > 0: cur.execute( - cur.mogrify(f"""UPDATE public.Alerts + cur.mogrify(f"""UPDATE public.alerts SET options = options||'{{"lastNotification":{TimeUTC.now()}}}'::jsonb WHERE alert_id IN %(ids)s;""", {"ids": tuple([n["alertId"] for n in notifications])})) if len(notifications) > 0: @@ -233,7 +234,7 @@ def generate_notification(alert, result): "alertId": alert["alertId"], "tenantId": alert["tenantId"], "title": alert["name"], - "description": f"has been triggered, {alert['query']['left']} = {left} ({alert['query']['operator']} {right}).", + "description": f"has been triggered, {alert['seriesName']} = {left} ({alert['query']['operator']} {right}).", "buttonText": "Check metrics for more details", "buttonUrl": f"/{alert['projectId']}/metrics", "imageUrl": None, diff --git a/ee/api/chalicelib/core/alerts_processor_exp.py b/ee/api/chalicelib/core/alerts_processor_exp.py index 7a300654c..310c6faa9 100644 --- a/ee/api/chalicelib/core/alerts_processor_exp.py +++ b/ee/api/chalicelib/core/alerts_processor_exp.py @@ -4,9 +4,10 @@ from decouple import config import schemas from chalicelib.core import alerts_listener, alerts_processor -from chalicelib.core import sessions, alerts +from chalicelib.core import alerts from chalicelib.utils import pg_client, ch_client, exp_ch_helper from chalicelib.utils.TimeUTC import TimeUTC +from chalicelib.core import sessions_exp as sessions logging.basicConfig(level=config("LOGLEVEL", default=logging.INFO)) @@ -135,7 +136,7 @@ def Build(a): FROM {colDef["table"](now)} WHERE project_id = %(project_id)s {"AND event_type=%(event_type)s" if params["event_type"] else ""} - {"AND " + colDef["condition"] if colDef.get("condition") is not None else ""}""" + {"AND " + colDef["condition"] if colDef.get("condition") else ""}""" q = f"""SELECT coalesce(value,0) AS value, coalesce(value,0) {a["query"]["operator"]} {a["query"]["right"]} AS valid""" @@ -198,9 +199,14 @@ def process(): if alert["query"]["left"] != "CUSTOM": continue if alerts_processor.can_check(alert): - logging.info(f"Querying alertId:{alert['alertId']} name: {alert['name']}") query, params = Build(alert) - query = ch_cur.format(query, params) + try: + query = ch_cur.format(query, params) + except Exception as e: + logging.error( + f"!!!Error while building alert query for alertId:{alert['alertId']} name: {alert['name']}") + logging.error(e) + continue logging.debug(alert) logging.debug(query) try: diff --git a/ee/api/chalicelib/core/assist_records.py b/ee/api/chalicelib/core/assist_records.py new file mode 100644 index 000000000..18449dc14 --- /dev/null +++ b/ee/api/chalicelib/core/assist_records.py @@ -0,0 +1,138 @@ +import hashlib + +from decouple import config + +import schemas +import schemas_ee +from chalicelib.utils import s3, pg_client, helper, s3_extra +from chalicelib.utils.TimeUTC import TimeUTC + + +def generate_file_key(project_id, key): + return f"{project_id}/{hashlib.md5(key.encode()).hexdigest()}" + + +def presign_record(project_id, data: schemas_ee.AssistRecordPayloadSchema, context: schemas_ee.CurrentContext): + key = generate_file_key(project_id=project_id, key=f"{TimeUTC.now()}-{data.name}") + presigned_url = s3.get_presigned_url_for_upload(bucket=config('ASSIST_RECORDS_BUCKET'), expires_in=1800, key=key) + return {"URL": presigned_url, "key": key} + + +def save_record(project_id, data: schemas_ee.AssistRecordSavePayloadSchema, context: schemas_ee.CurrentContext): + s3_extra.tag_record(file_key=data.key, tag_value=config('RETENTION_L_VALUE', default='vault')) + params = {"user_id": context.user_id, "project_id": project_id, **data.dict()} + with pg_client.PostgresClient() as cur: + query = cur.mogrify( + f"""INSERT INTO assist_records(project_id, user_id, name, file_key, duration, session_id) + VALUES (%(project_id)s, %(user_id)s, %(name)s, %(key)s,%(duration)s, %(session_id)s) + RETURNING record_id, user_id, session_id, created_at, name, duration, + (SELECT name FROM users WHERE users.user_id = %(user_id)s LIMIT 1) AS created_by, file_key;""", + params) + cur.execute(query) + result = helper.dict_to_camel_case(cur.fetchone()) + result["URL"] = s3.client.generate_presigned_url( + 'get_object', + Params={'Bucket': config("ASSIST_RECORDS_BUCKET"), 'Key': result.pop("fileKey")}, + ExpiresIn=config("PRESIGNED_URL_EXPIRATION", cast=int, default=900) + ) + return result + + +def search_records(project_id, data: schemas_ee.AssistRecordSearchPayloadSchema, context: schemas_ee.CurrentContext): + conditions = ["projects.tenant_id=%(tenant_id)s", + "projects.deleted_at ISNULL", + "assist_records.created_at>=%(startDate)s", + "assist_records.created_at<=%(endDate)s", + "assist_records.deleted_at ISNULL"] + params = {"tenant_id": context.tenant_id, "project_id": project_id, + "startDate": data.startDate, "endDate": data.endDate, + "p_start": (data.page - 1) * data.limit, "p_limit": data.limit, + **data.dict()} + if data.user_id is not None: + conditions.append("assist_records.user_id=%(user_id)s") + if data.query is not None and len(data.query) > 0: + conditions.append("(users.name ILIKE %(query)s OR assist_records.name ILIKE %(query)s)") + params["query"] = helper.values_for_operator(value=data.query, + op=schemas.SearchEventOperator._contains) + with pg_client.PostgresClient() as cur: + query = cur.mogrify(f"""SELECT record_id, user_id, session_id, assist_records.created_at, + assist_records.name, duration, users.name AS created_by + FROM assist_records + INNER JOIN projects USING (project_id) + LEFT JOIN users USING (user_id) + WHERE {" AND ".join(conditions)} + ORDER BY assist_records.created_at {data.order} + LIMIT %(p_limit)s OFFSET %(p_start)s;""", + params) + cur.execute(query) + results = helper.list_to_camel_case(cur.fetchall()) + return results + + +def get_record(project_id, record_id, context: schemas_ee.CurrentContext): + conditions = ["projects.tenant_id=%(tenant_id)s", + "projects.deleted_at ISNULL", + "assist_records.record_id=%(record_id)s", + "assist_records.deleted_at ISNULL"] + params = {"tenant_id": context.tenant_id, "project_id": project_id, "record_id": record_id} + with pg_client.PostgresClient() as cur: + query = cur.mogrify(f"""SELECT record_id, user_id, session_id, assist_records.created_at, + assist_records.name, duration, users.name AS created_by, + file_key + FROM assist_records + INNER JOIN projects USING (project_id) + LEFT JOIN users USING (user_id) + WHERE {" AND ".join(conditions)} + LIMIT 1;""", params) + cur.execute(query) + result = helper.dict_to_camel_case(cur.fetchone()) + if result: + result["URL"] = s3.client.generate_presigned_url( + 'get_object', + Params={'Bucket': config("ASSIST_RECORDS_BUCKET"), 'Key': result.pop("fileKey")}, + ExpiresIn=config("PRESIGNED_URL_EXPIRATION", cast=int, default=900) + ) + return result + + +def update_record(project_id, record_id, data: schemas_ee.AssistRecordUpdatePayloadSchema, + context: schemas_ee.CurrentContext): + conditions = ["assist_records.record_id=%(record_id)s", "assist_records.deleted_at ISNULL"] + params = {"tenant_id": context.tenant_id, "project_id": project_id, "record_id": record_id, "name": data.name} + with pg_client.PostgresClient() as cur: + query = cur.mogrify(f"""UPDATE assist_records + SET name= %(name)s + FROM (SELECT users.name AS created_by + FROM assist_records INNER JOIN users USING (user_id) + WHERE record_id = %(record_id)s + AND assist_records.deleted_at ISNULL + LIMIT 1) AS users + WHERE {" AND ".join(conditions)} + RETURNING record_id, user_id, session_id, assist_records.created_at, + assist_records.name, duration, created_by, file_key;""", params) + cur.execute(query) + result = helper.dict_to_camel_case(cur.fetchone()) + if not result: + return {"errors": ["record not found"]} + result["URL"] = s3.client.generate_presigned_url( + 'get_object', + Params={'Bucket': config("ASSIST_RECORDS_BUCKET"), 'Key': result.pop("fileKey")}, + ExpiresIn=config("PRESIGNED_URL_EXPIRATION", cast=int, default=900) + ) + return result + + +def delete_record(project_id, record_id, context: schemas_ee.CurrentContext): + conditions = ["assist_records.record_id=%(record_id)s"] + params = {"tenant_id": context.tenant_id, "project_id": project_id, "record_id": record_id} + with pg_client.PostgresClient() as cur: + query = cur.mogrify(f"""UPDATE assist_records + SET deleted_at= (now() at time zone 'utc') + WHERE {" AND ".join(conditions)} + RETURNING file_key;""", params) + cur.execute(query) + result = helper.dict_to_camel_case(cur.fetchone()) + if not result: + return {"errors": ["record not found"]} + s3_extra.tag_record(file_key=result["fileKey"], tag_value=config('RETENTION_D_VALUE', default='default')) + return {"state": "success"} diff --git a/ee/api/chalicelib/core/authorizers.py b/ee/api/chalicelib/core/authorizers.py index 363745f4b..21a6634bd 100644 --- a/ee/api/chalicelib/core/authorizers.py +++ b/ee/api/chalicelib/core/authorizers.py @@ -38,13 +38,16 @@ def jwt_context(context): } +def get_jwt_exp(iat): + return iat // 1000 + config("JWT_EXPIRATION", cast=int) + TimeUTC.get_utc_offset() // 1000 + + def generate_jwt(id, tenant_id, iat, aud, exp=None): token = jwt.encode( payload={ "userId": id, "tenantId": tenant_id, - "exp": exp + TimeUTC.get_utc_offset() // 1000 if exp is not None \ - else iat // 1000 + config("JWT_EXPIRATION", cast=int) + TimeUTC.get_utc_offset() // 1000, + "exp": exp + TimeUTC.get_utc_offset() // 1000 if exp is not None else get_jwt_exp(iat), "iss": config("JWT_ISSUER"), "iat": iat // 1000, "aud": aud diff --git a/ee/api/chalicelib/core/autocomplete_exp.py b/ee/api/chalicelib/core/autocomplete_exp.py index 9ae59b7a2..03ca85957 100644 --- a/ee/api/chalicelib/core/autocomplete_exp.py +++ b/ee/api/chalicelib/core/autocomplete_exp.py @@ -25,24 +25,24 @@ def __get_autocomplete_table(value, project_id): if e == schemas.FilterType.user_country: c_list = countries.get_country_code_autocomplete(value) if len(c_list) > 0: - sub_queries.append(f"""(SELECT DISTINCT ON(value) type, value + sub_queries.append(f"""(SELECT DISTINCT ON(value) '{e.value}' AS _type, value FROM {TABLE} WHERE project_id = %(project_id)s - AND type= '{e}' + AND type= '{e.value.upper()}' AND value IN %(c_list)s)""") continue - sub_queries.append(f"""(SELECT type, value + sub_queries.append(f"""(SELECT '{e.value}' AS _type, value FROM {TABLE} WHERE project_id = %(project_id)s - AND type= '{e}' + AND type= '{e.value.upper()}' AND value ILIKE %(svalue)s ORDER BY value LIMIT 5)""") if len(value) > 2: - sub_queries.append(f"""(SELECT type, value + sub_queries.append(f"""(SELECT '{e.value}' AS _type, value FROM {TABLE} WHERE project_id = %(project_id)s - AND type= '{e}' + AND type= '{e.value.upper()}' AND value ILIKE %(value)s ORDER BY value LIMIT 5)""") @@ -64,25 +64,28 @@ def __get_autocomplete_table(value, project_id): print(value) print("--------------------") raise err - return results + for r in results: + r["type"] = r.pop("_type") + results = helper.list_to_camel_case(results) + return results def __generic_query(typename, value_length=None): if typename == schemas.FilterType.user_country: return f"""SELECT DISTINCT value, type - FROM {TABLE} - WHERE - project_id = %(project_id)s - AND type='{typename}' - AND value IN %(c_list)s - ORDER BY value""" + FROM {TABLE} + WHERE + project_id = %(project_id)s + AND type='{typename.upper()}' + AND value IN %(value)s + ORDER BY value""" if value_length is None or value_length > 2: return f"""(SELECT DISTINCT value, type FROM {TABLE} WHERE project_id = %(project_id)s - AND type='{typename}' + AND type='{typename.upper()}' AND value ILIKE %(svalue)s ORDER BY value LIMIT 5) @@ -91,7 +94,7 @@ def __generic_query(typename, value_length=None): FROM {TABLE} WHERE project_id = %(project_id)s - AND type='{typename}' + AND type='{typename.upper()}' AND value ILIKE %(value)s ORDER BY value LIMIT 5);""" @@ -99,7 +102,7 @@ def __generic_query(typename, value_length=None): FROM {TABLE} WHERE project_id = %(project_id)s - AND type='{typename}' + AND type='{typename.upper()}' AND value ILIKE %(svalue)s ORDER BY value LIMIT 10;""" @@ -120,18 +123,17 @@ def __generic_autocomplete(event: Event): def __generic_autocomplete_metas(typename): def f(project_id, text): with ch_client.ClickHouseClient() as cur: - c_list = [] + params = {"project_id": project_id, "value": helper.string_to_sql_like(text), + "svalue": helper.string_to_sql_like("^" + text)} + if typename == schemas.FilterType.user_country: - c_list = countries.get_country_code_autocomplete(text) - if len(c_list) == 0: + params["value"] = tuple(countries.get_country_code_autocomplete(text)) + if len(params["value"]) == 0: return [] query = __generic_query(typename, value_length=len(text)) - params = {"project_id": project_id, "value": helper.string_to_sql_like(text), - "svalue": helper.string_to_sql_like("^" + text), "rvalue": text, - "c_list": tuple(c_list)} - results = cur.execute(query=query, params=params) - return results + rows = cur.execute(query=query, params=params) + return rows return f @@ -142,7 +144,7 @@ def __pg_errors_query(source=None, value_length=None): return f"""((SELECT DISTINCT ON(message) message AS value, source, - '{events.event_type.ERROR.ui_type}' AS type + '{events.EventType.ERROR.ui_type}' AS type FROM {MAIN_TABLE} WHERE project_id = %(project_id)s @@ -154,7 +156,7 @@ def __pg_errors_query(source=None, value_length=None): (SELECT DISTINCT ON(name) name AS value, source, - '{events.event_type.ERROR.ui_type}' AS type + '{events.EventType.ERROR.ui_type}' AS type FROM {MAIN_TABLE} WHERE project_id = %(project_id)s @@ -165,7 +167,7 @@ def __pg_errors_query(source=None, value_length=None): (SELECT DISTINCT ON(message) message AS value, source, - '{events.event_type.ERROR.ui_type}' AS type + '{events.EventType.ERROR.ui_type}' AS type FROM {MAIN_TABLE} WHERE project_id = %(project_id)s @@ -176,7 +178,7 @@ def __pg_errors_query(source=None, value_length=None): (SELECT DISTINCT ON(name) name AS value, source, - '{events.event_type.ERROR.ui_type}' AS type + '{events.EventType.ERROR.ui_type}' AS type FROM {MAIN_TABLE} WHERE project_id = %(project_id)s @@ -186,7 +188,7 @@ def __pg_errors_query(source=None, value_length=None): return f"""((SELECT DISTINCT ON(message) message AS value, source, - '{events.event_type.ERROR.ui_type}' AS type + '{events.EventType.ERROR.ui_type}' AS type FROM {MAIN_TABLE} WHERE project_id = %(project_id)s @@ -197,7 +199,7 @@ def __pg_errors_query(source=None, value_length=None): (SELECT DISTINCT ON(name) name AS value, source, - '{events.event_type.ERROR.ui_type}' AS type + '{events.EventType.ERROR.ui_type}' AS type FROM {MAIN_TABLE} WHERE project_id = %(project_id)s @@ -206,7 +208,7 @@ def __pg_errors_query(source=None, value_length=None): LIMIT 5));""" -def __search_pg_errors(project_id, value, key=None, source=None): +def __search_errors(project_id, value, key=None, source=None): with ch_client.ClickHouseClient() as cur: query = cur.format(__pg_errors_query(source, value_length=len(value)), {"project_id": project_id, "value": helper.string_to_sql_like(value), @@ -216,12 +218,12 @@ def __search_pg_errors(project_id, value, key=None, source=None): return helper.list_to_camel_case(results) -def __search_pg_errors_ios(project_id, value, key=None, source=None): +def __search_errors_ios(project_id, value, key=None, source=None): # TODO: define this when ios events are supported in CH return [] -def __search_pg_metadata(project_id, value, key=None, source=None): +def __search_metadata(project_id, value, key=None, source=None): meta_keys = metadata.get(project_id=project_id) meta_keys = {m["key"]: m["index"] for m in meta_keys} if len(meta_keys) == 0 or key is not None and key not in meta_keys.keys(): @@ -234,18 +236,18 @@ def __search_pg_metadata(project_id, value, key=None, source=None): colname = metadata.index_to_colname(meta_keys[k]) if len(value) > 2: sub_from.append(f"""((SELECT DISTINCT ON ({colname}) {colname} AS value, '{k}' AS key - FROM {exp_ch_helper.get_main_sessions_table(0)} + FROM {exp_ch_helper.get_main_sessions_table()} WHERE project_id = %(project_id)s AND {colname} ILIKE %(svalue)s LIMIT 5) UNION DISTINCT (SELECT DISTINCT ON ({colname}) {colname} AS value, '{k}' AS key - FROM {exp_ch_helper.get_main_sessions_table(0)} + FROM {exp_ch_helper.get_main_sessions_table()} WHERE project_id = %(project_id)s AND {colname} ILIKE %(value)s LIMIT 5)) """) else: sub_from.append(f"""(SELECT DISTINCT ON ({colname}) {colname} AS value, '{k}' AS key - FROM {exp_ch_helper.get_main_sessions_table(0)} + FROM {exp_ch_helper.get_main_sessions_table()} WHERE project_id = %(project_id)s AND {colname} ILIKE %(svalue)s LIMIT 5)""") with ch_client.ClickHouseClient() as cur: diff --git a/ee/api/chalicelib/core/custom_metrics.py b/ee/api/chalicelib/core/custom_metrics.py index 3fef819b3..b7502edd9 100644 --- a/ee/api/chalicelib/core/custom_metrics.py +++ b/ee/api/chalicelib/core/custom_metrics.py @@ -1,12 +1,15 @@ import json from typing import Union -import schemas -from chalicelib.core import funnels, issues -from chalicelib.utils import helper, pg_client -from chalicelib.utils.TimeUTC import TimeUTC - from decouple import config +from fastapi import HTTPException +from starlette import status + +import schemas +import schemas_ee +from chalicelib.core import funnels, issues, metrics, click_maps, sessions_insights, sessions_mobs, sessions_favorite +from chalicelib.utils import helper, pg_client, s3_extra, s3 +from chalicelib.utils.TimeUTC import TimeUTC if config("EXP_ERRORS_SEARCH", cast=bool, default=False): print(">>> Using experimental error search") @@ -22,7 +25,7 @@ else: PIE_CHART_GROUP = 5 -def __try_live(project_id, data: schemas.TryCustomMetricsPayloadSchema): +def __try_live(project_id, data: schemas_ee.CreateCardSchema): results = [] for i, s in enumerate(data.series): s.filter.startDate = data.startTimestamp @@ -55,11 +58,11 @@ def __try_live(project_id, data: schemas.TryCustomMetricsPayloadSchema): return results -def __is_funnel_chart(data: schemas.TryCustomMetricsPayloadSchema): +def __is_funnel_chart(data: schemas_ee.CreateCardSchema): return data.metric_type == schemas.MetricType.funnel -def __get_funnel_chart(project_id, data: schemas.TryCustomMetricsPayloadSchema): +def __get_funnel_chart(project_id, data: schemas_ee.CreateCardSchema): if len(data.series) == 0: return { "stages": [], @@ -70,12 +73,12 @@ def __get_funnel_chart(project_id, data: schemas.TryCustomMetricsPayloadSchema): return funnels.get_top_insights_on_the_fly_widget(project_id=project_id, data=data.series[0].filter) -def __is_errors_list(data): +def __is_errors_list(data: schemas_ee.CreateCardSchema): return data.metric_type == schemas.MetricType.table \ - and data.metric_of == schemas.TableMetricOfType.errors + and data.metric_of == schemas.MetricOfTable.errors -def __get_errors_list(project_id, user_id, data): +def __get_errors_list(project_id, user_id, data: schemas_ee.CreateCardSchema): if len(data.series) == 0: return { "total": 0, @@ -88,12 +91,12 @@ def __get_errors_list(project_id, user_id, data): return errors.search(data.series[0].filter, project_id=project_id, user_id=user_id) -def __is_sessions_list(data): +def __is_sessions_list(data: schemas_ee.CreateCardSchema): return data.metric_type == schemas.MetricType.table \ - and data.metric_of == schemas.TableMetricOfType.sessions + and data.metric_of == schemas.MetricOfTable.sessions -def __get_sessions_list(project_id, user_id, data): +def __get_sessions_list(project_id, user_id, data: schemas_ee.CreateCardSchema): if len(data.series) == 0: print("empty series") return { @@ -107,14 +110,54 @@ def __get_sessions_list(project_id, user_id, data): return sessions.search_sessions(data=data.series[0].filter, project_id=project_id, user_id=user_id) -def merged_live(project_id, data: schemas.TryCustomMetricsPayloadSchema, user_id=None): - if __is_funnel_chart(data): +def __is_predefined(data: schemas_ee.CreateCardSchema): + return data.is_template + + +def __is_click_map(data: schemas_ee.CreateCardSchema): + return data.metric_type == schemas.MetricType.click_map + + +def __get_click_map_chart(project_id, user_id, data: schemas_ee.CreateCardSchema, include_mobs: bool = True): + if len(data.series) == 0: + return None + data.series[0].filter.startDate = data.startTimestamp + data.series[0].filter.endDate = data.endTimestamp + return click_maps.search_short_session(project_id=project_id, user_id=user_id, + data=schemas.FlatClickMapSessionsSearch(**data.series[0].filter.dict()), + include_mobs=include_mobs) + + +# EE only +def __is_insights(data: schemas_ee.CreateCardSchema): + return data.metric_type == schemas.MetricType.insights + + +# EE only +def __get_insights_chart(project_id, user_id, data: schemas_ee.CreateCardSchema): + return sessions_insights.fetch_selected(project_id=project_id, + data=schemas_ee.GetInsightsSchema(startTimestamp=data.startTimestamp, + endTimestamp=data.endTimestamp, + metricValue=data.metric_value, + series=data.series)) + + +def merged_live(project_id, data: schemas_ee.CreateCardSchema, user_id=None): + if data.is_template: + return get_predefined_metric(key=data.metric_of, project_id=project_id, data=data.dict()) + elif __is_funnel_chart(data): return __get_funnel_chart(project_id=project_id, data=data) elif __is_errors_list(data): return __get_errors_list(project_id=project_id, user_id=user_id, data=data) elif __is_sessions_list(data): return __get_sessions_list(project_id=project_id, user_id=user_id, data=data) - + elif __is_click_map(data): + return __get_click_map_chart(project_id=project_id, user_id=user_id, data=data) + # EE only + elif __is_insights(data): + return __get_insights_chart(project_id=project_id, user_id=user_id, data=data) + elif len(data.series) == 0: + return [] series_charts = __try_live(project_id=project_id, data=data) if data.view_type == schemas.MetricTimeseriesViewType.progress or data.metric_type == schemas.MetricType.table: return series_charts @@ -126,69 +169,67 @@ def merged_live(project_id, data: schemas.TryCustomMetricsPayloadSchema, user_id return results -def __merge_metric_with_data(metric, data: Union[schemas.CustomMetricChartPayloadSchema, - schemas.CustomMetricSessionsPayloadSchema]) \ - -> Union[schemas.CreateCustomMetricsSchema, None]: +def __merge_metric_with_data(metric: schemas_ee.CreateCardSchema, + data: schemas.CardChartSchema) -> schemas_ee.CreateCardSchema: if data.series is not None and len(data.series) > 0: - metric["series"] = data.series - metric: schemas.CreateCustomMetricsSchema = schemas.CreateCustomMetricsSchema.parse_obj({**data.dict(), **metric}) + metric.series = data.series + metric: schemas_ee.CreateCardSchema = schemas_ee.CreateCardSchema( + **{**data.dict(by_alias=True), **metric.dict(by_alias=True)}) if len(data.filters) > 0 or len(data.events) > 0: for s in metric.series: if len(data.filters) > 0: s.filter.filters += data.filters if len(data.events) > 0: s.filter.events += data.events + metric.limit = data.limit + metric.page = data.page + metric.startTimestamp = data.startTimestamp + metric.endTimestamp = data.endTimestamp return metric -def make_chart(project_id, user_id, metric_id, data: schemas.CustomMetricChartPayloadSchema, metric=None): - if metric is None: - metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) +def make_chart(project_id, user_id, data: schemas.CardChartSchema, metric: schemas_ee.CreateCardSchema): if metric is None: return None - metric: schemas.CreateCustomMetricsSchema = __merge_metric_with_data(metric=metric, data=data) + metric: schemas_ee.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) return merged_live(project_id=project_id, data=metric, user_id=user_id) - # if __is_funnel_chart(metric): - # return __get_funnel_chart(project_id=project_id, data=metric) - # elif __is_errors_list(metric): - # return __get_errors_list(project_id=project_id, user_id=user_id, data=metric) - # - # series_charts = __try_live(project_id=project_id, data=metric) - # if metric.view_type == schemas.MetricTimeseriesViewType.progress or metric.metric_type == schemas.MetricType.table: - # return series_charts - # results = [{}] * len(series_charts[0]) - # for i in range(len(results)): - # for j, series_chart in enumerate(series_charts): - # results[i] = {**results[i], "timestamp": series_chart[i]["timestamp"], - # metric.series[j].name: series_chart[i]["count"]} - # return results -def get_sessions(project_id, user_id, metric_id, data: schemas.CustomMetricSessionsPayloadSchema): - metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) - if metric is None: +def get_sessions(project_id, user_id, metric_id, data: schemas.CardSessionsSchema): + # raw_metric = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False, include_data=True) + raw_metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) + if raw_metric is None: return None - metric: schemas.CreateCustomMetricsSchema = __merge_metric_with_data(metric=metric, data=data) + metric: schemas_ee.CreateCardSchema = schemas_ee.CreateCardSchema(**raw_metric) + metric: schemas_ee.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) if metric is None: return None results = [] + # is_click_map = False + # if __is_click_map(metric) and raw_metric.get("data") is not None: + # is_click_map = True for s in metric.series: s.filter.startDate = data.startTimestamp s.filter.endDate = data.endTimestamp s.filter.limit = data.limit s.filter.page = data.page + # if is_click_map: + # results.append( + # {"seriesId": s.series_id, "seriesName": s.name, "total": 1, "sessions": [raw_metric["data"]]}) + # break results.append({"seriesId": s.series_id, "seriesName": s.name, **sessions.search_sessions(data=s.filter, project_id=project_id, user_id=user_id)}) return results -def get_funnel_issues(project_id, user_id, metric_id, data: schemas.CustomMetricSessionsPayloadSchema): - metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) - if metric is None: +def get_funnel_issues(project_id, user_id, metric_id, data: schemas.CardSessionsSchema): + raw_metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) + if raw_metric is None: return None - metric: schemas.CreateCustomMetricsSchema = __merge_metric_with_data(metric=metric, data=data) + metric: schemas_ee.CreateCardSchema = schemas_ee.CreateCardSchema(**raw_metric) + metric: schemas_ee.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) if metric is None: return None for s in metric.series: @@ -200,11 +241,12 @@ def get_funnel_issues(project_id, user_id, metric_id, data: schemas.CustomMetric **funnels.get_issues_on_the_fly_widget(project_id=project_id, data=s.filter)} -def get_errors_list(project_id, user_id, metric_id, data: schemas.CustomMetricSessionsPayloadSchema): - metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) - if metric is None: +def get_errors_list(project_id, user_id, metric_id, data: schemas.CardSessionsSchema): + raw_metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) + if raw_metric is None: return None - metric: schemas.CreateCustomMetricsSchema = __merge_metric_with_data(metric=metric, data=data) + metric: schemas_ee.CreateCardSchema = schemas_ee.CreateCardSchema(**raw_metric) + metric: schemas_ee.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) if metric is None: return None for s in metric.series: @@ -216,7 +258,7 @@ def get_errors_list(project_id, user_id, metric_id, data: schemas.CustomMetricSe **errors.search(data=s.filter, project_id=project_id, user_id=user_id)} -def try_sessions(project_id, user_id, data: schemas.CustomMetricSessionsPayloadSchema): +def try_sessions(project_id, user_id, data: schemas.CardSessionsSchema): results = [] if data.series is None: return results @@ -225,50 +267,72 @@ def try_sessions(project_id, user_id, data: schemas.CustomMetricSessionsPayloadS s.filter.endDate = data.endTimestamp s.filter.limit = data.limit s.filter.page = data.page + if len(data.filters) > 0: + s.filter.filters += data.filters + if len(data.events) > 0: + s.filter.events += data.events results.append({"seriesId": None, "seriesName": s.name, **sessions.search_sessions(data=s.filter, project_id=project_id, user_id=user_id)}) return results -def create(project_id, user_id, data: schemas.CreateCustomMetricsSchema, dashboard=False): +def create(project_id, user_id, data: schemas_ee.CreateCardSchema, dashboard=False): with pg_client.PostgresClient() as cur: - _data = {} + session_data = None + if __is_click_map(data): + session_data = __get_click_map_chart(project_id=project_id, user_id=user_id, + data=data, include_mobs=False) + if session_data is not None: + # for EE only + keys = sessions_mobs. \ + __get_mob_keys(project_id=project_id, session_id=session_data["sessionId"]) + keys += sessions_mobs. \ + __get_mob_keys_deprecated(session_id=session_data["sessionId"]) # To support old sessions + tag = config('RETENTION_L_VALUE', default='vault') + for k in keys: + try: + s3_extra.tag_session(file_key=k, tag_value=tag) + except Exception as e: + print(f"!!!Error while tagging: {k} to {tag} for clickMap") + print(str(e)) + session_data = json.dumps(session_data) + _data = {"session_data": session_data} for i, s in enumerate(data.series): for k in s.dict().keys(): _data[f"{k}_{i}"] = s.__getattribute__(k) _data[f"index_{i}"] = i _data[f"filter_{i}"] = s.filter.json() series_len = len(data.series) - data.series = None - params = {"user_id": user_id, "project_id": project_id, - "default_config": json.dumps(data.config.dict()), - **data.dict(), **_data} - query = cur.mogrify(f"""\ - WITH m AS (INSERT INTO metrics (project_id, user_id, name, is_public, - view_type, metric_type, metric_of, metric_value, - metric_format, default_config) - VALUES (%(project_id)s, %(user_id)s, %(name)s, %(is_public)s, - %(view_type)s, %(metric_type)s, %(metric_of)s, %(metric_value)s, - %(metric_format)s, %(default_config)s) - RETURNING *) - INSERT - INTO metric_series(metric_id, index, name, filter) - VALUES {",".join([f"((SELECT metric_id FROM m), %(index_{i})s, %(name_{i})s, %(filter_{i})s::jsonb)" - for i in range(series_len)])} - RETURNING metric_id;""", params) + params = {"user_id": user_id, "project_id": project_id, **data.dict(), **_data} + params["default_config"] = json.dumps(data.default_config.dict()) + query = """INSERT INTO metrics (project_id, user_id, name, is_public, + view_type, metric_type, metric_of, metric_value, + metric_format, default_config, thumbnail, data) + VALUES (%(project_id)s, %(user_id)s, %(name)s, %(is_public)s, + %(view_type)s, %(metric_type)s, %(metric_of)s, %(metric_value)s, + %(metric_format)s, %(default_config)s, %(thumbnail)s, %(session_data)s) + RETURNING metric_id""" + if len(data.series) > 0: + query = f"""WITH m AS ({query}) + INSERT INTO metric_series(metric_id, index, name, filter) + VALUES {",".join([f"((SELECT metric_id FROM m), %(index_{i})s, %(name_{i})s, %(filter_{i})s::jsonb)" + for i in range(series_len)])} + RETURNING metric_id;""" - cur.execute( - query - ) + query = cur.mogrify(query, params) + # print("-------") + # print(query) + # print("-------") + cur.execute(query) r = cur.fetchone() if dashboard: return r["metric_id"] - return {"data": get(metric_id=r["metric_id"], project_id=project_id, user_id=user_id)} + return {"data": get_card(metric_id=r["metric_id"], project_id=project_id, user_id=user_id)} -def update(metric_id, user_id, project_id, data: schemas.UpdateCustomMetricsSchema): - metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) +def update(metric_id, user_id, project_id, data: schemas_ee.UpdateCardSchema): + metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) if metric is None: return None series_ids = [r["seriesId"] for r in metric["series"]] @@ -280,7 +344,7 @@ def update(metric_id, user_id, project_id, data: schemas.UpdateCustomMetricsSche "user_id": user_id, "project_id": project_id, "view_type": data.view_type, "metric_type": data.metric_type, "metric_of": data.metric_of, "metric_value": data.metric_value, "metric_format": data.metric_format, - "config": json.dumps(data.config.dict())} + "config": json.dumps(data.default_config.dict()), "thumbnail": data.thumbnail} for i, s in enumerate(data.series): prefix = "u_" if s.index is None: @@ -331,16 +395,33 @@ def update(metric_id, user_id, project_id, data: schemas.UpdateCustomMetricsSche metric_of= %(metric_of)s, metric_value= %(metric_value)s, metric_format= %(metric_format)s, edited_at = timezone('utc'::text, now()), - default_config = %(config)s + default_config = %(config)s, + thumbnail = %(thumbnail)s WHERE metric_id = %(metric_id)s AND project_id = %(project_id)s AND (user_id = %(user_id)s OR is_public) RETURNING metric_id;""", params) cur.execute(query) - return get(metric_id=metric_id, project_id=project_id, user_id=user_id) + return get_card(metric_id=metric_id, project_id=project_id, user_id=user_id) -def get_all(project_id, user_id, include_series=False): +def search_all(project_id, user_id, data: schemas.SearchCardsSchema, include_series=False): + constraints = ["metrics.project_id = %(project_id)s", + "metrics.deleted_at ISNULL"] + params = {"project_id": project_id, "user_id": user_id, + "offset": (data.page - 1) * data.limit, + "limit": data.limit, } + if data.mine_only: + constraints.append("user_id = %(user_id)s") + else: + constraints.append("(user_id = %(user_id)s OR metrics.is_public)") + if data.shared_only: + constraints.append("is_public") + + if data.query is not None and len(data.query) > 0: + constraints.append("(name ILIKE %(query)s OR owner.owner_email ILIKE %(query)s)") + params["query"] = helper.values_for_operator(value=data.query, + op=schemas.SearchEventOperator._contains) with pg_client.PostgresClient() as cur: sub_join = "" if include_series: @@ -349,35 +430,32 @@ def get_all(project_id, user_id, include_series=False): WHERE metric_series.metric_id = metrics.metric_id AND metric_series.deleted_at ISNULL ) AS metric_series ON (TRUE)""" - cur.execute( - cur.mogrify( - f"""SELECT * - FROM metrics - {sub_join} - LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(connected_dashboards.* ORDER BY is_public,name),'[]'::jsonb) AS dashboards - FROM (SELECT DISTINCT dashboard_id, name, is_public - FROM dashboards INNER JOIN dashboard_widgets USING (dashboard_id) - WHERE deleted_at ISNULL - AND dashboard_widgets.metric_id = metrics.metric_id - AND project_id = %(project_id)s - AND ((dashboards.user_id = %(user_id)s OR is_public))) AS connected_dashboards - ) AS connected_dashboards ON (TRUE) - LEFT JOIN LATERAL (SELECT email AS owner_email - FROM users - WHERE deleted_at ISNULL - AND users.user_id = metrics.user_id - ) AS owner ON (TRUE) - WHERE metrics.project_id = %(project_id)s - AND metrics.deleted_at ISNULL - AND (user_id = %(user_id)s OR metrics.is_public) - ORDER BY metrics.edited_at DESC, metrics.created_at DESC;""", - {"project_id": project_id, "user_id": user_id} - ) - ) + query = cur.mogrify( + f"""SELECT metric_id, project_id, user_id, name, is_public, created_at, edited_at, + metric_type, metric_of, metric_format, metric_value, view_type, is_pinned, + dashboards, owner_email, default_config AS config, thumbnail + FROM metrics + {sub_join} + LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(connected_dashboards.* ORDER BY is_public,name),'[]'::jsonb) AS dashboards + FROM (SELECT DISTINCT dashboard_id, name, is_public + FROM dashboards INNER JOIN dashboard_widgets USING (dashboard_id) + WHERE deleted_at ISNULL + AND dashboard_widgets.metric_id = metrics.metric_id + AND project_id = %(project_id)s + AND ((dashboards.user_id = %(user_id)s OR is_public))) AS connected_dashboards + ) AS connected_dashboards ON (TRUE) + LEFT JOIN LATERAL (SELECT email AS owner_email + FROM users + WHERE deleted_at ISNULL + AND users.user_id = metrics.user_id + ) AS owner ON (TRUE) + WHERE {" AND ".join(constraints)} + ORDER BY created_at {data.order.value} + LIMIT %(limit)s OFFSET %(offset)s;""", params) + cur.execute(query) rows = cur.fetchall() if include_series: for r in rows: - # r["created_at"] = TimeUTC.datetime_to_timestamp(r["created_at"]) for s in r["series"]: s["filter"] = helper.old_search_payload_to_flat(s["filter"]) else: @@ -388,6 +466,17 @@ def get_all(project_id, user_id, include_series=False): return rows +def get_all(project_id, user_id): + default_search = schemas.SearchCardsSchema() + result = rows = search_all(project_id=project_id, user_id=user_id, data=default_search) + while len(rows) == default_search.limit: + default_search.page += 1 + rows = search_all(project_id=project_id, user_id=user_id, data=default_search) + result += rows + + return result + + def delete(project_id, metric_id, user_id): with pg_client.PostgresClient() as cur: cur.execute( @@ -396,44 +485,62 @@ def delete(project_id, metric_id, user_id): SET deleted_at = timezone('utc'::text, now()), edited_at = timezone('utc'::text, now()) WHERE project_id = %(project_id)s AND metric_id = %(metric_id)s - AND (user_id = %(user_id)s OR is_public);""", + AND (user_id = %(user_id)s OR is_public) + RETURNING data;""", {"metric_id": metric_id, "project_id": project_id, "user_id": user_id}) ) - + # for EE only + row = cur.fetchone() + if row: + if row["data"] and not sessions_favorite.favorite_session_exists(session_id=row["data"]["sessionId"]): + keys = sessions_mobs. \ + __get_mob_keys(project_id=project_id, session_id=row["data"]["sessionId"]) + keys += sessions_mobs. \ + __get_mob_keys_deprecated(session_id=row["data"]["sessionId"]) # To support old sessions + tag = config('RETENTION_D_VALUE', default='default') + for k in keys: + try: + s3_extra.tag_session(file_key=k, tag_value=tag) + except Exception as e: + print(f"!!!Error while tagging: {k} to {tag} for clickMap") + print(str(e)) return {"state": "success"} -def get(metric_id, project_id, user_id, flatten=True): +def get_card(metric_id, project_id, user_id, flatten: bool = True, include_data: bool = False): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - """SELECT *, default_config AS config - FROM metrics - LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(metric_series.* ORDER BY index),'[]'::jsonb) AS series - FROM metric_series - WHERE metric_series.metric_id = metrics.metric_id - AND metric_series.deleted_at ISNULL - ) AS metric_series ON (TRUE) - LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(connected_dashboards.* ORDER BY is_public,name),'[]'::jsonb) AS dashboards - FROM (SELECT dashboard_id, name, is_public - FROM dashboards - WHERE deleted_at ISNULL - AND project_id = %(project_id)s - AND ((user_id = %(user_id)s OR is_public))) AS connected_dashboards - ) AS connected_dashboards ON (TRUE) - LEFT JOIN LATERAL (SELECT email AS owner_email - FROM users - WHERE deleted_at ISNULL - AND users.user_id = metrics.user_id - ) AS owner ON (TRUE) - WHERE metrics.project_id = %(project_id)s - AND metrics.deleted_at ISNULL - AND (metrics.user_id = %(user_id)s OR metrics.is_public) - AND metrics.metric_id = %(metric_id)s - ORDER BY created_at;""", - {"metric_id": metric_id, "project_id": project_id, "user_id": user_id} - ) + query = cur.mogrify( + f"""SELECT metric_id, project_id, user_id, name, is_public, created_at, deleted_at, edited_at, metric_type, + view_type, metric_of, metric_value, metric_format, is_pinned, default_config, + default_config AS config,series, dashboards, owner_email + {',data' if include_data else ''} + FROM metrics + LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(metric_series.* ORDER BY index),'[]'::jsonb) AS series + FROM metric_series + WHERE metric_series.metric_id = metrics.metric_id + AND metric_series.deleted_at ISNULL + ) AS metric_series ON (TRUE) + LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(connected_dashboards.* ORDER BY is_public,name),'[]'::jsonb) AS dashboards + FROM (SELECT dashboard_id, name, is_public + FROM dashboards INNER JOIN dashboard_widgets USING (dashboard_id) + WHERE deleted_at ISNULL + AND project_id = %(project_id)s + AND ((dashboards.user_id = %(user_id)s OR is_public)) + AND metric_id = %(metric_id)s) AS connected_dashboards + ) AS connected_dashboards ON (TRUE) + LEFT JOIN LATERAL (SELECT email AS owner_email + FROM users + WHERE deleted_at ISNULL + AND users.user_id = metrics.user_id + ) AS owner ON (TRUE) + WHERE metrics.project_id = %(project_id)s + AND metrics.deleted_at ISNULL + AND (metrics.user_id = %(user_id)s OR metrics.is_public) + AND metrics.metric_id = %(metric_id)s + ORDER BY created_at;""", + {"metric_id": metric_id, "project_id": project_id, "user_id": user_id} ) + cur.execute(query) row = cur.fetchone() if row is None: return None @@ -445,39 +552,6 @@ def get(metric_id, project_id, user_id, flatten=True): return helper.dict_to_camel_case(row) -def get_with_template(metric_id, project_id, user_id, include_dashboard=True): - with pg_client.PostgresClient() as cur: - sub_query = "" - if include_dashboard: - sub_query = """LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(connected_dashboards.* ORDER BY is_public,name),'[]'::jsonb) AS dashboards - FROM (SELECT dashboard_id, name, is_public - FROM dashboards - WHERE deleted_at ISNULL - AND project_id = %(project_id)s - AND ((user_id = %(user_id)s OR is_public))) AS connected_dashboards - ) AS connected_dashboards ON (TRUE)""" - cur.execute( - cur.mogrify( - f"""SELECT *, default_config AS config - FROM metrics - LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(metric_series.* ORDER BY index),'[]'::jsonb) AS series - FROM metric_series - WHERE metric_series.metric_id = metrics.metric_id - AND metric_series.deleted_at ISNULL - ) AS metric_series ON (TRUE) - {sub_query} - WHERE (metrics.project_id = %(project_id)s OR metrics.project_id ISNULL) - AND metrics.deleted_at ISNULL - AND (metrics.user_id = %(user_id)s OR metrics.is_public) - AND metrics.metric_id = %(metric_id)s - ORDER BY created_at;""", - {"metric_id": metric_id, "project_id": project_id, "user_id": user_id} - ) - ) - row = cur.fetchone() - return helper.dict_to_camel_case(row) - - def get_series_for_alert(project_id, user_id): with pg_client.PostgresClient() as cur: cur.execute( @@ -512,17 +586,18 @@ def change_state(project_id, metric_id, user_id, status): AND (user_id = %(user_id)s OR is_public);""", {"metric_id": metric_id, "status": status, "user_id": user_id}) ) - return get(metric_id=metric_id, project_id=project_id, user_id=user_id) + return get_card(metric_id=metric_id, project_id=project_id, user_id=user_id) def get_funnel_sessions_by_issue(user_id, project_id, metric_id, issue_id, - data: schemas.CustomMetricSessionsPayloadSchema + data: schemas.CardSessionsSchema # , range_value=None, start_date=None, end_date=None ): - metric = get(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) + metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, flatten=False) if metric is None: return None - metric: schemas.CreateCustomMetricsSchema = __merge_metric_with_data(metric=metric, data=data) + metric: schemas_ee.CreateCardSchema = schemas.CreateCardSchema(**metric) + metric: schemas_ee.CreateCardSchema = __merge_metric_with_data(metric=metric, data=data) if metric is None: return None for s in metric.series: @@ -551,3 +626,81 @@ def get_funnel_sessions_by_issue(user_id, project_id, metric_id, issue_id, issue=issue, data=s.filter) if issue is not None else {"total": 0, "sessions": []}, "issue": issue} + + +def make_chart_from_card(project_id, user_id, metric_id, data: schemas.CardChartSchema): + raw_metric: dict = get_card(metric_id=metric_id, project_id=project_id, user_id=user_id, include_data=True) + if raw_metric is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="card not found") + metric: schemas_ee.CreateCardSchema = schemas_ee.CreateCardSchema(**raw_metric) + if metric.is_template: + return get_predefined_metric(key=metric.metric_of, project_id=project_id, data=data.dict()) + elif __is_click_map(metric): + if raw_metric["data"]: + keys = sessions_mobs. \ + __get_mob_keys(project_id=project_id, session_id=raw_metric["data"]["sessionId"]) + mob_exists = False + for k in keys: + if s3.exists(bucket=config("sessions_bucket"), key=k): + mob_exists = True + break + if mob_exists: + raw_metric["data"]['domURL'] = sessions_mobs.get_urls(session_id=raw_metric["data"]["sessionId"], + project_id=project_id) + raw_metric["data"]['mobsUrl'] = sessions_mobs.get_urls_depercated( + session_id=raw_metric["data"]["sessionId"]) + return raw_metric["data"] + + return make_chart(project_id=project_id, user_id=user_id, data=data, metric=metric) + + +PREDEFINED = {schemas.MetricOfWebVitals.count_sessions: metrics.get_processed_sessions, + schemas.MetricOfWebVitals.avg_image_load_time: metrics.get_application_activity_avg_image_load_time, + schemas.MetricOfWebVitals.avg_page_load_time: metrics.get_application_activity_avg_page_load_time, + schemas.MetricOfWebVitals.avg_request_load_time: metrics.get_application_activity_avg_request_load_time, + schemas.MetricOfWebVitals.avg_dom_content_load_start: metrics.get_page_metrics_avg_dom_content_load_start, + schemas.MetricOfWebVitals.avg_first_contentful_pixel: metrics.get_page_metrics_avg_first_contentful_pixel, + schemas.MetricOfWebVitals.avg_visited_pages: metrics.get_user_activity_avg_visited_pages, + schemas.MetricOfWebVitals.avg_session_duration: metrics.get_user_activity_avg_session_duration, + schemas.MetricOfWebVitals.avg_pages_dom_buildtime: metrics.get_pages_dom_build_time, + schemas.MetricOfWebVitals.avg_pages_response_time: metrics.get_pages_response_time, + schemas.MetricOfWebVitals.avg_response_time: metrics.get_top_metrics_avg_response_time, + schemas.MetricOfWebVitals.avg_first_paint: metrics.get_top_metrics_avg_first_paint, + schemas.MetricOfWebVitals.avg_dom_content_loaded: metrics.get_top_metrics_avg_dom_content_loaded, + schemas.MetricOfWebVitals.avg_till_first_byte: metrics.get_top_metrics_avg_till_first_bit, + schemas.MetricOfWebVitals.avg_time_to_interactive: metrics.get_top_metrics_avg_time_to_interactive, + schemas.MetricOfWebVitals.count_requests: metrics.get_top_metrics_count_requests, + schemas.MetricOfWebVitals.avg_time_to_render: metrics.get_time_to_render, + schemas.MetricOfWebVitals.avg_used_js_heap_size: metrics.get_memory_consumption, + schemas.MetricOfWebVitals.avg_cpu: metrics.get_avg_cpu, + schemas.MetricOfWebVitals.avg_fps: metrics.get_avg_fps, + schemas.MetricOfErrors.impacted_sessions_by_js_errors: metrics.get_impacted_sessions_by_js_errors, + schemas.MetricOfErrors.domains_errors_4xx: metrics.get_domains_errors_4xx, + schemas.MetricOfErrors.domains_errors_5xx: metrics.get_domains_errors_5xx, + schemas.MetricOfErrors.errors_per_domains: metrics.get_errors_per_domains, + schemas.MetricOfErrors.calls_errors: metrics.get_calls_errors, + schemas.MetricOfErrors.errors_per_type: metrics.get_errors_per_type, + schemas.MetricOfErrors.resources_by_party: metrics.get_resources_by_party, + schemas.MetricOfPerformance.speed_location: metrics.get_speed_index_location, + schemas.MetricOfPerformance.slowest_domains: metrics.get_slowest_domains, + schemas.MetricOfPerformance.sessions_per_browser: metrics.get_sessions_per_browser, + schemas.MetricOfPerformance.time_to_render: metrics.get_time_to_render, + schemas.MetricOfPerformance.impacted_sessions_by_slow_pages: metrics.get_impacted_sessions_by_slow_pages, + schemas.MetricOfPerformance.memory_consumption: metrics.get_memory_consumption, + schemas.MetricOfPerformance.cpu: metrics.get_avg_cpu, + schemas.MetricOfPerformance.fps: metrics.get_avg_fps, + schemas.MetricOfPerformance.crashes: metrics.get_crashes, + schemas.MetricOfPerformance.resources_vs_visually_complete: metrics.get_resources_vs_visually_complete, + schemas.MetricOfPerformance.pages_dom_buildtime: metrics.get_pages_dom_build_time, + schemas.MetricOfPerformance.pages_response_time: metrics.get_pages_response_time, + schemas.MetricOfPerformance.pages_response_time_distribution: metrics.get_pages_response_time_distribution, + schemas.MetricOfResources.missing_resources: metrics.get_missing_resources_trend, + schemas.MetricOfResources.slowest_resources: metrics.get_slowest_resources, + schemas.MetricOfResources.resources_loading_time: metrics.get_resources_loading_time, + schemas.MetricOfResources.resource_type_vs_response_end: metrics.resource_type_vs_response_end, + schemas.MetricOfResources.resources_count_by_type: metrics.get_resources_count_by_type, } + + +def get_predefined_metric(key: Union[schemas.MetricOfWebVitals, schemas.MetricOfErrors, \ + schemas.MetricOfPerformance, schemas.MetricOfResources], project_id: int, data: dict): + return PREDEFINED.get(key, lambda *args: None)(project_id=project_id, **data) diff --git a/ee/api/chalicelib/core/dashboards.py b/ee/api/chalicelib/core/dashboards.py deleted file mode 100644 index 25b1551d3..000000000 --- a/ee/api/chalicelib/core/dashboards.py +++ /dev/null @@ -1,335 +0,0 @@ -import json - -import schemas -from chalicelib.core import custom_metrics -from chalicelib.utils import helper -from chalicelib.utils import pg_client -from chalicelib.utils.TimeUTC import TimeUTC - -from decouple import config - -if config("EXP_METRICS", cast=bool, default=False): - from . import metrics_exp as metrics -else: - from . import metrics as metrics - -# category name should be lower cased -CATEGORY_DESCRIPTION = { - 'web vitals': 'A set of metrics that assess app performance on criteria such as load time, load performance, and stability.', - 'custom': 'Previously created custom metrics by me and my team.', - 'errors': 'Keep a closer eye on errors and track their type, origin and domain.', - 'performance': 'Optimize your app’s performance by tracking slow domains, page response times, memory consumption, CPU usage and more.', - 'resources': 'Find out which resources are missing and those that may be slowing your web app.' -} - - -def get_templates(project_id, user_id): - with pg_client.PostgresClient() as cur: - pg_query = cur.mogrify(f"""SELECT category, jsonb_agg(metrics ORDER BY name) AS widgets - FROM (SELECT * , default_config AS config - FROM metrics LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(metric_series.* ORDER BY index), '[]'::jsonb) AS series - FROM metric_series - WHERE metric_series.metric_id = metrics.metric_id - AND metric_series.deleted_at ISNULL - ) AS metric_series ON (TRUE) - WHERE deleted_at IS NULL - AND (project_id ISNULL OR (project_id = %(project_id)s AND (is_public OR user_id= %(userId)s))) - ) AS metrics - GROUP BY category - ORDER BY ARRAY_POSITION(ARRAY ['custom','overview','errors','performance','resources'], category);""", - {"project_id": project_id, "userId": user_id}) - cur.execute(pg_query) - rows = cur.fetchall() - for r in rows: - r["description"] = CATEGORY_DESCRIPTION.get(r["category"].lower(), "") - for w in r["widgets"]: - w["created_at"] = TimeUTC.datetime_to_timestamp(w["created_at"]) - w["edited_at"] = TimeUTC.datetime_to_timestamp(w["edited_at"]) - for s in w["series"]: - s["filter"] = helper.old_search_payload_to_flat(s["filter"]) - - return helper.list_to_camel_case(rows) - - -def create_dashboard(project_id, user_id, data: schemas.CreateDashboardSchema): - with pg_client.PostgresClient() as cur: - pg_query = f"""INSERT INTO dashboards(project_id, user_id, name, is_public, is_pinned, description) - VALUES(%(projectId)s, %(userId)s, %(name)s, %(is_public)s, %(is_pinned)s, %(description)s) - RETURNING *""" - params = {"userId": user_id, "projectId": project_id, **data.dict()} - if data.metrics is not None and len(data.metrics) > 0: - pg_query = f"""WITH dash AS ({pg_query}) - INSERT INTO dashboard_widgets(dashboard_id, metric_id, user_id, config) - VALUES {",".join([f"((SELECT dashboard_id FROM dash),%(metric_id_{i})s, %(userId)s, (SELECT default_config FROM metrics WHERE metric_id=%(metric_id_{i})s)||%(config_{i})s)" for i in range(len(data.metrics))])} - RETURNING (SELECT dashboard_id FROM dash)""" - for i, m in enumerate(data.metrics): - params[f"metric_id_{i}"] = m - # params[f"config_{i}"] = schemas.AddWidgetToDashboardPayloadSchema.schema() \ - # .get("properties", {}).get("config", {}).get("default", {}) - # params[f"config_{i}"]["position"] = i - # params[f"config_{i}"] = json.dumps(params[f"config_{i}"]) - params[f"config_{i}"] = json.dumps({"position": i}) - cur.execute(cur.mogrify(pg_query, params)) - row = cur.fetchone() - if row is None: - return {"errors": ["something went wrong while creating the dashboard"]} - return {"data": get_dashboard(project_id=project_id, user_id=user_id, dashboard_id=row["dashboard_id"])} - - -def get_dashboards(project_id, user_id): - with pg_client.PostgresClient() as cur: - pg_query = f"""SELECT * - FROM dashboards - WHERE deleted_at ISNULL - AND project_id = %(projectId)s - AND (user_id = %(userId)s OR is_public);""" - params = {"userId": user_id, "projectId": project_id} - cur.execute(cur.mogrify(pg_query, params)) - rows = cur.fetchall() - return helper.list_to_camel_case(rows) - - -def get_dashboard(project_id, user_id, dashboard_id): - with pg_client.PostgresClient() as cur: - pg_query = """SELECT dashboards.*, all_metric_widgets.widgets AS widgets - FROM dashboards - LEFT JOIN LATERAL (SELECT COALESCE(JSONB_AGG(raw_metrics), '[]') AS widgets - FROM (SELECT dashboard_widgets.*, metrics.*, metric_series.series - FROM metrics - INNER JOIN dashboard_widgets USING (metric_id) - LEFT JOIN LATERAL (SELECT COALESCE(JSONB_AGG(metric_series.* ORDER BY index),'[]') AS series - FROM metric_series - WHERE metric_series.metric_id = metrics.metric_id - AND metric_series.deleted_at ISNULL - ) AS metric_series ON (TRUE) - WHERE dashboard_widgets.dashboard_id = dashboards.dashboard_id - AND metrics.deleted_at ISNULL - AND (metrics.project_id = %(projectId)s OR metrics.project_id ISNULL)) AS raw_metrics - ) AS all_metric_widgets ON (TRUE) - WHERE dashboards.deleted_at ISNULL - AND dashboards.project_id = %(projectId)s - AND dashboard_id = %(dashboard_id)s - AND (dashboards.user_id = %(userId)s OR is_public);""" - params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id} - cur.execute(cur.mogrify(pg_query, params)) - row = cur.fetchone() - if row is not None: - row["created_at"] = TimeUTC.datetime_to_timestamp(row["created_at"]) - for w in row["widgets"]: - w["created_at"] = TimeUTC.datetime_to_timestamp(w["created_at"]) - w["edited_at"] = TimeUTC.datetime_to_timestamp(w["edited_at"]) - w["config"]["col"] = w["default_config"]["col"] - w["config"]["row"] = w["default_config"]["row"] - for s in w["series"]: - s["created_at"] = TimeUTC.datetime_to_timestamp(s["created_at"]) - return helper.dict_to_camel_case(row) - - -def delete_dashboard(project_id, user_id, dashboard_id): - with pg_client.PostgresClient() as cur: - pg_query = """UPDATE dashboards - SET deleted_at = timezone('utc'::text, now()) - WHERE dashboards.project_id = %(projectId)s - AND dashboard_id = %(dashboard_id)s - AND (dashboards.user_id = %(userId)s OR is_public);""" - params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id} - cur.execute(cur.mogrify(pg_query, params)) - return {"data": {"success": True}} - - -def update_dashboard(project_id, user_id, dashboard_id, data: schemas.EditDashboardSchema): - with pg_client.PostgresClient() as cur: - pg_query = """SELECT COALESCE(COUNT(*),0) AS count - FROM dashboard_widgets - WHERE dashboard_id = %(dashboard_id)s;""" - params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id, **data.dict()} - cur.execute(cur.mogrify(pg_query, params)) - row = cur.fetchone() - offset = row["count"] - pg_query = f"""UPDATE dashboards - SET name = %(name)s, - description= %(description)s - {", is_public = %(is_public)s" if data.is_public is not None else ""} - {", is_pinned = %(is_pinned)s" if data.is_pinned is not None else ""} - WHERE dashboards.project_id = %(projectId)s - AND dashboard_id = %(dashboard_id)s - AND (dashboards.user_id = %(userId)s OR is_public)""" - if data.metrics is not None and len(data.metrics) > 0: - pg_query = f"""WITH dash AS ({pg_query}) - INSERT INTO dashboard_widgets(dashboard_id, metric_id, user_id, config) - VALUES {",".join([f"(%(dashboard_id)s, %(metric_id_{i})s, %(userId)s, (SELECT default_config FROM metrics WHERE metric_id=%(metric_id_{i})s)||%(config_{i})s)" for i in range(len(data.metrics))])};""" - for i, m in enumerate(data.metrics): - params[f"metric_id_{i}"] = m - # params[f"config_{i}"] = schemas.AddWidgetToDashboardPayloadSchema.schema() \ - # .get("properties", {}).get("config", {}).get("default", {}) - # params[f"config_{i}"]["position"] = i - # params[f"config_{i}"] = json.dumps(params[f"config_{i}"]) - params[f"config_{i}"] = json.dumps({"position": i + offset}) - - cur.execute(cur.mogrify(pg_query, params)) - - return get_dashboard(project_id=project_id, user_id=user_id, dashboard_id=dashboard_id) - - -def get_widget(project_id, user_id, dashboard_id, widget_id): - with pg_client.PostgresClient() as cur: - pg_query = """SELECT metrics.*, metric_series.series - FROM dashboard_widgets - INNER JOIN dashboards USING (dashboard_id) - INNER JOIN metrics USING (metric_id) - LEFT JOIN LATERAL (SELECT COALESCE(jsonb_agg(metric_series.* ORDER BY index), '[]'::jsonb) AS series - FROM metric_series - WHERE metric_series.metric_id = metrics.metric_id - AND metric_series.deleted_at ISNULL - ) AS metric_series ON (TRUE) - WHERE dashboard_id = %(dashboard_id)s - AND widget_id = %(widget_id)s - AND (dashboards.is_public OR dashboards.user_id = %(userId)s) - AND dashboards.deleted_at IS NULL - AND metrics.deleted_at ISNULL - AND (metrics.project_id = %(projectId)s OR metrics.project_id ISNULL) - AND (metrics.is_public OR metrics.user_id = %(userId)s);""" - params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id, "widget_id": widget_id} - cur.execute(cur.mogrify(pg_query, params)) - row = cur.fetchone() - return helper.dict_to_camel_case(row) - - -def add_widget(project_id, user_id, dashboard_id, data: schemas.AddWidgetToDashboardPayloadSchema): - with pg_client.PostgresClient() as cur: - pg_query = """INSERT INTO dashboard_widgets(dashboard_id, metric_id, user_id, config) - SELECT %(dashboard_id)s AS dashboard_id, %(metric_id)s AS metric_id, - %(userId)s AS user_id, (SELECT default_config FROM metrics WHERE metric_id=%(metric_id)s)||%(config)s::jsonb AS config - WHERE EXISTS(SELECT 1 FROM dashboards - WHERE dashboards.deleted_at ISNULL AND dashboards.project_id = %(projectId)s - AND dashboard_id = %(dashboard_id)s - AND (dashboards.user_id = %(userId)s OR is_public)) - RETURNING *;""" - params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id, **data.dict()} - params["config"] = json.dumps(data.config) - cur.execute(cur.mogrify(pg_query, params)) - row = cur.fetchone() - return helper.dict_to_camel_case(row) - - -def update_widget(project_id, user_id, dashboard_id, widget_id, data: schemas.UpdateWidgetPayloadSchema): - with pg_client.PostgresClient() as cur: - pg_query = """UPDATE dashboard_widgets - SET config= %(config)s - WHERE dashboard_id=%(dashboard_id)s AND widget_id=%(widget_id)s - RETURNING *;""" - params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id, - "widget_id": widget_id, **data.dict()} - params["config"] = json.dumps(data.config) - cur.execute(cur.mogrify(pg_query, params)) - row = cur.fetchone() - return helper.dict_to_camel_case(row) - - -def remove_widget(project_id, user_id, dashboard_id, widget_id): - with pg_client.PostgresClient() as cur: - pg_query = """DELETE FROM dashboard_widgets - WHERE dashboard_id=%(dashboard_id)s AND widget_id=%(widget_id)s;""" - params = {"userId": user_id, "projectId": project_id, "dashboard_id": dashboard_id, "widget_id": widget_id} - cur.execute(cur.mogrify(pg_query, params)) - return {"data": {"success": True}} - - -def pin_dashboard(project_id, user_id, dashboard_id): - with pg_client.PostgresClient() as cur: - pg_query = """UPDATE dashboards - SET is_pinned = FALSE - WHERE project_id=%(project_id)s; - UPDATE dashboards - SET is_pinned = True - WHERE dashboard_id=%(dashboard_id)s AND project_id=%(project_id)s AND deleted_at ISNULL - RETURNING *;""" - params = {"userId": user_id, "project_id": project_id, "dashboard_id": dashboard_id} - cur.execute(cur.mogrify(pg_query, params)) - row = cur.fetchone() - return helper.dict_to_camel_case(row) - - -def create_metric_add_widget(project_id, user_id, dashboard_id, data: schemas.CreateCustomMetricsSchema): - metric_id = custom_metrics.create(project_id=project_id, user_id=user_id, data=data, dashboard=True) - return add_widget(project_id=project_id, user_id=user_id, dashboard_id=dashboard_id, - data=schemas.AddWidgetToDashboardPayloadSchema(metricId=metric_id)) - - -PREDEFINED = {schemas.TemplatePredefinedKeys.count_sessions: metrics.get_processed_sessions, - schemas.TemplatePredefinedKeys.avg_image_load_time: metrics.get_application_activity_avg_image_load_time, - schemas.TemplatePredefinedKeys.avg_page_load_time: metrics.get_application_activity_avg_page_load_time, - schemas.TemplatePredefinedKeys.avg_request_load_time: metrics.get_application_activity_avg_request_load_time, - schemas.TemplatePredefinedKeys.avg_dom_content_load_start: metrics.get_page_metrics_avg_dom_content_load_start, - schemas.TemplatePredefinedKeys.avg_first_contentful_pixel: metrics.get_page_metrics_avg_first_contentful_pixel, - schemas.TemplatePredefinedKeys.avg_visited_pages: metrics.get_user_activity_avg_visited_pages, - schemas.TemplatePredefinedKeys.avg_session_duration: metrics.get_user_activity_avg_session_duration, - schemas.TemplatePredefinedKeys.avg_pages_dom_buildtime: metrics.get_pages_dom_build_time, - schemas.TemplatePredefinedKeys.avg_pages_response_time: metrics.get_pages_response_time, - schemas.TemplatePredefinedKeys.avg_response_time: metrics.get_top_metrics_avg_response_time, - schemas.TemplatePredefinedKeys.avg_first_paint: metrics.get_top_metrics_avg_first_paint, - schemas.TemplatePredefinedKeys.avg_dom_content_loaded: metrics.get_top_metrics_avg_dom_content_loaded, - schemas.TemplatePredefinedKeys.avg_till_first_bit: metrics.get_top_metrics_avg_till_first_bit, - schemas.TemplatePredefinedKeys.avg_time_to_interactive: metrics.get_top_metrics_avg_time_to_interactive, - schemas.TemplatePredefinedKeys.count_requests: metrics.get_top_metrics_count_requests, - schemas.TemplatePredefinedKeys.avg_time_to_render: metrics.get_time_to_render, - schemas.TemplatePredefinedKeys.avg_used_js_heap_size: metrics.get_memory_consumption, - schemas.TemplatePredefinedKeys.avg_cpu: metrics.get_avg_cpu, - schemas.TemplatePredefinedKeys.avg_fps: metrics.get_avg_fps, - schemas.TemplatePredefinedKeys.impacted_sessions_by_js_errors: metrics.get_impacted_sessions_by_js_errors, - schemas.TemplatePredefinedKeys.domains_errors_4xx: metrics.get_domains_errors_4xx, - schemas.TemplatePredefinedKeys.domains_errors_5xx: metrics.get_domains_errors_5xx, - schemas.TemplatePredefinedKeys.errors_per_domains: metrics.get_errors_per_domains, - schemas.TemplatePredefinedKeys.calls_errors: metrics.get_calls_errors, - schemas.TemplatePredefinedKeys.errors_by_type: metrics.get_errors_per_type, - schemas.TemplatePredefinedKeys.errors_by_origin: metrics.get_resources_by_party, - schemas.TemplatePredefinedKeys.speed_index_by_location: metrics.get_speed_index_location, - schemas.TemplatePredefinedKeys.slowest_domains: metrics.get_slowest_domains, - schemas.TemplatePredefinedKeys.sessions_per_browser: metrics.get_sessions_per_browser, - schemas.TemplatePredefinedKeys.time_to_render: metrics.get_time_to_render, - schemas.TemplatePredefinedKeys.impacted_sessions_by_slow_pages: metrics.get_impacted_sessions_by_slow_pages, - schemas.TemplatePredefinedKeys.memory_consumption: metrics.get_memory_consumption, - schemas.TemplatePredefinedKeys.cpu_load: metrics.get_avg_cpu, - schemas.TemplatePredefinedKeys.frame_rate: metrics.get_avg_fps, - schemas.TemplatePredefinedKeys.crashes: metrics.get_crashes, - schemas.TemplatePredefinedKeys.resources_vs_visually_complete: metrics.get_resources_vs_visually_complete, - schemas.TemplatePredefinedKeys.pages_dom_buildtime: metrics.get_pages_dom_build_time, - schemas.TemplatePredefinedKeys.pages_response_time: metrics.get_pages_response_time, - schemas.TemplatePredefinedKeys.pages_response_time_distribution: metrics.get_pages_response_time_distribution, - schemas.TemplatePredefinedKeys.missing_resources: metrics.get_missing_resources_trend, - schemas.TemplatePredefinedKeys.slowest_resources: metrics.get_slowest_resources, - schemas.TemplatePredefinedKeys.resources_fetch_time: metrics.get_resources_loading_time, - schemas.TemplatePredefinedKeys.resource_type_vs_response_end: metrics.resource_type_vs_response_end, - schemas.TemplatePredefinedKeys.resources_count_by_type: metrics.get_resources_count_by_type, - } - - -def get_predefined_metric(key: schemas.TemplatePredefinedKeys, project_id: int, data: dict): - return PREDEFINED.get(key, lambda *args: None)(project_id=project_id, **data) - - -def make_chart_metrics(project_id, user_id, metric_id, data: schemas.CustomMetricChartPayloadSchema): - raw_metric = custom_metrics.get_with_template(metric_id=metric_id, project_id=project_id, user_id=user_id, - include_dashboard=False) - if raw_metric is None: - return None - metric: schemas.CustomMetricAndTemplate = schemas.CustomMetricAndTemplate.parse_obj(raw_metric) - if metric.is_template and metric.predefined_key is None: - return None - if metric.is_template: - return get_predefined_metric(key=metric.predefined_key, project_id=project_id, data=data.dict()) - else: - return custom_metrics.make_chart(project_id=project_id, user_id=user_id, metric_id=metric_id, data=data, - metric=raw_metric) - - -def make_chart_widget(dashboard_id, project_id, user_id, widget_id, data: schemas.CustomMetricChartPayloadSchema): - raw_metric = get_widget(widget_id=widget_id, project_id=project_id, user_id=user_id, dashboard_id=dashboard_id) - if raw_metric is None: - return None - metric = schemas.CustomMetricAndTemplate = schemas.CustomMetricAndTemplate.parse_obj(raw_metric) - if metric.is_template: - return get_predefined_metric(key=metric.predefined_key, project_id=project_id, data=data.dict()) - else: - return custom_metrics.make_chart(project_id=project_id, user_id=user_id, metric_id=raw_metric["metricId"], - data=data, metric=raw_metric) diff --git a/ee/api/chalicelib/core/errors.py b/ee/api/chalicelib/core/errors.py index 66c3a195d..c191c69cf 100644 --- a/ee/api/chalicelib/core/errors.py +++ b/ee/api/chalicelib/core/errors.py @@ -1,13 +1,14 @@ import json +from decouple import config + import schemas from chalicelib.core import sourcemaps +from chalicelib.utils import errors_helper from chalicelib.utils import pg_client, helper from chalicelib.utils.TimeUTC import TimeUTC from chalicelib.utils.metrics_helper import __get_step_size -from decouple import config - if config("EXP_SESSIONS_SEARCH", cast=bool, default=False): from chalicelib.core import sessions_legacy as sessions else: @@ -90,13 +91,14 @@ def __process_tags(row): def get_details(project_id, error_id, user_id, **data): pg_sub_query24 = __get_basic_constraints(time_constraint=False, chart=True, step_size_name="step_size24") pg_sub_query24.append("error_id = %(error_id)s") - pg_sub_query30_session = __get_basic_constraints(time_constraint=True, chart=False, startTime_arg_name="startDate30", - endTime_arg_name="endDate30",project_key="sessions.project_id") + pg_sub_query30_session = __get_basic_constraints(time_constraint=True, chart=False, + startTime_arg_name="startDate30", + endTime_arg_name="endDate30", project_key="sessions.project_id") pg_sub_query30_session.append("sessions.start_ts >= %(startDate30)s") pg_sub_query30_session.append("sessions.start_ts <= %(endDate30)s") pg_sub_query30_session.append("error_id = %(error_id)s") pg_sub_query30_err = __get_basic_constraints(time_constraint=True, chart=False, startTime_arg_name="startDate30", - endTime_arg_name="endDate30",project_key="errors.project_id") + endTime_arg_name="endDate30", project_key="errors.project_id") pg_sub_query30_err.append("sessions.project_id = %(project_id)s") pg_sub_query30_err.append("sessions.start_ts >= %(startDate30)s") pg_sub_query30_err.append("sessions.start_ts <= %(endDate30)s") @@ -283,7 +285,7 @@ def get_details(project_id, error_id, user_id, **data): status = cur.fetchone() if status is not None: - row["stack"] = format_first_stack_frame(status).pop("stack") + row["stack"] = errors_helper.format_first_stack_frame(status).pop("stack") row["status"] = status.pop("status") row["parent_error_id"] = status.pop("parent_error_id") row["favorite"] = status.pop("favorite") @@ -475,9 +477,9 @@ def search(data: schemas.SearchErrorsSchema, project_id, user_id): sort = __get_sort_key('datetime') if data.sort is not None: sort = __get_sort_key(data.sort) - order = schemas.SortOrderType.desc + order = schemas.SortOrderType.desc.value if data.order is not None: - order = data.order + order = data.order.value extra_join = "" params = { @@ -727,19 +729,6 @@ def __status_rank(status): }.get(status) -def format_first_stack_frame(error): - error["stack"] = sourcemaps.format_payload(error.pop("payload"), truncate_to_first=True) - for s in error["stack"]: - for c in s.get("context", []): - for sci, sc in enumerate(c): - if isinstance(sc, str) and len(sc) > 1000: - c[sci] = sc[:1000] - # convert bytes to string: - if isinstance(s["filename"], bytes): - s["filename"] = s["filename"].decode("utf-8") - return error - - def stats(project_id, user_id, startTimestamp=TimeUTC.now(delta_days=-7), endTimestamp=TimeUTC.now()): with pg_client.PostgresClient() as cur: query = cur.mogrify( diff --git a/ee/api/chalicelib/core/errors_exp.py b/ee/api/chalicelib/core/errors_exp.py index 1fb201492..eb4331cb6 100644 --- a/ee/api/chalicelib/core/errors_exp.py +++ b/ee/api/chalicelib/core/errors_exp.py @@ -744,7 +744,7 @@ def __get_basic_constraints(platform=None, time_constraint=True, startTime_arg_n else: table_name = "" if type_condition: - ch_sub_query.append(f"{table_name}event_type='ERROR'") + ch_sub_query.append(f"{table_name}EventType='ERROR'") if time_constraint: ch_sub_query += [f"{table_name}datetime >= toDateTime(%({startTime_arg_name})s/1000)", f"{table_name}datetime < toDateTime(%({endTime_arg_name})s/1000)"] @@ -920,7 +920,7 @@ def search(data: schemas.SearchErrorsSchema, project_id, user_id): params["maxDuration"] = f.value[1] elif filter_type == schemas.FilterType.referrer: - # extra_from += f"INNER JOIN {events.event_type.LOCATION.table} AS p USING(session_id)" + # extra_from += f"INNER JOIN {events.EventType.LOCATION.table} AS p USING(session_id)" if is_any: referrer_constraint = 'isNotNull(s.base_referrer)' else: @@ -1062,7 +1062,7 @@ def search(data: schemas.SearchErrorsSchema, project_id, user_id): toUnixTimestamp(MIN(datetime))*1000 AS first_occurrence FROM {MAIN_EVENTS_TABLE} WHERE project_id=%(project_id)s - AND event_type='ERROR' + AND EventType='ERROR' GROUP BY error_id) AS time_details ON details.error_id=time_details.error_id INNER JOIN (SELECT error_id, groupArray([timestamp, count]) AS chart diff --git a/ee/api/chalicelib/core/events.py b/ee/api/chalicelib/core/events.py index f0831864e..3d7e2f661 100644 --- a/ee/api/chalicelib/core/events.py +++ b/ee/api/chalicelib/core/events.py @@ -1,21 +1,20 @@ +from typing import Optional + +from decouple import config + import schemas from chalicelib.core import issues -from chalicelib.core import metadata from chalicelib.core import sessions_metas - from chalicelib.utils import pg_client, helper from chalicelib.utils.TimeUTC import TimeUTC from chalicelib.utils.event_filter_definition import SupportedFilter, Event -from decouple import config - if config("EXP_AUTOCOMPLETE", cast=bool, default=False): from . import autocomplete_exp as autocomplete else: from . import autocomplete as autocomplete - -def get_customs_by_sessionId2_pg(session_id, project_id): +def get_customs_by_session_id(session_id, project_id): with pg_client.PostgresClient() as cur: cur.execute(cur.mogrify("""\ SELECT @@ -58,50 +57,53 @@ def __get_grouped_clickrage(rows, session_id, project_id): return rows -def get_by_sessionId2_pg(session_id, project_id, group_clickrage=False): +def get_by_session_id(session_id, project_id, group_clickrage=False, event_type: Optional[schemas.EventType] = None): with pg_client.PostgresClient() as cur: - cur.execute(cur.mogrify("""\ - SELECT - c.*, - 'CLICK' AS type - FROM events.clicks AS c - WHERE - c.session_id = %(session_id)s - ORDER BY c.timestamp;""", - {"project_id": project_id, "session_id": session_id}) - ) - rows = cur.fetchall() - if group_clickrage: - rows = __get_grouped_clickrage(rows=rows, session_id=session_id, project_id=project_id) - - cur.execute(cur.mogrify(""" - SELECT - i.*, - 'INPUT' AS type - FROM events.inputs AS i - WHERE - i.session_id = %(session_id)s - ORDER BY i.timestamp;""", - {"project_id": project_id, "session_id": session_id}) - ) - rows += cur.fetchall() - cur.execute(cur.mogrify("""\ - SELECT - l.*, - l.path AS value, - l.path AS url, - 'LOCATION' AS type - FROM events.pages AS l - WHERE - l.session_id = %(session_id)s - ORDER BY l.timestamp;""", {"project_id": project_id, "session_id": session_id})) - rows += cur.fetchall() + rows = [] + if event_type is None or event_type == schemas.EventType.click: + cur.execute(cur.mogrify("""\ + SELECT + c.*, + 'CLICK' AS type + FROM events.clicks AS c + WHERE + c.session_id = %(session_id)s + ORDER BY c.timestamp;""", + {"project_id": project_id, "session_id": session_id}) + ) + rows += cur.fetchall() + if group_clickrage: + rows = __get_grouped_clickrage(rows=rows, session_id=session_id, project_id=project_id) + if event_type is None or event_type == schemas.EventType.input: + cur.execute(cur.mogrify(""" + SELECT + i.*, + 'INPUT' AS type + FROM events.inputs AS i + WHERE + i.session_id = %(session_id)s + ORDER BY i.timestamp;""", + {"project_id": project_id, "session_id": session_id}) + ) + rows += cur.fetchall() + if event_type is None or event_type == schemas.EventType.location: + cur.execute(cur.mogrify("""\ + SELECT + l.*, + l.path AS value, + l.path AS url, + 'LOCATION' AS type + FROM events.pages AS l + WHERE + l.session_id = %(session_id)s + ORDER BY l.timestamp;""", {"project_id": project_id, "session_id": session_id})) + rows += cur.fetchall() rows = helper.list_to_camel_case(rows) rows = sorted(rows, key=lambda k: (k["timestamp"], k["messageId"])) return rows -class event_type: +class EventType: CLICK = Event(ui_type=schemas.EventType.click, table="events.clicks", column="label") INPUT = Event(ui_type=schemas.EventType.input, table="events.inputs", column="label") LOCATION = Event(ui_type=schemas.EventType.location, table="events.pages", column="path") @@ -123,46 +125,46 @@ class event_type: SUPPORTED_TYPES = { - event_type.CLICK.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.CLICK), - query=autocomplete.__generic_query(typename=event_type.CLICK.ui_type)), - event_type.INPUT.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.INPUT), - query=autocomplete.__generic_query(typename=event_type.INPUT.ui_type)), - event_type.LOCATION.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.LOCATION), - query=autocomplete.__generic_query( - typename=event_type.LOCATION.ui_type)), - event_type.CUSTOM.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.CUSTOM), - query=autocomplete.__generic_query(typename=event_type.CUSTOM.ui_type)), - event_type.REQUEST.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.REQUEST), + EventType.CLICK.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.CLICK), + query=autocomplete.__generic_query(typename=EventType.CLICK.ui_type)), + EventType.INPUT.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.INPUT), + query=autocomplete.__generic_query(typename=EventType.INPUT.ui_type)), + EventType.LOCATION.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.LOCATION), query=autocomplete.__generic_query( - typename=event_type.REQUEST.ui_type)), - event_type.GRAPHQL.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.GRAPHQL), - query=autocomplete.__generic_query( - typename=event_type.GRAPHQL.ui_type)), - event_type.STATEACTION.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.STATEACTION), - query=autocomplete.__generic_query( - typename=event_type.STATEACTION.ui_type)), - event_type.ERROR.ui_type: SupportedFilter(get=autocomplete.__search_pg_errors, - query=None), - event_type.METADATA.ui_type: SupportedFilter(get=autocomplete.__search_pg_metadata, - query=None), - # IOS - event_type.CLICK_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.CLICK_IOS), - query=autocomplete.__generic_query( - typename=event_type.CLICK_IOS.ui_type)), - event_type.INPUT_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.INPUT_IOS), - query=autocomplete.__generic_query( - typename=event_type.INPUT_IOS.ui_type)), - event_type.VIEW_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.VIEW_IOS), - query=autocomplete.__generic_query( - typename=event_type.VIEW_IOS.ui_type)), - event_type.CUSTOM_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.CUSTOM_IOS), + typename=EventType.LOCATION.ui_type)), + EventType.CUSTOM.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.CUSTOM), + query=autocomplete.__generic_query(typename=EventType.CUSTOM.ui_type)), + EventType.REQUEST.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.REQUEST), + query=autocomplete.__generic_query( + typename=EventType.REQUEST.ui_type)), + EventType.GRAPHQL.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.GRAPHQL), + query=autocomplete.__generic_query( + typename=EventType.GRAPHQL.ui_type)), + EventType.STATEACTION.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.STATEACTION), query=autocomplete.__generic_query( - typename=event_type.CUSTOM_IOS.ui_type)), - event_type.REQUEST_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(event_type.REQUEST_IOS), - query=autocomplete.__generic_query( - typename=event_type.REQUEST_IOS.ui_type)), - event_type.ERROR_IOS.ui_type: SupportedFilter(get=autocomplete.__search_pg_errors_ios, - query=None), + typename=EventType.STATEACTION.ui_type)), + EventType.ERROR.ui_type: SupportedFilter(get=autocomplete.__search_errors, + query=None), + EventType.METADATA.ui_type: SupportedFilter(get=autocomplete.__search_metadata, + query=None), + # IOS + EventType.CLICK_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.CLICK_IOS), + query=autocomplete.__generic_query( + typename=EventType.CLICK_IOS.ui_type)), + EventType.INPUT_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.INPUT_IOS), + query=autocomplete.__generic_query( + typename=EventType.INPUT_IOS.ui_type)), + EventType.VIEW_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.VIEW_IOS), + query=autocomplete.__generic_query( + typename=EventType.VIEW_IOS.ui_type)), + EventType.CUSTOM_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.CUSTOM_IOS), + query=autocomplete.__generic_query( + typename=EventType.CUSTOM_IOS.ui_type)), + EventType.REQUEST_IOS.ui_type: SupportedFilter(get=autocomplete.__generic_autocomplete(EventType.REQUEST_IOS), + query=autocomplete.__generic_query( + typename=EventType.REQUEST_IOS.ui_type)), + EventType.ERROR_IOS.ui_type: SupportedFilter(get=autocomplete.__search_errors_ios, + query=None), } @@ -170,7 +172,7 @@ def get_errors_by_session_id(session_id, project_id): with pg_client.PostgresClient() as cur: cur.execute(cur.mogrify(f"""\ SELECT er.*,ur.*, er.timestamp - s.start_ts AS time - FROM {event_type.ERROR.table} AS er INNER JOIN public.errors AS ur USING (error_id) INNER JOIN public.sessions AS s USING (session_id) + FROM {EventType.ERROR.table} AS er INNER JOIN public.errors AS ur USING (error_id) INNER JOIN public.sessions AS s USING (session_id) WHERE er.session_id = %(session_id)s AND s.project_id=%(project_id)s ORDER BY timestamp;""", {"session_id": session_id, "project_id": project_id})) errors = cur.fetchall() @@ -187,11 +189,9 @@ def search(text, event_type, project_id, source, key): rows = SUPPORTED_TYPES[event_type].get(project_id=project_id, value=text, key=key, source=source) # for IOS events autocomplete # if event_type + "_IOS" in SUPPORTED_TYPES.keys(): - # rows += SUPPORTED_TYPES[event_type + "_IOS"].get(project_id=project_id, value=text, key=key, - # source=source) + # rows += SUPPORTED_TYPES[event_type + "_IOS"].get(project_id=project_id, value=text, key=key,source=source) elif event_type + "_IOS" in SUPPORTED_TYPES.keys(): - rows = SUPPORTED_TYPES[event_type + "_IOS"].get(project_id=project_id, value=text, key=key, - source=source) + rows = SUPPORTED_TYPES[event_type + "_IOS"].get(project_id=project_id, value=text, key=key, source=source) elif event_type in sessions_metas.SUPPORTED_TYPES.keys(): return sessions_metas.search(text, event_type, project_id) elif event_type.endswith("_IOS") \ diff --git a/ee/api/chalicelib/core/funnels.py b/ee/api/chalicelib/core/funnels.py deleted file mode 100644 index 0afce0219..000000000 --- a/ee/api/chalicelib/core/funnels.py +++ /dev/null @@ -1,380 +0,0 @@ -import json -from typing import List - -import chalicelib.utils.helper -import schemas -from chalicelib.core import significance -from chalicelib.utils import dev -from chalicelib.utils import helper, pg_client -from chalicelib.utils.TimeUTC import TimeUTC - -from decouple import config - -if config("EXP_SESSIONS_SEARCH", cast=bool, default=False): - from chalicelib.core import sessions_legacy as sessions -else: - from chalicelib.core import sessions - -REMOVE_KEYS = ["key", "_key", "startDate", "endDate"] - -ALLOW_UPDATE_FOR = ["name", "filter"] - - -def filter_stages(stages: List[schemas._SessionSearchEventSchema]): - ALLOW_TYPES = [schemas.EventType.click, schemas.EventType.input, - schemas.EventType.location, schemas.EventType.custom, - schemas.EventType.click_ios, schemas.EventType.input_ios, - schemas.EventType.view_ios, schemas.EventType.custom_ios, ] - return [s for s in stages if s.type in ALLOW_TYPES and s.value is not None] - - -def __parse_events(f_events: List[dict]): - return [schemas._SessionSearchEventSchema.parse_obj(e) for e in f_events] - - -def __unparse_events(f_events: List[schemas._SessionSearchEventSchema]): - return [e.dict() for e in f_events] - - -def __fix_stages(f_events: List[schemas._SessionSearchEventSchema]): - if f_events is None: - return - events = [] - for e in f_events: - if e.operator is None: - e.operator = schemas.SearchEventOperator._is - - if not isinstance(e.value, list): - e.value = [e.value] - is_any = sessions._isAny_opreator(e.operator) - if not is_any and isinstance(e.value, list) and len(e.value) == 0: - continue - events.append(e) - return events - - -def __transform_old_funnels(events): - for e in events: - if not isinstance(e.get("value"), list): - e["value"] = [e["value"]] - return events - - -def create(project_id, user_id, name, filter: schemas.FunnelSearchPayloadSchema, is_public): - helper.delete_keys_from_dict(filter, REMOVE_KEYS) - filter.events = filter_stages(stages=filter.events) - with pg_client.PostgresClient() as cur: - query = cur.mogrify("""\ - INSERT INTO public.funnels (project_id, user_id, name, filter,is_public) - VALUES (%(project_id)s, %(user_id)s, %(name)s, %(filter)s::jsonb,%(is_public)s) - RETURNING *;""", - {"user_id": user_id, "project_id": project_id, "name": name, - "filter": json.dumps(filter.dict()), - "is_public": is_public}) - - cur.execute( - query - ) - r = cur.fetchone() - r["created_at"] = TimeUTC.datetime_to_timestamp(r["created_at"]) - r = helper.dict_to_camel_case(r) - r["filter"]["startDate"], r["filter"]["endDate"] = TimeUTC.get_start_end_from_range(r["filter"]["rangeValue"]) - return {"data": r} - - -def update(funnel_id, user_id, project_id, name=None, filter=None, is_public=None): - s_query = [] - if filter is not None: - helper.delete_keys_from_dict(filter, REMOVE_KEYS) - s_query.append("filter = %(filter)s::jsonb") - if name is not None and len(name) > 0: - s_query.append("name = %(name)s") - if is_public is not None: - s_query.append("is_public = %(is_public)s") - if len(s_query) == 0: - return {"errors": ["Nothing to update"]} - with pg_client.PostgresClient() as cur: - query = cur.mogrify(f"""\ - UPDATE public.funnels - SET {" , ".join(s_query)} - WHERE funnel_id=%(funnel_id)s - AND project_id = %(project_id)s - AND (user_id = %(user_id)s OR is_public) - RETURNING *;""", {"user_id": user_id, "funnel_id": funnel_id, "name": name, - "filter": json.dumps(filter) if filter is not None else None, "is_public": is_public, - "project_id": project_id}) - # print("--------------------") - # print(query) - # print("--------------------") - cur.execute( - query - ) - r = cur.fetchone() - if r is None: - return {"errors": ["funnel not found"]} - r["created_at"] = TimeUTC.datetime_to_timestamp(r["created_at"]) - r = helper.dict_to_camel_case(r) - r["filter"]["startDate"], r["filter"]["endDate"] = TimeUTC.get_start_end_from_range(r["filter"]["rangeValue"]) - r["filter"] = helper.old_search_payload_to_flat(r["filter"]) - return {"data": r} - - -def get_by_user(project_id, user_id, range_value=None, start_date=None, end_date=None, details=False): - with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - f"""\ - SELECT funnel_id, project_id, user_id, name, created_at, deleted_at, is_public - {",filter" if details else ""} - FROM public.funnels - WHERE project_id = %(project_id)s - AND funnels.deleted_at IS NULL - AND (funnels.user_id = %(user_id)s OR funnels.is_public);""", - {"project_id": project_id, "user_id": user_id} - ) - ) - - rows = cur.fetchall() - rows = helper.list_to_camel_case(rows) - for row in rows: - row["createdAt"] = TimeUTC.datetime_to_timestamp(row["createdAt"]) - if details: - row["filter"]["events"] = filter_stages(__parse_events(row["filter"]["events"])) - if row.get("filter") is not None and row["filter"].get("events") is not None: - row["filter"]["events"] = __transform_old_funnels(__unparse_events(row["filter"]["events"])) - - get_start_end_time(filter_d=row["filter"], range_value=range_value, start_date=start_date, - end_date=end_date) - counts = sessions.search_sessions(data=schemas.SessionsSearchPayloadSchema.parse_obj(row["filter"]), - project_id=project_id, user_id=None, count_only=True) - row["sessionsCount"] = counts["countSessions"] - row["usersCount"] = counts["countUsers"] - filter_clone = dict(row["filter"]) - overview = significance.get_overview(filter_d=row["filter"], project_id=project_id) - row["stages"] = overview["stages"] - row.pop("filter") - row["stagesCount"] = len(row["stages"]) - # TODO: ask david to count it alone - row["criticalIssuesCount"] = overview["criticalIssuesCount"] - row["missedConversions"] = 0 if len(row["stages"]) < 2 \ - else row["stages"][0]["sessionsCount"] - row["stages"][-1]["sessionsCount"] - row["filter"] = helper.old_search_payload_to_flat(filter_clone) - return rows - - -def get_possible_issue_types(project_id): - return [{"type": t, "title": chalicelib.utils.helper.get_issue_title(t)} for t in - ['click_rage', 'dead_click', 'excessive_scrolling', - 'bad_request', 'missing_resource', 'memory', 'cpu', - 'slow_resource', 'slow_page_load', 'crash', 'custom_event_error', - 'js_error']] - - -def get_start_end_time(filter_d, range_value, start_date, end_date): - if start_date is not None and end_date is not None: - filter_d["startDate"], filter_d["endDate"] = start_date, end_date - elif range_value is not None and len(range_value) > 0: - filter_d["rangeValue"] = range_value - filter_d["startDate"], filter_d["endDate"] = TimeUTC.get_start_end_from_range(range_value) - else: - filter_d["startDate"], filter_d["endDate"] = TimeUTC.get_start_end_from_range(filter_d["rangeValue"]) - - -def delete(project_id, funnel_id, user_id): - with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""\ - UPDATE public.funnels - SET deleted_at = timezone('utc'::text, now()) - WHERE project_id = %(project_id)s - AND funnel_id = %(funnel_id)s - AND (user_id = %(user_id)s OR is_public);""", - {"funnel_id": funnel_id, "project_id": project_id, "user_id": user_id}) - ) - - return {"data": {"state": "success"}} - - -def get_sessions(project_id, funnel_id, user_id, range_value=None, start_date=None, end_date=None): - f = get(funnel_id=funnel_id, project_id=project_id, user_id=user_id, flatten=False) - if f is None: - return {"errors": ["funnel not found"]} - get_start_end_time(filter_d=f["filter"], range_value=range_value, start_date=start_date, end_date=end_date) - return sessions.search_sessions(data=schemas.SessionsSearchPayloadSchema.parse_obj(f["filter"]), - project_id=project_id, - user_id=user_id) - - -def get_sessions_on_the_fly(funnel_id, project_id, user_id, data: schemas.FunnelSearchPayloadSchema): - data.events = filter_stages(data.events) - data.events = __fix_stages(data.events) - if len(data.events) == 0: - f = get(funnel_id=funnel_id, project_id=project_id, user_id=user_id, flatten=False) - if f is None: - return {"errors": ["funnel not found"]} - get_start_end_time(filter_d=f["filter"], range_value=data.range_value, - start_date=data.startDate, end_date=data.endDate) - data = schemas.FunnelSearchPayloadSchema.parse_obj(f["filter"]) - return sessions.search_sessions(data=data, project_id=project_id, - user_id=user_id) - - -def get_top_insights(project_id, user_id, funnel_id, range_value=None, start_date=None, end_date=None): - f = get(funnel_id=funnel_id, project_id=project_id, user_id=user_id, flatten=False) - if f is None: - return {"errors": ["funnel not found"]} - get_start_end_time(filter_d=f["filter"], range_value=range_value, start_date=start_date, end_date=end_date) - insights, total_drop_due_to_issues = significance.get_top_insights(filter_d=f["filter"], project_id=project_id) - insights = helper.list_to_camel_case(insights) - if len(insights) > 0: - # fix: this fix for huge drop count - if total_drop_due_to_issues > insights[0]["sessionsCount"]: - total_drop_due_to_issues = insights[0]["sessionsCount"] - # end fix - insights[-1]["dropDueToIssues"] = total_drop_due_to_issues - return {"data": {"stages": insights, - "totalDropDueToIssues": total_drop_due_to_issues}} - - -def get_top_insights_on_the_fly(funnel_id, user_id, project_id, data: schemas.FunnelInsightsPayloadSchema): - data.events = filter_stages(__parse_events(data.events)) - if len(data.events) == 0: - f = get(funnel_id=funnel_id, project_id=project_id, user_id=user_id, flatten=False) - if f is None: - return {"errors": ["funnel not found"]} - get_start_end_time(filter_d=f["filter"], range_value=data.rangeValue, - start_date=data.startDate, - end_date=data.endDate) - data = schemas.FunnelInsightsPayloadSchema.parse_obj(f["filter"]) - data.events = __fix_stages(data.events) - insights, total_drop_due_to_issues = significance.get_top_insights(filter_d=data.dict(), project_id=project_id) - insights = helper.list_to_camel_case(insights) - if len(insights) > 0: - # fix: this fix for huge drop count - if total_drop_due_to_issues > insights[0]["sessionsCount"]: - total_drop_due_to_issues = insights[0]["sessionsCount"] - # end fix - insights[-1]["dropDueToIssues"] = total_drop_due_to_issues - return {"data": {"stages": insights, - "totalDropDueToIssues": total_drop_due_to_issues}} - - -# def get_top_insights_on_the_fly_widget(project_id, data: schemas.FunnelInsightsPayloadSchema): -def get_top_insights_on_the_fly_widget(project_id, data: schemas.CustomMetricSeriesFilterSchema): - data.events = filter_stages(__parse_events(data.events)) - data.events = __fix_stages(data.events) - if len(data.events) == 0: - return {"stages": [], "totalDropDueToIssues": 0} - insights, total_drop_due_to_issues = significance.get_top_insights(filter_d=data.dict(), project_id=project_id) - insights = helper.list_to_camel_case(insights) - if len(insights) > 0: - # TODO: check if this correct - if total_drop_due_to_issues > insights[0]["sessionsCount"]: - if len(insights) == 0: - total_drop_due_to_issues = 0 - else: - total_drop_due_to_issues = insights[0]["sessionsCount"] - insights[-1]["sessionsCount"] - insights[-1]["dropDueToIssues"] = total_drop_due_to_issues - return {"stages": insights, - "totalDropDueToIssues": total_drop_due_to_issues} - - -def get_issues(project_id, user_id, funnel_id, range_value=None, start_date=None, end_date=None): - f = get(funnel_id=funnel_id, project_id=project_id, user_id=user_id, flatten=False) - if f is None: - return {"errors": ["funnel not found"]} - get_start_end_time(filter_d=f["filter"], range_value=range_value, start_date=start_date, end_date=end_date) - return {"data": { - "issues": helper.dict_to_camel_case(significance.get_issues_list(filter_d=f["filter"], project_id=project_id)) - }} - - -def get_issues_on_the_fly(funnel_id, user_id, project_id, data: schemas.FunnelSearchPayloadSchema): - data.events = filter_stages(data.events) - data.events = __fix_stages(data.events) - if len(data.events) == 0: - f = get(funnel_id=funnel_id, project_id=project_id, user_id=user_id, flatten=False) - if f is None: - return {"errors": ["funnel not found"]} - get_start_end_time(filter_d=f["filter"], range_value=data.rangeValue, - start_date=data.startDate, - end_date=data.endDate) - data = schemas.FunnelSearchPayloadSchema.parse_obj(f["filter"]) - if len(data.events) < 2: - return {"issues": []} - return { - "issues": helper.dict_to_camel_case( - significance.get_issues_list(filter_d=data.dict(), project_id=project_id, first_stage=1, - last_stage=len(data.events)))} - - -# def get_issues_on_the_fly_widget(project_id, data: schemas.FunnelSearchPayloadSchema): -def get_issues_on_the_fly_widget(project_id, data: schemas.CustomMetricSeriesFilterSchema): - data.events = filter_stages(data.events) - data.events = __fix_stages(data.events) - if len(data.events) < 0: - return {"issues": []} - - return { - "issues": helper.dict_to_camel_case( - significance.get_issues_list(filter_d=data.dict(), project_id=project_id, first_stage=1, - last_stage=len(data.events)))} - - -def get(funnel_id, project_id, user_id, flatten=True, fix_stages=True): - with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - """\ - SELECT - * - FROM public.funnels - WHERE project_id = %(project_id)s - AND deleted_at IS NULL - AND funnel_id = %(funnel_id)s - AND (user_id = %(user_id)s OR is_public);""", - {"funnel_id": funnel_id, "project_id": project_id, "user_id": user_id} - ) - ) - - f = helper.dict_to_camel_case(cur.fetchone()) - if f is None: - return None - if f.get("filter") is not None and f["filter"].get("events") is not None: - f["filter"]["events"] = __transform_old_funnels(f["filter"]["events"]) - f["createdAt"] = TimeUTC.datetime_to_timestamp(f["createdAt"]) - f["filter"]["events"] = __parse_events(f["filter"]["events"]) - f["filter"]["events"] = filter_stages(stages=f["filter"]["events"]) - if fix_stages: - f["filter"]["events"] = __fix_stages(f["filter"]["events"]) - f["filter"]["events"] = [e.dict() for e in f["filter"]["events"]] - if flatten: - f["filter"] = helper.old_search_payload_to_flat(f["filter"]) - return f - - -def search_by_issue(user_id, project_id, funnel_id, issue_id, data: schemas.FunnelSearchPayloadSchema, range_value=None, - start_date=None, end_date=None): - if len(data.events) == 0: - f = get(funnel_id=funnel_id, project_id=project_id, user_id=user_id, flatten=False) - if f is None: - return {"errors": ["funnel not found"]} - data.startDate = data.startDate if data.startDate is not None else start_date - data.endDate = data.endDate if data.endDate is not None else end_date - get_start_end_time(filter_d=f["filter"], range_value=range_value, start_date=data.startDate, - end_date=data.endDate) - data = schemas.FunnelSearchPayloadSchema.parse_obj(f["filter"]) - - issues = get_issues_on_the_fly(funnel_id=funnel_id, user_id=user_id, project_id=project_id, data=data) \ - .get("issues", {}) - issues = issues.get("significant", []) + issues.get("insignificant", []) - issue = None - for i in issues: - if i.get("issueId", "") == issue_id: - issue = i - break - return {"sessions": sessions.search_sessions(user_id=user_id, project_id=project_id, issue=issue, - data=data) if issue is not None else {"total": 0, "sessions": []}, - # "stages": helper.list_to_camel_case(insights), - # "totalDropDueToIssues": total_drop_due_to_issues, - "issue": issue} diff --git a/ee/api/chalicelib/core/integrations_global.py b/ee/api/chalicelib/core/integrations_global.py index b923fc5ab..e601a94cc 100644 --- a/ee/api/chalicelib/core/integrations_global.py +++ b/ee/api/chalicelib/core/integrations_global.py @@ -9,49 +9,52 @@ def get_global_integrations_status(tenant_id, user_id, project_id): SELECT EXISTS((SELECT 1 FROM public.oauth_authentication WHERE user_id = %(user_id)s - AND provider = 'github')) AS {schemas.IntegrationType.github}, + AND provider = 'github')) AS {schemas.IntegrationType.github.value}, EXISTS((SELECT 1 FROM public.jira_cloud - WHERE user_id = %(user_id)s)) AS {schemas.IntegrationType.jira}, + WHERE user_id = %(user_id)s)) AS {schemas.IntegrationType.jira.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='bugsnag')) AS {schemas.IntegrationType.bugsnag}, + AND provider='bugsnag')) AS {schemas.IntegrationType.bugsnag.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='cloudwatch')) AS {schemas.IntegrationType.cloudwatch}, + AND provider='cloudwatch')) AS {schemas.IntegrationType.cloudwatch.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='datadog')) AS {schemas.IntegrationType.datadog}, + AND provider='datadog')) AS {schemas.IntegrationType.datadog.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='newrelic')) AS {schemas.IntegrationType.newrelic}, + AND provider='newrelic')) AS {schemas.IntegrationType.newrelic.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='rollbar')) AS {schemas.IntegrationType.rollbar}, + AND provider='rollbar')) AS {schemas.IntegrationType.rollbar.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='sentry')) AS {schemas.IntegrationType.sentry}, + AND provider='sentry')) AS {schemas.IntegrationType.sentry.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='stackdriver')) AS {schemas.IntegrationType.stackdriver}, + AND provider='stackdriver')) AS {schemas.IntegrationType.stackdriver.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='sumologic')) AS {schemas.IntegrationType.sumologic}, + AND provider='sumologic')) AS {schemas.IntegrationType.sumologic.value}, EXISTS((SELECT 1 FROM public.integrations WHERE project_id=%(project_id)s - AND provider='elasticsearch')) AS {schemas.IntegrationType.elasticsearch}, + AND provider='elasticsearch')) AS {schemas.IntegrationType.elasticsearch.value}, EXISTS((SELECT 1 FROM public.webhooks - WHERE type='slack' AND tenant_id=%(tenant_id)s)) AS {schemas.IntegrationType.slack};""", + WHERE type='slack' AND tenant_id=%(tenant_id)s AND deleted_at ISNULL)) AS {schemas.IntegrationType.slack.value}, + EXISTS((SELECT 1 + FROM public.webhooks + WHERE type='msteams' AND tenant_id=%(tenant_id)s AND deleted_at ISNULL)) AS {schemas.IntegrationType.ms_teams.value};""", {"user_id": user_id, "tenant_id": tenant_id, "project_id": project_id}) ) current_integrations = cur.fetchone() diff --git a/ee/api/chalicelib/core/projects.py b/ee/api/chalicelib/core/projects.py index 18d71914b..dc06703ce 100644 --- a/ee/api/chalicelib/core/projects.py +++ b/ee/api/chalicelib/core/projects.py @@ -1,4 +1,8 @@ import json +from typing import Optional + +from fastapi import HTTPException +from starlette import status import schemas from chalicelib.core import users @@ -6,6 +10,21 @@ from chalicelib.utils import pg_client, helper from chalicelib.utils.TimeUTC import TimeUTC +def __exists_by_name(tenant_id: int, name: str, exclude_id: Optional[int]) -> bool: + with pg_client.PostgresClient() as cur: + query = cur.mogrify(f"""SELECT EXISTS(SELECT 1 + FROM public.projects + WHERE deleted_at IS NULL + AND name ILIKE %(name)s + AND tenant_id = %(tenant_id)s + {"AND project_id!=%(exclude_id)s" if exclude_id else ""}) AS exists;""", + {"tenant_id": tenant_id, "name": name, "exclude_id": exclude_id}) + + cur.execute(query=query) + row = cur.fetchone() + return row["exists"] + + def __update(tenant_id, project_id, changes): if len(changes.keys()) == 0: return None @@ -14,29 +33,23 @@ def __update(tenant_id, project_id, changes): for key in changes.keys(): sub_query.append(f"{helper.key_to_snake_case(key)} = %({key})s") with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify(f"""\ - UPDATE public.projects - SET - {" ,".join(sub_query)} - WHERE - project_id = %(project_id)s - AND deleted_at ISNULL - RETURNING project_id,name,gdpr;""", - {"project_id": project_id, **changes}) - ) + query = cur.mogrify(f"""UPDATE public.projects + SET {" ,".join(sub_query)} + WHERE project_id = %(project_id)s + AND deleted_at ISNULL + RETURNING project_id,name,gdpr;""", + {"project_id": project_id, **changes}) + cur.execute(query=query) return helper.dict_to_camel_case(cur.fetchone()) def __create(tenant_id, name): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify(f"""\ - INSERT INTO public.projects (tenant_id, name, active) - VALUES (%(tenant_id)s,%(name)s,TRUE) - RETURNING project_id;""", - {"tenant_id": tenant_id, "name": name}) - ) + query = cur.mogrify(f"""INSERT INTO public.projects (tenant_id, name, active) + VALUES (%(tenant_id)s,%(name)s,TRUE) + RETURNING project_id;""", + {"tenant_id": tenant_id, "name": name}) + cur.execute(query=query) project_id = cur.fetchone()["project_id"] return get_project(tenant_id=tenant_id, project_id=project_id, include_gdpr=True) @@ -44,15 +57,14 @@ def __create(tenant_id, name): def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, stack_integrations=False, user_id=None): with pg_client.PostgresClient() as cur: role_query = """INNER JOIN LATERAL (SELECT 1 - FROM users - INNER JOIN roles USING (role_id) - LEFT JOIN roles_projects USING (role_id) - WHERE users.user_id = %(user_id)s - AND users.deleted_at ISNULL - AND users.tenant_id = %(tenant_id)s - AND (roles.all_projects OR roles_projects.project_id = s.project_id) - LIMIT 1 - ) AS role_project ON (TRUE)""" + FROM users + INNER JOIN roles USING (role_id) + LEFT JOIN roles_projects USING (role_id) + WHERE users.user_id = %(user_id)s + AND users.deleted_at ISNULL + AND users.tenant_id = %(tenant_id)s + AND (roles.all_projects OR roles_projects.project_id = s.project_id) + LIMIT 1) AS role_project ON (TRUE)""" extra_projection = "" extra_join = "" if gdpr: @@ -71,13 +83,13 @@ def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, st if stack_integrations: extra_join = """LEFT JOIN LATERAL (SELECT COUNT(*) AS count - FROM public.integrations - WHERE s.project_id = integrations.project_id - LIMIT 1) AS stack_integrations ON TRUE""" + FROM public.integrations + WHERE s.project_id = integrations.project_id + LIMIT 1) AS stack_integrations ON TRUE""" query = cur.mogrify(f"""{"SELECT *, first_recorded IS NOT NULL AS recorded FROM (" if recorded else ""} - SELECT s.project_id, s.name, s.project_key, s.save_request_payloads, s.first_recorded_session_at - {extra_projection} + SELECT s.project_id, s.name, s.project_key, s.save_request_payloads, s.first_recorded_session_at, + created_at {extra_projection} FROM public.projects AS s {extra_join} {role_query if user_id is not None else ""} @@ -92,6 +104,7 @@ def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, st u_values = [] params = {} for i, r in enumerate(rows): + r["created_at"] = TimeUTC.datetime_to_timestamp(r["created_at"]) if r["first_recorded_session_at"] is None: u_values.append(f"(%(project_id_{i})s,to_timestamp(%(first_recorded_{i})s/1000))") params[f"project_id_{i}"] = r["project_id"] @@ -104,7 +117,9 @@ def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, st FROM (VALUES {",".join(u_values)}) AS u(project_id,first_recorded) WHERE projects.project_id=u.project_id;""", params) cur.execute(query) - + else: + for r in rows: + r["created_at"] = TimeUTC.datetime_to_timestamp(r["created_at"]) if recording_state and len(rows) > 0: project_ids = [f'({r["project_id"]})' for r in rows] query = cur.mogrify(f"""SELECT projects.project_id, COALESCE(MAX(start_ts), 0) AS last @@ -131,29 +146,33 @@ def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, st def get_project(tenant_id, project_id, include_last_session=False, include_gdpr=None): with pg_client.PostgresClient() as cur: - query = cur.mogrify(f"""\ - SELECT - s.project_id, - s.project_key, - s.name, - s.save_request_payloads - {",(SELECT max(ss.start_ts) FROM public.sessions AS ss WHERE ss.project_id = %(project_id)s) AS last_recorded_session_at" if include_last_session else ""} - {',s.gdpr' if include_gdpr else ''} - FROM public.projects AS s - where s.tenant_id =%(tenant_id)s - AND s.project_id =%(project_id)s - AND s.deleted_at IS NULL - LIMIT 1;""", + extra_select = "" + if include_last_session: + extra_select += """,(SELECT max(ss.start_ts) + FROM public.sessions AS ss + WHERE ss.project_id = %(project_id)s) AS last_recorded_session_at""" + if include_gdpr: + extra_select += ",s.gdpr" + query = cur.mogrify(f"""SELECT s.project_id, + s.project_key, + s.name, + s.save_request_payloads + {extra_select} + FROM public.projects AS s + WHERE s.tenant_id =%(tenant_id)s + AND s.project_id =%(project_id)s + AND s.deleted_at IS NULL + LIMIT 1;""", {"tenant_id": tenant_id, "project_id": project_id}) - cur.execute( - query=query - ) + cur.execute(query=query) row = cur.fetchone() return helper.dict_to_camel_case(row) def create(tenant_id, user_id, data: schemas.CreateProjectSchema, skip_authorization=False): + if __exists_by_name(name=data.name, exclude_id=None, tenant_id=tenant_id): + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"name already exists.") if not skip_authorization: admin = users.get(user_id=user_id, tenant_id=tenant_id) if not admin["admin"] and not admin["superAdmin"]: @@ -164,6 +183,8 @@ def create(tenant_id, user_id, data: schemas.CreateProjectSchema, skip_authoriza def edit(tenant_id, user_id, project_id, data: schemas.CreateProjectSchema): + if __exists_by_name(name=data.name, exclude_id=project_id, tenant_id=tenant_id): + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"name already exists.") admin = users.get(user_id=user_id, tenant_id=tenant_id) if not admin["admin"] and not admin["superAdmin"]: return {"errors": ["unauthorized"]} @@ -177,95 +198,77 @@ def delete(tenant_id, user_id, project_id): if not admin["admin"] and not admin["superAdmin"]: return {"errors": ["unauthorized"]} with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""UPDATE public.projects - SET - deleted_at = timezone('utc'::text, now()), - active = FALSE - WHERE - project_id = %(project_id)s;""", - {"project_id": project_id}) - ) + query = cur.mogrify("""UPDATE public.projects + SET deleted_at = timezone('utc'::text, now()), + active = FALSE + WHERE project_id = %(project_id)s;""", + {"project_id": project_id}) + cur.execute(query=query) return {"data": {"state": "success"}} -def count_by_tenant(tenant_id): - with pg_client.PostgresClient() as cur: - cur.execute(cur.mogrify("""\ - SELECT - count(s.project_id) - FROM public.projects AS s - WHERE s.deleted_at IS NULL - AND tenant_id= %(tenant_id)s;""", {"tenant_id": tenant_id})) - return cur.fetchone()["count"] - - def get_gdpr(project_id): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""\ - SELECT - gdpr - FROM public.projects AS s - where s.project_id =%(project_id)s - AND s.deleted_at IS NULL;""", - {"project_id": project_id}) - ) - return cur.fetchone()["gdpr"] + query = cur.mogrify("""SELECT gdpr + FROM public.projects AS s + WHERE s.project_id =%(project_id)s + AND s.deleted_at IS NULL;""", + {"project_id": project_id}) + cur.execute(query=query) + row = cur.fetchone()["gdpr"] + row["projectId"] = project_id + return row def edit_gdpr(project_id, gdpr): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""\ - UPDATE public.projects - SET - gdpr = gdpr|| %(gdpr)s - WHERE - project_id = %(project_id)s - AND deleted_at ISNULL - RETURNING gdpr;""", - {"project_id": project_id, "gdpr": json.dumps(gdpr)}) - ) - return cur.fetchone()["gdpr"] + query = cur.mogrify("""UPDATE public.projects + SET gdpr = gdpr|| %(gdpr)s + WHERE project_id = %(project_id)s + AND deleted_at ISNULL + RETURNING gdpr;""", + {"project_id": project_id, "gdpr": json.dumps(gdpr)}) + cur.execute(query=query) + row = cur.fetchone() + if not row: + return {"errors": ["something went wrong"]} + row = row["gdpr"] + row["projectId"] = project_id + return row def get_internal_project_id(project_key): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""\ - SELECT project_id - FROM public.projects - where project_key =%(project_key)s AND deleted_at ISNULL;""", - {"project_key": project_key}) - ) + query = cur.mogrify("""SELECT project_id + FROM public.projects + WHERE project_key =%(project_key)s + AND deleted_at ISNULL;""", + {"project_key": project_key}) + cur.execute(query=query) row = cur.fetchone() return row["project_id"] if row else None def get_project_key(project_id): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""\ - SELECT project_key - FROM public.projects - where project_id =%(project_id)s AND deleted_at ISNULL;""", - {"project_id": project_id}) - ) + query = cur.mogrify("""SELECT project_key + FROM public.projects + WHERE project_id =%(project_id)s + AND deleted_at ISNULL;""", + {"project_id": project_id}) + cur.execute(query=query) project = cur.fetchone() return project["project_key"] if project is not None else None def get_capture_status(project_id): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""\ - SELECT - sample_rate AS rate, sample_rate=100 AS capture_all - FROM public.projects - where project_id =%(project_id)s AND deleted_at ISNULL;""", - {"project_id": project_id}) - ) + query = cur.mogrify("""SELECT sample_rate AS rate, sample_rate=100 AS capture_all + FROM public.projects + WHERE project_id =%(project_id)s + AND deleted_at ISNULL;""", + {"project_id": project_id}) + cur.execute(query=query) return helper.dict_to_camel_case(cur.fetchone()) @@ -280,45 +283,48 @@ def update_capture_status(project_id, changes): if changes.get("captureAll"): sample_rate = 100 with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""\ - UPDATE public.projects - SET sample_rate= %(sample_rate)s - WHERE project_id =%(project_id)s AND deleted_at ISNULL;""", - {"project_id": project_id, "sample_rate": sample_rate}) - ) + query = cur.mogrify("""UPDATE public.projects + SET sample_rate= %(sample_rate)s + WHERE project_id =%(project_id)s + AND deleted_at ISNULL;""", + {"project_id": project_id, "sample_rate": sample_rate}) + cur.execute(query=query) return changes def get_projects_ids(tenant_id): with pg_client.PostgresClient() as cur: - cur.execute(cur.mogrify("""SELECT s.project_id - FROM public.projects AS s - WHERE tenant_id =%(tenant_id)s AND s.deleted_at IS NULL - ORDER BY s.project_id;""", {"tenant_id": tenant_id})) + query = cur.mogrify("""SELECT s.project_id + FROM public.projects AS s + WHERE tenant_id =%(tenant_id)s + AND s.deleted_at IS NULL + ORDER BY s.project_id;""", {"tenant_id": tenant_id}) + cur.execute(query=query) rows = cur.fetchall() return [r["project_id"] for r in rows] def get_project_by_key(tenant_id, project_key, include_last_session=False, include_gdpr=None): with pg_client.PostgresClient() as cur: - query = cur.mogrify(f"""\ - SELECT - s.project_key, - s.name - {",(SELECT max(ss.start_ts) FROM public.sessions AS ss WHERE ss.project_key = %(project_key)s) AS last_recorded_session_at" if include_last_session else ""} - {',s.gdpr' if include_gdpr else ''} - FROM public.projects AS s - where s.project_key =%(project_key)s - AND s.tenant_id =%(tenant_id)s - AND s.deleted_at IS NULL - LIMIT 1;""", + extra_select = "" + if include_last_session: + extra_select += """,(SELECT max(ss.start_ts) + FROM public.sessions AS ss + WHERE ss.project_key = %(project_key)s) AS last_recorded_session_at""" + if include_gdpr: + extra_select += ",s.gdpr" + query = cur.mogrify(f"""SELECT s.project_key, + s.name + {extra_select} + FROM public.projects AS s + WHERE s.project_key =%(project_key)s + AND s.tenant_id =%(tenant_id)s + AND s.deleted_at IS NULL + LIMIT 1;""", {"project_key": project_key, "tenant_id": tenant_id}) - cur.execute( - query=query - ) + cur.execute(query=query) row = cur.fetchone() return helper.dict_to_camel_case(row) @@ -328,27 +334,24 @@ def is_authorized(project_id, tenant_id, user_id=None): return False with pg_client.PostgresClient() as cur: role_query = """INNER JOIN LATERAL (SELECT 1 - FROM users - INNER JOIN roles USING (role_id) - LEFT JOIN roles_projects USING (role_id) - WHERE users.user_id = %(user_id)s - AND users.deleted_at ISNULL - AND users.tenant_id = %(tenant_id)s - AND (roles.all_projects OR roles_projects.project_id = %(project_id)s) - ) AS role_project ON (TRUE)""" + FROM users + INNER JOIN roles USING (role_id) + LEFT JOIN roles_projects USING (role_id) + WHERE users.user_id = %(user_id)s + AND users.deleted_at ISNULL + AND users.tenant_id = %(tenant_id)s + AND (roles.all_projects OR roles_projects.project_id = %(project_id)s) + ) AS role_project ON (TRUE)""" - query = cur.mogrify(f"""\ - SELECT project_id - FROM public.projects AS s - {role_query if user_id is not None else ""} - where s.tenant_id =%(tenant_id)s - AND s.project_id =%(project_id)s - AND s.deleted_at IS NULL - LIMIT 1;""", + query = cur.mogrify(f"""SELECT project_id + FROM public.projects AS s + {role_query if user_id is not None else ""} + WHERE s.tenant_id =%(tenant_id)s + AND s.project_id =%(project_id)s + AND s.deleted_at IS NULL + LIMIT 1;""", {"tenant_id": tenant_id, "project_id": project_id, "user_id": user_id}) - cur.execute( - query=query - ) + cur.execute(query=query) row = cur.fetchone() return row is not None @@ -357,16 +360,13 @@ def is_authorized_batch(project_ids, tenant_id): if project_ids is None or not len(project_ids): return False with pg_client.PostgresClient() as cur: - query = cur.mogrify("""\ - SELECT project_id - FROM public.projects - WHERE tenant_id =%(tenant_id)s - AND project_id IN %(project_ids)s - AND deleted_at IS NULL;""", + query = cur.mogrify("""SELECT project_id + FROM public.projects + WHERE tenant_id =%(tenant_id)s + AND project_id IN %(project_ids)s + AND deleted_at IS NULL;""", {"tenant_id": tenant_id, "project_ids": tuple(project_ids)}) - cur.execute( - query=query - ) + cur.execute(query=query) rows = cur.fetchall() return [r["project_id"] for r in rows] diff --git a/ee/api/chalicelib/core/reset_password.py b/ee/api/chalicelib/core/reset_password.py index 6f1af14b6..889b6d2f8 100644 --- a/ee/api/chalicelib/core/reset_password.py +++ b/ee/api/chalicelib/core/reset_password.py @@ -1,3 +1,5 @@ +from decouple import config + import schemas from chalicelib.core import users from chalicelib.utils import email_helper, captcha, helper @@ -15,6 +17,8 @@ def reset(data: schemas.ForgetPasswordPayloadSchema): # ---FOR SSO if a_user.get("origin") is not None and a_user.get("hasPassword", False) is False: return {"errors": ["Please use your SSO to login"]} + if config("enforce_SSO", cast=bool, default=False) and not a_user["superAdmin"] and helper.is_saml2_available(): + return {"errors": ["Please use your SSO to login, enforced by admin"]} # ---------- invitation_link = users.generate_new_invitation(user_id=a_user["id"]) email_helper.send_forgot_password(recipient=data.email, invitation_link=invitation_link) diff --git a/ee/api/chalicelib/core/roles.py b/ee/api/chalicelib/core/roles.py index 5bd80dc06..79f1caec7 100644 --- a/ee/api/chalicelib/core/roles.py +++ b/ee/api/chalicelib/core/roles.py @@ -1,64 +1,81 @@ +from typing import Optional + +from fastapi import HTTPException +from starlette import status + import schemas_ee from chalicelib.core import users, projects from chalicelib.utils import pg_client, helper from chalicelib.utils.TimeUTC import TimeUTC +def __exists_by_name(tenant_id: int, name: str, exclude_id: Optional[int]) -> bool: + with pg_client.PostgresClient() as cur: + query = cur.mogrify(f"""SELECT EXISTS(SELECT 1 + FROM public.roles + WHERE tenant_id = %(tenant_id)s + AND name ILIKE %(name)s + AND deleted_at ISNULL + {"AND role_id!=%(exclude_id)s" if exclude_id else ""}) AS exists;""", + {"tenant_id": tenant_id, "name": name, "exclude_id": exclude_id}) + cur.execute(query=query) + row = cur.fetchone() + return row["exists"] + + def update(tenant_id, user_id, role_id, data: schemas_ee.RolePayloadSchema): admin = users.get(user_id=user_id, tenant_id=tenant_id) if not admin["admin"] and not admin["superAdmin"]: return {"errors": ["unauthorized"]} + if __exists_by_name(tenant_id=tenant_id, name=data.name, exclude_id=role_id): + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"name already exists.") if not data.all_projects and (data.projects is None or len(data.projects) == 0): return {"errors": ["must specify a project or all projects"]} if data.projects is not None and len(data.projects) > 0 and not data.all_projects: data.projects = projects.is_authorized_batch(project_ids=data.projects, tenant_id=tenant_id) with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""SELECT 1 - FROM public.roles - WHERE role_id = %(role_id)s + query = cur.mogrify("""SELECT 1 + FROM public.roles + WHERE role_id = %(role_id)s AND tenant_id = %(tenant_id)s AND protected = TRUE - LIMIT 1;""", - {"tenant_id": tenant_id, "role_id": role_id}) - ) + LIMIT 1;""", + {"tenant_id": tenant_id, "role_id": role_id}) + cur.execute(query=query) if cur.fetchone() is not None: return {"errors": ["this role is protected"]} - cur.execute( - cur.mogrify("""\ - UPDATE public.roles - SET name= %(name)s, - description= %(description)s, - permissions= %(permissions)s, - all_projects= %(all_projects)s - WHERE role_id = %(role_id)s - AND tenant_id = %(tenant_id)s - AND deleted_at ISNULL - AND protected = FALSE - RETURNING *, COALESCE((SELECT ARRAY_AGG(project_id) - FROM roles_projects WHERE roles_projects.role_id=%(role_id)s),'{}') AS projects;""", - {"tenant_id": tenant_id, "role_id": role_id, **data.dict()}) - ) + query = cur.mogrify("""UPDATE public.roles + SET name= %(name)s, + description= %(description)s, + permissions= %(permissions)s, + all_projects= %(all_projects)s + WHERE role_id = %(role_id)s + AND tenant_id = %(tenant_id)s + AND deleted_at ISNULL + AND protected = FALSE + RETURNING *, COALESCE((SELECT ARRAY_AGG(project_id) + FROM roles_projects + WHERE roles_projects.role_id=%(role_id)s),'{}') AS projects;""", + {"tenant_id": tenant_id, "role_id": role_id, **data.dict()}) + cur.execute(query=query) row = cur.fetchone() row["created_at"] = TimeUTC.datetime_to_timestamp(row["created_at"]) if not data.all_projects: d_projects = [i for i in row["projects"] if i not in data.projects] if len(d_projects) > 0: - cur.execute( - cur.mogrify( - "DELETE FROM roles_projects WHERE role_id=%(role_id)s AND project_id IN %(project_ids)s", - {"role_id": role_id, "project_ids": tuple(d_projects)}) - ) + query = cur.mogrify("""DELETE FROM roles_projects + WHERE role_id=%(role_id)s + AND project_id IN %(project_ids)s""", + {"role_id": role_id, "project_ids": tuple(d_projects)}) + cur.execute(query=query) n_projects = [i for i in data.projects if i not in row["projects"]] if len(n_projects) > 0: - cur.execute( - cur.mogrify( - f"""INSERT INTO roles_projects(role_id, project_id) - VALUES {",".join([f"(%(role_id)s,%(project_id_{i})s)" for i in range(len(n_projects))])}""", - {"role_id": role_id, **{f"project_id_{i}": p for i, p in enumerate(n_projects)}}) - ) + query = cur.mogrify(f"""INSERT INTO roles_projects(role_id, project_id) + VALUES {",".join([f"(%(role_id)s,%(project_id_{i})s)" for i in range(len(n_projects))])}""", + {"role_id": role_id, **{f"project_id_{i}": p for i, p in enumerate(n_projects)}}) + cur.execute(query=query) row["projects"] = data.projects return helper.dict_to_camel_case(row) @@ -69,45 +86,46 @@ def create(tenant_id, user_id, data: schemas_ee.RolePayloadSchema): if not admin["admin"] and not admin["superAdmin"]: return {"errors": ["unauthorized"]} + + if __exists_by_name(tenant_id=tenant_id, name=data.name, exclude_id=None): + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"name already exists.") + if not data.all_projects and (data.projects is None or len(data.projects) == 0): return {"errors": ["must specify a project or all projects"]} if data.projects is not None and len(data.projects) > 0 and not data.all_projects: data.projects = projects.is_authorized_batch(project_ids=data.projects, tenant_id=tenant_id) with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""INSERT INTO roles(tenant_id, name, description, permissions, all_projects) - VALUES (%(tenant_id)s, %(name)s, %(description)s, %(permissions)s::text[], %(all_projects)s) - RETURNING *;""", - {"tenant_id": tenant_id, "name": data.name, "description": data.description, - "permissions": data.permissions, "all_projects": data.all_projects}) - ) + query = cur.mogrify("""INSERT INTO roles(tenant_id, name, description, permissions, all_projects) + VALUES (%(tenant_id)s, %(name)s, %(description)s, %(permissions)s::text[], %(all_projects)s) + RETURNING *;""", + {"tenant_id": tenant_id, "name": data.name, "description": data.description, + "permissions": data.permissions, "all_projects": data.all_projects}) + cur.execute(query=query) row = cur.fetchone() row["created_at"] = TimeUTC.datetime_to_timestamp(row["created_at"]) if not data.all_projects: role_id = row["role_id"] - cur.execute( - cur.mogrify(f"""INSERT INTO roles_projects(role_id, project_id) - VALUES {",".join(f"(%(role_id)s,%(project_id_{i})s)" for i in range(len(data.projects)))};""", - {"role_id": role_id, **{f"project_id_{i}": p for i, p in enumerate(data.projects)}}) - ) + query = cur.mogrify(f"""INSERT INTO roles_projects(role_id, project_id) + VALUES {",".join(f"(%(role_id)s,%(project_id_{i})s)" for i in range(len(data.projects)))};""", + {"role_id": role_id, **{f"project_id_{i}": p for i, p in enumerate(data.projects)}}) + cur.execute(query=query) return helper.dict_to_camel_case(row) def get_roles(tenant_id): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""SELECT roles.*, COALESCE(projects, '{}') AS projects - FROM public.roles - LEFT JOIN LATERAL (SELECT array_agg(project_id) AS projects - FROM roles_projects - INNER JOIN projects USING (project_id) - WHERE roles_projects.role_id = roles.role_id - AND projects.deleted_at ISNULL ) AS role_projects ON (TRUE) - WHERE tenant_id =%(tenant_id)s - AND deleted_at IS NULL - ORDER BY role_id;""", - {"tenant_id": tenant_id}) - ) + query = cur.mogrify("""SELECT roles.*, COALESCE(projects, '{}') AS projects + FROM public.roles + LEFT JOIN LATERAL (SELECT array_agg(project_id) AS projects + FROM roles_projects + INNER JOIN projects USING (project_id) + WHERE roles_projects.role_id = roles.role_id + AND projects.deleted_at ISNULL ) AS role_projects ON (TRUE) + WHERE tenant_id =%(tenant_id)s + AND deleted_at IS NULL + ORDER BY role_id;""", + {"tenant_id": tenant_id}) + cur.execute(query=query) rows = cur.fetchall() for r in rows: r["created_at"] = TimeUTC.datetime_to_timestamp(r["created_at"]) @@ -116,14 +134,13 @@ def get_roles(tenant_id): def get_role_by_name(tenant_id, name): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""SELECT * - FROM public.roles - where tenant_id =%(tenant_id)s - AND deleted_at IS NULL - AND name ILIKE %(name)s;""", - {"tenant_id": tenant_id, "name": name}) - ) + query = cur.mogrify("""SELECT * + FROM public.roles + WHERE tenant_id =%(tenant_id)s + AND deleted_at IS NULL + AND name ILIKE %(name)s;""", + {"tenant_id": tenant_id, "name": name}) + cur.execute(query=query) row = cur.fetchone() if row is not None: row["created_at"] = TimeUTC.datetime_to_timestamp(row["created_at"]) @@ -136,33 +153,30 @@ def delete(tenant_id, user_id, role_id): if not admin["admin"] and not admin["superAdmin"]: return {"errors": ["unauthorized"]} with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""SELECT 1 - FROM public.roles - WHERE role_id = %(role_id)s - AND tenant_id = %(tenant_id)s - AND protected = TRUE - LIMIT 1;""", - {"tenant_id": tenant_id, "role_id": role_id}) - ) + query = cur.mogrify("""SELECT 1 + FROM public.roles + WHERE role_id = %(role_id)s + AND tenant_id = %(tenant_id)s + AND protected = TRUE + LIMIT 1;""", + {"tenant_id": tenant_id, "role_id": role_id}) + cur.execute(query=query) if cur.fetchone() is not None: return {"errors": ["this role is protected"]} - cur.execute( - cur.mogrify("""SELECT 1 - FROM public.users - WHERE role_id = %(role_id)s - AND tenant_id = %(tenant_id)s - LIMIT 1;""", - {"tenant_id": tenant_id, "role_id": role_id}) - ) + query = cur.mogrify("""SELECT 1 + FROM public.users + WHERE role_id = %(role_id)s + AND tenant_id = %(tenant_id)s + LIMIT 1;""", + {"tenant_id": tenant_id, "role_id": role_id}) + cur.execute(query=query) if cur.fetchone() is not None: return {"errors": ["this role is already attached to other user(s)"]} - cur.execute( - cur.mogrify("""UPDATE public.roles - SET deleted_at = timezone('utc'::text, now()) - WHERE role_id = %(role_id)s - AND tenant_id = %(tenant_id)s - AND protected = FALSE;""", - {"tenant_id": tenant_id, "role_id": role_id}) - ) + query = cur.mogrify("""UPDATE public.roles + SET deleted_at = timezone('utc'::text, now()) + WHERE role_id = %(role_id)s + AND tenant_id = %(tenant_id)s + AND protected = FALSE;""", + {"tenant_id": tenant_id, "role_id": role_id}) + cur.execute(query=query) return get_roles(tenant_id=tenant_id) diff --git a/ee/api/chalicelib/core/sessions.py b/ee/api/chalicelib/core/sessions.py index 7d999fe6c..6d92c3954 100644 --- a/ee/api/chalicelib/core/sessions.py +++ b/ee/api/chalicelib/core/sessions.py @@ -3,9 +3,11 @@ from typing import List import schemas import schemas_ee from chalicelib.core import events, metadata, events_ios, \ - sessions_mobs, issues, projects, errors, resources, assist, performance_event, sessions_viewed, sessions_favorite, \ + sessions_mobs, issues, projects, resources, assist, performance_event, sessions_favorite, \ sessions_devtool, sessions_notes +from chalicelib.utils import errors_helper from chalicelib.utils import pg_client, helper, metrics_helper +from chalicelib.utils import sql_helper as sh SESSION_PROJECTION_COLS = """s.project_id, s.session_id::text AS session_id, @@ -62,7 +64,7 @@ def get_by_id2_pg(project_id, session_id, context: schemas_ee.CurrentContext, fu (SELECT project_key FROM public.projects WHERE project_id = %(project_id)s LIMIT 1) AS project_key, encode(file_key,'hex') AS file_key {"," if len(extra_query) > 0 else ""}{",".join(extra_query)} - {(",json_build_object(" + ",".join([f"'{m}',p.{m}" for m in metadata._get_column_names()]) + ") AS project_metadata") if group_metadata else ''} + {(",json_build_object(" + ",".join([f"'{m}',p.{m}" for m in metadata.column_names()]) + ") AS project_metadata") if group_metadata else ''} FROM public.sessions AS s {"INNER JOIN public.projects AS p USING (project_id)" if group_metadata else ""} WHERE s.project_id = %(project_id)s AND s.session_id = %(session_id)s;""", @@ -86,16 +88,16 @@ def get_by_id2_pg(project_id, session_id, context: schemas_ee.CurrentContext, fu session_id=session_id) data['mobsUrl'] = sessions_mobs.get_ios(session_id=session_id) else: - data['events'] = events.get_by_sessionId2_pg(project_id=project_id, session_id=session_id, - group_clickrage=True) + data['events'] = events.get_by_session_id(project_id=project_id, session_id=session_id, + group_clickrage=True) all_errors = events.get_errors_by_session_id(session_id=session_id, project_id=project_id) data['stackEvents'] = [e for e in all_errors if e['source'] != "js_exception"] # to keep only the first stack # limit the number of errors to reduce the response-body size - data['errors'] = [errors.format_first_stack_frame(e) for e in all_errors + data['errors'] = [errors_helper.format_first_stack_frame(e) for e in all_errors if e['source'] == "js_exception"][:500] - data['userEvents'] = events.get_customs_by_sessionId2_pg(project_id=project_id, - session_id=session_id) + data['userEvents'] = events.get_customs_by_session_id(project_id=project_id, + session_id=session_id) data['domURL'] = sessions_mobs.get_urls(session_id=session_id, project_id=project_id) data['mobsUrl'] = sessions_mobs.get_urls_depercated(session_id=session_id) data['devtoolsURL'] = sessions_devtool.get_urls(session_id=session_id, project_id=project_id, @@ -117,67 +119,6 @@ def get_by_id2_pg(project_id, session_id, context: schemas_ee.CurrentContext, fu return None -def __get_sql_operator(op: schemas.SearchEventOperator): - return { - schemas.SearchEventOperator._is: "=", - schemas.SearchEventOperator._is_any: "IN", - schemas.SearchEventOperator._on: "=", - schemas.SearchEventOperator._on_any: "IN", - schemas.SearchEventOperator._is_not: "!=", - schemas.SearchEventOperator._not_on: "!=", - schemas.SearchEventOperator._contains: "ILIKE", - schemas.SearchEventOperator._not_contains: "NOT ILIKE", - schemas.SearchEventOperator._starts_with: "ILIKE", - schemas.SearchEventOperator._ends_with: "ILIKE", - }.get(op, "=") - - -def __is_negation_operator(op: schemas.SearchEventOperator): - return op in [schemas.SearchEventOperator._is_not, - schemas.SearchEventOperator._not_on, - schemas.SearchEventOperator._not_contains] - - -def __reverse_sql_operator(op): - return "=" if op == "!=" else "!=" if op == "=" else "ILIKE" if op == "NOT ILIKE" else "NOT ILIKE" - - -def __get_sql_operator_multiple(op: schemas.SearchEventOperator): - return " IN " if op not in [schemas.SearchEventOperator._is_not, schemas.SearchEventOperator._not_on, - schemas.SearchEventOperator._not_contains] else " NOT IN " - - -def __get_sql_value_multiple(values): - if isinstance(values, tuple): - return values - return tuple(values) if isinstance(values, list) else (values,) - - -def _multiple_conditions(condition, values, value_key="value", is_not=False): - query = [] - for i in range(len(values)): - k = f"{value_key}_{i}" - query.append(condition.replace(value_key, k)) - return "(" + (" AND " if is_not else " OR ").join(query) + ")" - - -def _multiple_values(values, value_key="value"): - query_values = {} - if values is not None and isinstance(values, list): - for i in range(len(values)): - k = f"{value_key}_{i}" - query_values[k] = values[i] - return query_values - - -def _isAny_opreator(op: schemas.SearchEventOperator): - return op in [schemas.SearchEventOperator._on_any, schemas.SearchEventOperator._is_any] - - -def _isUndefined_operator(op: schemas.SearchEventOperator): - return op in [schemas.SearchEventOperator._is_undefined] - - # This function executes the query and return result def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_id, errors_only=False, error_status=schemas.ErrorStatus.all, count_only=False, issue=None, ids_only=False): @@ -213,9 +154,9 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_ elif data.group_by_user: g_sort = "count(full_sessions)" if data.order is None: - data.order = schemas.SortOrderType.desc + data.order = schemas.SortOrderType.desc.value else: - data.order = data.order.upper() + data.order = data.order.value if data.sort is not None and data.sort != 'sessionsCount': sort = helper.key_to_snake_case(data.sort) g_sort = f"{'MIN' if data.order == schemas.SortOrderType.desc else 'MAX'}({sort})" @@ -248,7 +189,9 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_ full_args) else: if data.order is None: - data.order = schemas.SortOrderType.desc + data.order = schemas.SortOrderType.desc.value + else: + data.order = data.order.value sort = 'session_id' if data.sort is not None and data.sort != "session_id": # sort += " " + data.order + "," + helper.key_to_snake_case(data.sort) @@ -307,13 +250,13 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, density: int, view_type: schemas.MetricTimeseriesViewType, metric_type: schemas.MetricType, - metric_of: schemas.TableMetricOfType, metric_value: List): + metric_of: schemas.MetricOfTable, metric_value: List): step_size = int(metrics_helper.__get_step_size(endTimestamp=data.endDate, startTimestamp=data.startDate, density=density, factor=1, decimal=True)) extra_event = None - if metric_of == schemas.TableMetricOfType.visited_url: + if metric_of == schemas.MetricOfTable.visited_url: extra_event = "events.pages" - elif metric_of == schemas.TableMetricOfType.issues and len(metric_value) > 0: + elif metric_of == schemas.MetricOfTable.issues and len(metric_value) > 0: data.filters.append(schemas.SessionSearchFilterSchema(value=metric_value, type=schemas.FilterType.issue, operator=schemas.SearchEventOperator._is)) full_args, query_part = search_query_parts(data=data, error_status=None, errors_only=False, @@ -356,18 +299,19 @@ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, d else: sessions = cur.fetchone()["count"] elif metric_type == schemas.MetricType.table: - if isinstance(metric_of, schemas.TableMetricOfType): + if isinstance(metric_of, schemas.MetricOfTable): main_col = "user_id" extra_col = "" extra_where = "" pre_query = "" - if metric_of == schemas.TableMetricOfType.user_country: + distinct_on = "s.session_id" + if metric_of == schemas.MetricOfTable.user_country: main_col = "user_country" - elif metric_of == schemas.TableMetricOfType.user_device: + elif metric_of == schemas.MetricOfTable.user_device: main_col = "user_device" - elif metric_of == schemas.TableMetricOfType.user_browser: + elif metric_of == schemas.MetricOfTable.user_browser: main_col = "user_browser" - elif metric_of == schemas.TableMetricOfType.issues: + elif metric_of == schemas.MetricOfTable.issues: main_col = "issue" extra_col = f", UNNEST(s.issue_types) AS {main_col}" if len(metric_value) > 0: @@ -377,16 +321,17 @@ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, d extra_where.append(f"{main_col} = %({arg_name})s") full_args[arg_name] = metric_value[i] extra_where = f"WHERE ({' OR '.join(extra_where)})" - elif metric_of == schemas.TableMetricOfType.visited_url: + elif metric_of == schemas.MetricOfTable.visited_url: main_col = "path" extra_col = ", path" + distinct_on += ",path" main_query = cur.mogrify(f"""{pre_query} SELECT COUNT(*) AS count, COALESCE(JSONB_AGG(users_sessions) FILTER ( WHERE rn <= 200 ), '[]'::JSONB) AS values FROM (SELECT {main_col} AS name, - count(full_sessions) AS session_count, + count(DISTINCT session_id) AS session_count, ROW_NUMBER() OVER (ORDER BY count(full_sessions) DESC) AS rn FROM (SELECT * - FROM (SELECT DISTINCT ON(s.session_id) s.session_id, s.user_uuid, + FROM (SELECT DISTINCT ON({distinct_on}) s.session_id, s.user_uuid, s.user_id, s.user_os, s.user_browser, s.user_device, s.user_device_type, s.user_country, s.issue_types{extra_col} @@ -423,7 +368,8 @@ def __is_valid_event(is_any: bool, event: schemas._SessionSearchEventSchema): # this function generates the query and return the generated-query with the dict of query arguments -def search_query_parts(data, error_status, errors_only, favorite_only, issue, project_id, user_id, extra_event=None): +def search_query_parts(data: schemas.SessionsSearchPayloadSchema, error_status, errors_only, favorite_only, issue, + project_id, user_id, extra_event=None): ss_constraints = [] full_args = {"project_id": project_id, "startDate": data.startDate, "endDate": data.endDate, "projectId": project_id, "userId": user_id} @@ -441,15 +387,15 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr filter_type = f.type f.value = helper.values_for_operator(value=f.value, op=f.operator) f_k = f"f_value{i}" - full_args = {**full_args, **_multiple_values(f.value, value_key=f_k)} - op = __get_sql_operator(f.operator) \ + full_args = {**full_args, **sh.multi_values(f.value, value_key=f_k)} + op = sh.get_sql_operator(f.operator) \ if filter_type not in [schemas.FilterType.events_count] else f.operator - is_any = _isAny_opreator(f.operator) - is_undefined = _isUndefined_operator(f.operator) + is_any = sh.isAny_opreator(f.operator) + is_undefined = sh.isUndefined_operator(f.operator) if not is_any and not is_undefined and len(f.value) == 0: continue is_not = False - if __is_negation_operator(f.operator): + if sh.is_negation_operator(f.operator): is_not = True if filter_type == schemas.FilterType.user_browser: if is_any: @@ -457,9 +403,10 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.user_browser IS NOT NULL') else: extra_constraints.append( - _multiple_conditions(f's.user_browser {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f's.user_browser {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) ss_constraints.append( - _multiple_conditions(f'ms.user_browser {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f'ms.user_browser {op} %({f_k})s', f.value, is_not=is_not, + value_key=f_k)) elif filter_type in [schemas.FilterType.user_os, schemas.FilterType.user_os_ios]: if is_any: @@ -467,9 +414,9 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.user_os IS NOT NULL') else: extra_constraints.append( - _multiple_conditions(f's.user_os {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f's.user_os {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) ss_constraints.append( - _multiple_conditions(f'ms.user_os {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f'ms.user_os {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) elif filter_type in [schemas.FilterType.user_device, schemas.FilterType.user_device_ios]: if is_any: @@ -477,9 +424,9 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.user_device IS NOT NULL') else: extra_constraints.append( - _multiple_conditions(f's.user_device {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f's.user_device {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) ss_constraints.append( - _multiple_conditions(f'ms.user_device {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f'ms.user_device {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) elif filter_type in [schemas.FilterType.user_country, schemas.FilterType.user_country_ios]: if is_any: @@ -487,9 +434,10 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.user_country IS NOT NULL') else: extra_constraints.append( - _multiple_conditions(f's.user_country {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f's.user_country {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) ss_constraints.append( - _multiple_conditions(f'ms.user_country {op} %({f_k})s', f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f'ms.user_country {op} %({f_k})s', f.value, is_not=is_not, + value_key=f_k)) elif filter_type in [schemas.FilterType.utm_source]: if is_any: @@ -500,11 +448,11 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.utm_source IS NULL') else: extra_constraints.append( - _multiple_conditions(f's.utm_source {op} %({f_k})s::text', f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f's.utm_source {op} %({f_k})s::text', f.value, is_not=is_not, + value_key=f_k)) ss_constraints.append( - _multiple_conditions(f'ms.utm_source {op} %({f_k})s::text', f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f'ms.utm_source {op} %({f_k})s::text', f.value, is_not=is_not, + value_key=f_k)) elif filter_type in [schemas.FilterType.utm_medium]: if is_any: extra_constraints.append('s.utm_medium IS NOT NULL') @@ -514,11 +462,11 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.utm_medium IS NULL') else: extra_constraints.append( - _multiple_conditions(f's.utm_medium {op} %({f_k})s::text', f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f's.utm_medium {op} %({f_k})s::text', f.value, is_not=is_not, + value_key=f_k)) ss_constraints.append( - _multiple_conditions(f'ms.utm_medium {op} %({f_k})s::text', f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f'ms.utm_medium {op} %({f_k})s::text', f.value, is_not=is_not, + value_key=f_k)) elif filter_type in [schemas.FilterType.utm_campaign]: if is_any: extra_constraints.append('s.utm_campaign IS NOT NULL') @@ -528,11 +476,11 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.utm_campaign IS NULL') else: extra_constraints.append( - _multiple_conditions(f's.utm_campaign {op} %({f_k})s::text', f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f's.utm_campaign {op} %({f_k})s::text', f.value, is_not=is_not, + value_key=f_k)) ss_constraints.append( - _multiple_conditions(f'ms.utm_campaign {op} %({f_k})s::text', f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f'ms.utm_campaign {op} %({f_k})s::text', f.value, is_not=is_not, + value_key=f_k)) elif filter_type == schemas.FilterType.duration: if len(f.value) > 0 and f.value[0] is not None: @@ -549,8 +497,9 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr extra_constraints.append('s.base_referrer IS NOT NULL') else: extra_constraints.append( - _multiple_conditions(f"s.base_referrer {op} %({f_k})s", f.value, is_not=is_not, value_key=f_k)) - elif filter_type == events.event_type.METADATA.ui_type: + sh.multi_conditions(f"s.base_referrer {op} %({f_k})s", f.value, is_not=is_not, + value_key=f_k)) + elif filter_type == events.EventType.METADATA.ui_type: # get metadata list only if you need it if meta_keys is None: meta_keys = metadata.get(project_id=project_id) @@ -564,11 +513,11 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append(f"ms.{metadata.index_to_colname(meta_keys[f.source])} IS NULL") else: extra_constraints.append( - _multiple_conditions( + sh.multi_conditions( f"s.{metadata.index_to_colname(meta_keys[f.source])} {op} %({f_k})s::text", f.value, is_not=is_not, value_key=f_k)) ss_constraints.append( - _multiple_conditions( + sh.multi_conditions( f"ms.{metadata.index_to_colname(meta_keys[f.source])} {op} %({f_k})s::text", f.value, is_not=is_not, value_key=f_k)) elif filter_type in [schemas.FilterType.user_id, schemas.FilterType.user_id_ios]: @@ -580,9 +529,11 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.user_id IS NULL') else: extra_constraints.append( - _multiple_conditions(f"s.user_id {op} %({f_k})s::text", f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f"s.user_id {op} %({f_k})s::text", f.value, is_not=is_not, + value_key=f_k)) ss_constraints.append( - _multiple_conditions(f"ms.user_id {op} %({f_k})s::text", f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f"ms.user_id {op} %({f_k})s::text", f.value, is_not=is_not, + value_key=f_k)) elif filter_type in [schemas.FilterType.user_anonymous_id, schemas.FilterType.user_anonymous_id_ios]: if is_any: @@ -593,11 +544,11 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.user_anonymous_id IS NULL') else: extra_constraints.append( - _multiple_conditions(f"s.user_anonymous_id {op} %({f_k})s::text", f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f"s.user_anonymous_id {op} %({f_k})s::text", f.value, is_not=is_not, + value_key=f_k)) ss_constraints.append( - _multiple_conditions(f"ms.user_anonymous_id {op} %({f_k})s::text", f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f"ms.user_anonymous_id {op} %({f_k})s::text", f.value, is_not=is_not, + value_key=f_k)) elif filter_type in [schemas.FilterType.rev_id, schemas.FilterType.rev_id_ios]: if is_any: extra_constraints.append('s.rev_id IS NOT NULL') @@ -607,40 +558,58 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr ss_constraints.append('ms.rev_id IS NULL') else: extra_constraints.append( - _multiple_conditions(f"s.rev_id {op} %({f_k})s::text", f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f"s.rev_id {op} %({f_k})s::text", f.value, is_not=is_not, value_key=f_k)) ss_constraints.append( - _multiple_conditions(f"ms.rev_id {op} %({f_k})s::text", f.value, is_not=is_not, value_key=f_k)) + sh.multi_conditions(f"ms.rev_id {op} %({f_k})s::text", f.value, is_not=is_not, + value_key=f_k)) elif filter_type == schemas.FilterType.platform: - # op = __get_sql_operator(f.operator) + # op = __ sh.get_sql_operator(f.operator) extra_constraints.append( - _multiple_conditions(f"s.user_device_type {op} %({f_k})s", f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f"s.user_device_type {op} %({f_k})s", f.value, is_not=is_not, + value_key=f_k)) ss_constraints.append( - _multiple_conditions(f"ms.user_device_type {op} %({f_k})s", f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f"ms.user_device_type {op} %({f_k})s", f.value, is_not=is_not, + value_key=f_k)) elif filter_type == schemas.FilterType.issue: if is_any: extra_constraints.append("array_length(s.issue_types, 1) > 0") ss_constraints.append("array_length(ms.issue_types, 1) > 0") else: extra_constraints.append( - _multiple_conditions(f"%({f_k})s {op} ANY (s.issue_types)", f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f"%({f_k})s {op} ANY (s.issue_types)", f.value, is_not=is_not, + value_key=f_k)) ss_constraints.append( - _multiple_conditions(f"%({f_k})s {op} ANY (ms.issue_types)", f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f"%({f_k})s {op} ANY (ms.issue_types)", f.value, is_not=is_not, + value_key=f_k)) + # search sessions with click_rage on a specific selector + if len(f.filters) > 0 and schemas.IssueType.click_rage in f.value: + for j, sf in enumerate(f.filters): + if sf.operator == schemas.IssueFilterOperator._on_selector: + f_k = f"f_value{i}_{j}" + full_args = {**full_args, **sh.multi_values(sf.value, value_key=f_k)} + extra_constraints += ["mc.timestamp>=%(startDate)s", + "mc.timestamp<=%(endDate)s", + "mis.type='click_rage'", + sh.multi_conditions(f"mc.selector=%({f_k})s", + sf.value, is_not=is_not, + value_key=f_k)] + + extra_from += """INNER JOIN events.clicks AS mc USING(session_id) + INNER JOIN events_common.issues USING (session_id,timestamp) + INNER JOIN public.issues AS mis USING (issue_id)\n""" + elif filter_type == schemas.FilterType.events_count: extra_constraints.append( - _multiple_conditions(f"s.events_count {op} %({f_k})s", f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f"s.events_count {op} %({f_k})s", f.value, is_not=is_not, + value_key=f_k)) ss_constraints.append( - _multiple_conditions(f"ms.events_count {op} %({f_k})s", f.value, is_not=is_not, - value_key=f_k)) + sh.multi_conditions(f"ms.events_count {op} %({f_k})s", f.value, is_not=is_not, + value_key=f_k)) # --------------------------------------------------------------------------- if len(data.events) > 0: valid_events_count = 0 for event in data.events: - is_any = _isAny_opreator(event.operator) + is_any = sh.isAny_opreator(event.operator) if not isinstance(event.value, list): event.value = [event.value] if __is_valid_event(is_any=is_any, event=event): @@ -652,16 +621,16 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr events_joiner = " UNION " if or_events else " INNER JOIN LATERAL " for i, event in enumerate(data.events): event_type = event.type - is_any = _isAny_opreator(event.operator) + is_any = sh.isAny_opreator(event.operator) if not isinstance(event.value, list): event.value = [event.value] if not __is_valid_event(is_any=is_any, event=event): continue - op = __get_sql_operator(event.operator) + op = sh.get_sql_operator(event.operator) is_not = False - if __is_negation_operator(event.operator): + if sh.is_negation_operator(event.operator): is_not = True - op = __reverse_sql_operator(op) + op = sh.reverse_sql_operator(op) if event_index == 0 or or_events: event_from = "%s INNER JOIN public.sessions AS ms USING (session_id)" event_where = ["ms.project_id = %(projectId)s", "main.timestamp >= %(startDate)s", @@ -681,116 +650,120 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr if event.type != schemas.PerformanceEventType.time_between_events: event.value = helper.values_for_operator(value=event.value, op=event.operator) full_args = {**full_args, - **_multiple_values(event.value, value_key=e_k), - **_multiple_values(event.source, value_key=s_k)} + **sh.multi_values(event.value, value_key=e_k), + **sh.multi_values(event.source, value_key=s_k)} - if event_type == events.event_type.CLICK.ui_type: - event_from = event_from % f"{events.event_type.CLICK.table} AS main " + if event_type == events.EventType.CLICK.ui_type: + event_from = event_from % f"{events.EventType.CLICK.table} AS main " + if not is_any: + if event.operator == schemas.ClickEventExtraOperator._on_selector: + event_where.append( + sh.multi_conditions(f"main.selector = %({e_k})s", event.value, value_key=e_k)) + else: + event_where.append( + sh.multi_conditions(f"main.{events.EventType.CLICK.column} {op} %({e_k})s", event.value, + value_key=e_k)) + + elif event_type == events.EventType.INPUT.ui_type: + event_from = event_from % f"{events.EventType.INPUT.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.CLICK.column} {op} %({e_k})s", event.value, - value_key=e_k)) - - elif event_type == events.event_type.INPUT.ui_type: - event_from = event_from % f"{events.event_type.INPUT.table} AS main " - if not is_any: - event_where.append( - _multiple_conditions(f"main.{events.event_type.INPUT.column} {op} %({e_k})s", event.value, - value_key=e_k)) + sh.multi_conditions(f"main.{events.EventType.INPUT.column} {op} %({e_k})s", event.value, + value_key=e_k)) if event.source is not None and len(event.source) > 0: - event_where.append(_multiple_conditions(f"main.value ILIKE %(custom{i})s", event.source, - value_key=f"custom{i}")) - full_args = {**full_args, **_multiple_values(event.source, value_key=f"custom{i}")} + event_where.append(sh.multi_conditions(f"main.value ILIKE %(custom{i})s", event.source, + value_key=f"custom{i}")) + full_args = {**full_args, **sh.multi_values(event.source, value_key=f"custom{i}")} - elif event_type == events.event_type.LOCATION.ui_type: - event_from = event_from % f"{events.event_type.LOCATION.table} AS main " + elif event_type == events.EventType.LOCATION.ui_type: + event_from = event_from % f"{events.EventType.LOCATION.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.LOCATION.column} {op} %({e_k})s", - event.value, value_key=e_k)) - elif event_type == events.event_type.CUSTOM.ui_type: - event_from = event_from % f"{events.event_type.CUSTOM.table} AS main " + sh.multi_conditions(f"main.{events.EventType.LOCATION.column} {op} %({e_k})s", + event.value, value_key=e_k)) + elif event_type == events.EventType.CUSTOM.ui_type: + event_from = event_from % f"{events.EventType.CUSTOM.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.CUSTOM.column} {op} %({e_k})s", event.value, - value_key=e_k)) - elif event_type == events.event_type.REQUEST.ui_type: - event_from = event_from % f"{events.event_type.REQUEST.table} AS main " + sh.multi_conditions(f"main.{events.EventType.CUSTOM.column} {op} %({e_k})s", event.value, + value_key=e_k)) + elif event_type == events.EventType.REQUEST.ui_type: + event_from = event_from % f"{events.EventType.REQUEST.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.REQUEST.column} {op} %({e_k})s", event.value, - value_key=e_k)) + sh.multi_conditions(f"main.{events.EventType.REQUEST.column} {op} %({e_k})s", event.value, + value_key=e_k)) # elif event_type == events.event_type.GRAPHQL.ui_type: # event_from = event_from % f"{events.event_type.GRAPHQL.table} AS main " # if not is_any: # event_where.append( # _multiple_conditions(f"main.{events.event_type.GRAPHQL.column} {op} %({e_k})s", event.value, # value_key=e_k)) - elif event_type == events.event_type.STATEACTION.ui_type: - event_from = event_from % f"{events.event_type.STATEACTION.table} AS main " + elif event_type == events.EventType.STATEACTION.ui_type: + event_from = event_from % f"{events.EventType.STATEACTION.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.STATEACTION.column} {op} %({e_k})s", - event.value, value_key=e_k)) - elif event_type == events.event_type.ERROR.ui_type: - event_from = event_from % f"{events.event_type.ERROR.table} AS main INNER JOIN public.errors AS main1 USING(error_id)" + sh.multi_conditions(f"main.{events.EventType.STATEACTION.column} {op} %({e_k})s", + event.value, value_key=e_k)) + elif event_type == events.EventType.ERROR.ui_type: + event_from = event_from % f"{events.EventType.ERROR.table} AS main INNER JOIN public.errors AS main1 USING(error_id)" event.source = list(set(event.source)) if not is_any and event.value not in [None, "*", ""]: event_where.append( - _multiple_conditions(f"(main1.message {op} %({e_k})s OR main1.name {op} %({e_k})s)", - event.value, value_key=e_k)) + sh.multi_conditions(f"(main1.message {op} %({e_k})s OR main1.name {op} %({e_k})s)", + event.value, value_key=e_k)) if len(event.source) > 0 and event.source[0] not in [None, "*", ""]: - event_where.append(_multiple_conditions(f"main1.source = %({s_k})s", event.source, value_key=s_k)) + event_where.append(sh.multi_conditions(f"main1.source = %({s_k})s", event.source, value_key=s_k)) # ----- IOS - elif event_type == events.event_type.CLICK_IOS.ui_type: - event_from = event_from % f"{events.event_type.CLICK_IOS.table} AS main " + elif event_type == events.EventType.CLICK_IOS.ui_type: + event_from = event_from % f"{events.EventType.CLICK_IOS.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.CLICK_IOS.column} {op} %({e_k})s", - event.value, value_key=e_k)) + sh.multi_conditions(f"main.{events.EventType.CLICK_IOS.column} {op} %({e_k})s", + event.value, value_key=e_k)) - elif event_type == events.event_type.INPUT_IOS.ui_type: - event_from = event_from % f"{events.event_type.INPUT_IOS.table} AS main " + elif event_type == events.EventType.INPUT_IOS.ui_type: + event_from = event_from % f"{events.EventType.INPUT_IOS.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.INPUT_IOS.column} {op} %({e_k})s", - event.value, value_key=e_k)) + sh.multi_conditions(f"main.{events.EventType.INPUT_IOS.column} {op} %({e_k})s", + event.value, value_key=e_k)) if event.source is not None and len(event.source) > 0: - event_where.append(_multiple_conditions(f"main.value ILIKE %(custom{i})s", event.source, - value_key="custom{i}")) - full_args = {**full_args, **_multiple_values(event.source, f"custom{i}")} - elif event_type == events.event_type.VIEW_IOS.ui_type: - event_from = event_from % f"{events.event_type.VIEW_IOS.table} AS main " + event_where.append(sh.multi_conditions(f"main.value ILIKE %(custom{i})s", event.source, + value_key="custom{i}")) + full_args = {**full_args, **sh.multi_values(event.source, f"custom{i}")} + elif event_type == events.EventType.VIEW_IOS.ui_type: + event_from = event_from % f"{events.EventType.VIEW_IOS.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.VIEW_IOS.column} {op} %({e_k})s", - event.value, value_key=e_k)) - elif event_type == events.event_type.CUSTOM_IOS.ui_type: - event_from = event_from % f"{events.event_type.CUSTOM_IOS.table} AS main " + sh.multi_conditions(f"main.{events.EventType.VIEW_IOS.column} {op} %({e_k})s", + event.value, value_key=e_k)) + elif event_type == events.EventType.CUSTOM_IOS.ui_type: + event_from = event_from % f"{events.EventType.CUSTOM_IOS.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.CUSTOM_IOS.column} {op} %({e_k})s", - event.value, value_key=e_k)) - elif event_type == events.event_type.REQUEST_IOS.ui_type: - event_from = event_from % f"{events.event_type.REQUEST_IOS.table} AS main " + sh.multi_conditions(f"main.{events.EventType.CUSTOM_IOS.column} {op} %({e_k})s", + event.value, value_key=e_k)) + elif event_type == events.EventType.REQUEST_IOS.ui_type: + event_from = event_from % f"{events.EventType.REQUEST_IOS.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.REQUEST_IOS.column} {op} %({e_k})s", - event.value, value_key=e_k)) - elif event_type == events.event_type.ERROR_IOS.ui_type: - event_from = event_from % f"{events.event_type.ERROR_IOS.table} AS main INNER JOIN public.crashes_ios AS main1 USING(crash_id)" + sh.multi_conditions(f"main.{events.EventType.REQUEST_IOS.column} {op} %({e_k})s", + event.value, value_key=e_k)) + elif event_type == events.EventType.ERROR_IOS.ui_type: + event_from = event_from % f"{events.EventType.ERROR_IOS.table} AS main INNER JOIN public.crashes_ios AS main1 USING(crash_id)" if not is_any and event.value not in [None, "*", ""]: event_where.append( - _multiple_conditions(f"(main1.reason {op} %({e_k})s OR main1.name {op} %({e_k})s)", - event.value, value_key=e_k)) + sh.multi_conditions(f"(main1.reason {op} %({e_k})s OR main1.name {op} %({e_k})s)", + event.value, value_key=e_k)) elif event_type == schemas.PerformanceEventType.fetch_failed: - event_from = event_from % f"{events.event_type.REQUEST.table} AS main " + event_from = event_from % f"{events.EventType.REQUEST.table} AS main " if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.REQUEST.column} {op} %({e_k})s", - event.value, value_key=e_k)) + sh.multi_conditions(f"main.{events.EventType.REQUEST.column} {op} %({e_k})s", + event.value, value_key=e_k)) col = performance_event.get_col(event_type) colname = col["column"] event_where.append(f"main.{colname} = FALSE") @@ -804,7 +777,7 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr # colname = col["column"] # tname = "main" # e_k += "_custom" - # full_args = {**full_args, **_multiple_values(event.source, value_key=e_k)} + # full_args = {**full_args, **_ sh.multiple_values(event.source, value_key=e_k)} # event_where.append(f"{tname}.{colname} IS NOT NULL AND {tname}.{colname}>0 AND " + # _multiple_conditions(f"{tname}.{colname} {event.sourceOperator} %({e_k})s", # event.source, value_key=e_k)) @@ -814,7 +787,7 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr schemas.PerformanceEventType.location_avg_cpu_load, schemas.PerformanceEventType.location_avg_memory_usage ]: - event_from = event_from % f"{events.event_type.LOCATION.table} AS main " + event_from = event_from % f"{events.EventType.LOCATION.table} AS main " col = performance_event.get_col(event_type) colname = col["column"] tname = "main" @@ -825,16 +798,16 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr f"{tname}.timestamp <= %(endDate)s"] if not is_any: event_where.append( - _multiple_conditions(f"main.{events.event_type.LOCATION.column} {op} %({e_k})s", - event.value, value_key=e_k)) + sh.multi_conditions(f"main.{events.EventType.LOCATION.column} {op} %({e_k})s", + event.value, value_key=e_k)) e_k += "_custom" - full_args = {**full_args, **_multiple_values(event.source, value_key=e_k)} + full_args = {**full_args, **sh.multi_values(event.source, value_key=e_k)} event_where.append(f"{tname}.{colname} IS NOT NULL AND {tname}.{colname}>0 AND " + - _multiple_conditions(f"{tname}.{colname} {event.sourceOperator} %({e_k})s", - event.source, value_key=e_k)) + sh.multi_conditions(f"{tname}.{colname} {event.sourceOperator.value} %({e_k})s", + event.source, value_key=e_k)) elif event_type == schemas.PerformanceEventType.time_between_events: - event_from = event_from % f"{getattr(events.event_type, event.value[0].type).table} AS main INNER JOIN {getattr(events.event_type, event.value[1].type).table} AS main2 USING(session_id) " + event_from = event_from % f"{getattr(events.EventType, event.value[0].type).table} AS main INNER JOIN {getattr(events.EventType, event.value[1].type).table} AS main2 USING(session_id) " if not isinstance(event.value[0].value, list): event.value[0].value = [event.value[0].value] if not isinstance(event.value[1].value, list): @@ -846,98 +819,99 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr e_k1 = e_k + "_e1" e_k2 = e_k + "_e2" full_args = {**full_args, - **_multiple_values(event.value[0].value, value_key=e_k1), - **_multiple_values(event.value[1].value, value_key=e_k2)} - s_op = __get_sql_operator(event.value[0].operator) + **sh.multi_values(event.value[0].value, value_key=e_k1), + **sh.multi_values(event.value[1].value, value_key=e_k2)} + s_op = sh.get_sql_operator(event.value[0].operator) event_where += ["main2.timestamp >= %(startDate)s", "main2.timestamp <= %(endDate)s"] if event_index > 0 and not or_events: event_where.append("main2.session_id=event_0.session_id") - is_any = _isAny_opreator(event.value[0].operator) + is_any = sh.isAny_opreator(event.value[0].operator) if not is_any: event_where.append( - _multiple_conditions( - f"main.{getattr(events.event_type, event.value[0].type).column} {s_op} %({e_k1})s", + sh.multi_conditions( + f"main.{getattr(events.EventType, event.value[0].type).column} {s_op} %({e_k1})s", event.value[0].value, value_key=e_k1)) - s_op = __get_sql_operator(event.value[1].operator) - is_any = _isAny_opreator(event.value[1].operator) + s_op = sh.get_sql_operator(event.value[1].operator) + is_any = sh.isAny_opreator(event.value[1].operator) if not is_any: event_where.append( - _multiple_conditions( - f"main2.{getattr(events.event_type, event.value[1].type).column} {s_op} %({e_k2})s", + sh.multi_conditions( + f"main2.{getattr(events.EventType, event.value[1].type).column} {s_op} %({e_k2})s", event.value[1].value, value_key=e_k2)) e_k += "_custom" - full_args = {**full_args, **_multiple_values(event.source, value_key=e_k)} + full_args = {**full_args, **sh.multi_values(event.source, value_key=e_k)} event_where.append( - _multiple_conditions(f"main2.timestamp - main.timestamp {event.sourceOperator} %({e_k})s", - event.source, value_key=e_k)) + sh.multi_conditions(f"main2.timestamp - main.timestamp {event.sourceOperator.value} %({e_k})s", + event.source, value_key=e_k)) elif event_type == schemas.EventType.request_details: - event_from = event_from % f"{events.event_type.REQUEST.table} AS main " + event_from = event_from % f"{events.EventType.REQUEST.table} AS main " apply = False for j, f in enumerate(event.filters): - is_any = _isAny_opreator(f.operator) + is_any = sh.isAny_opreator(f.operator) if is_any or len(f.value) == 0: continue f.value = helper.values_for_operator(value=f.value, op=f.operator) - op = __get_sql_operator(f.operator) + op = sh.get_sql_operator(f.operator) e_k_f = e_k + f"_fetch{j}" - full_args = {**full_args, **_multiple_values(f.value, value_key=e_k_f)} + full_args = {**full_args, **sh.multi_values(f.value, value_key=e_k_f)} if f.type == schemas.FetchFilterType._url: event_where.append( - _multiple_conditions(f"main.{events.event_type.REQUEST.column} {op} %({e_k_f})s::text", - f.value, value_key=e_k_f)) + sh.multi_conditions(f"main.{events.EventType.REQUEST.column} {op} %({e_k_f})s::text", + f.value, value_key=e_k_f)) apply = True elif f.type == schemas.FetchFilterType._status_code: event_where.append( - _multiple_conditions(f"main.status_code {f.operator} %({e_k_f})s::integer", f.value, - value_key=e_k_f)) + sh.multi_conditions(f"main.status_code {f.operator.value} %({e_k_f})s::integer", f.value, + value_key=e_k_f)) apply = True elif f.type == schemas.FetchFilterType._method: event_where.append( - _multiple_conditions(f"main.method {op} %({e_k_f})s", f.value, value_key=e_k_f)) + sh.multi_conditions(f"main.method {op} %({e_k_f})s", f.value, value_key=e_k_f)) apply = True elif f.type == schemas.FetchFilterType._duration: event_where.append( - _multiple_conditions(f"main.duration {f.operator} %({e_k_f})s::integer", f.value, - value_key=e_k_f)) + sh.multi_conditions(f"main.duration {f.operator.value} %({e_k_f})s::integer", f.value, + value_key=e_k_f)) apply = True elif f.type == schemas.FetchFilterType._request_body: event_where.append( - _multiple_conditions(f"main.request_body {op} %({e_k_f})s::text", f.value, value_key=e_k_f)) + sh.multi_conditions(f"main.request_body {op} %({e_k_f})s::text", f.value, + value_key=e_k_f)) apply = True elif f.type == schemas.FetchFilterType._response_body: event_where.append( - _multiple_conditions(f"main.response_body {op} %({e_k_f})s::text", f.value, - value_key=e_k_f)) + sh.multi_conditions(f"main.response_body {op} %({e_k_f})s::text", f.value, + value_key=e_k_f)) apply = True else: print(f"undefined FETCH filter: {f.type}") if not apply: continue elif event_type == schemas.EventType.graphql: - event_from = event_from % f"{events.event_type.GRAPHQL.table} AS main " + event_from = event_from % f"{events.EventType.GRAPHQL.table} AS main " for j, f in enumerate(event.filters): - is_any = _isAny_opreator(f.operator) + is_any = sh.isAny_opreator(f.operator) if is_any or len(f.value) == 0: continue f.value = helper.values_for_operator(value=f.value, op=f.operator) - op = __get_sql_operator(f.operator) + op = sh.get_sql_operator(f.operator) e_k_f = e_k + f"_graphql{j}" - full_args = {**full_args, **_multiple_values(f.value, value_key=e_k_f)} + full_args = {**full_args, **sh.multi_values(f.value, value_key=e_k_f)} if f.type == schemas.GraphqlFilterType._name: event_where.append( - _multiple_conditions(f"main.{events.event_type.GRAPHQL.column} {op} %({e_k_f})s", f.value, - value_key=e_k_f)) + sh.multi_conditions(f"main.{events.EventType.GRAPHQL.column} {op} %({e_k_f})s", f.value, + value_key=e_k_f)) elif f.type == schemas.GraphqlFilterType._method: event_where.append( - _multiple_conditions(f"main.method {op} %({e_k_f})s", f.value, value_key=e_k_f)) + sh.multi_conditions(f"main.method {op} %({e_k_f})s", f.value, value_key=e_k_f)) elif f.type == schemas.GraphqlFilterType._request_body: event_where.append( - _multiple_conditions(f"main.request_body {op} %({e_k_f})s", f.value, value_key=e_k_f)) + sh.multi_conditions(f"main.request_body {op} %({e_k_f})s", f.value, value_key=e_k_f)) elif f.type == schemas.GraphqlFilterType._response_body: event_where.append( - _multiple_conditions(f"main.response_body {op} %({e_k_f})s", f.value, value_key=e_k_f)) + sh.multi_conditions(f"main.response_body {op} %({e_k_f})s", f.value, value_key=e_k_f)) else: print(f"undefined GRAPHQL filter: {f.type}") else: @@ -1008,7 +982,7 @@ def search_query_parts(data, error_status, errors_only, favorite_only, issue, pr # b"s.user_os in ('Chrome OS','Fedora','Firefox OS','Linux','Mac OS X','Ubuntu','Windows')") if errors_only: - extra_from += f" INNER JOIN {events.event_type.ERROR.table} AS er USING (session_id) INNER JOIN public.errors AS ser USING (error_id)" + extra_from += f" INNER JOIN {events.EventType.ERROR.table} AS er USING (session_id) INNER JOIN public.errors AS ser USING (error_id)" extra_constraints.append("ser.source = 'js_exception'") extra_constraints.append("ser.project_id = %(project_id)s") # if error_status != schemas.ErrorStatus.all: @@ -1117,39 +1091,6 @@ def search_by_metadata(tenant_id, user_id, m_key, m_value, project_id=None): return results -def search_by_issue(user_id, issue, project_id, start_date, end_date): - constraints = ["s.project_id = %(projectId)s", - "p_issues.context_string = %(issueContextString)s", - "p_issues.type = %(issueType)s"] - if start_date is not None: - constraints.append("start_ts >= %(startDate)s") - if end_date is not None: - constraints.append("start_ts <= %(endDate)s") - with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - f"""SELECT DISTINCT ON(favorite_sessions.session_id, s.session_id) {SESSION_PROJECTION_COLS} - FROM public.sessions AS s - INNER JOIN events_common.issues USING (session_id) - INNER JOIN public.issues AS p_issues USING (issue_id) - LEFT JOIN (SELECT user_id, session_id - FROM public.user_favorite_sessions - WHERE user_id = %(userId)s) AS favorite_sessions - USING (session_id) - WHERE {" AND ".join(constraints)} - ORDER BY s.session_id DESC;""", - { - "issueContextString": issue["contextString"], - "issueType": issue["type"], "userId": user_id, - "projectId": project_id, - "startDate": start_date, - "endDate": end_date - })) - - rows = cur.fetchall() - return helper.list_to_camel_case(rows) - - def get_user_sessions(project_id, user_id, start_date, end_date): with pg_client.PostgresClient() as cur: constraints = ["s.project_id = %(projectId)s", "s.user_id = %(userId)s"] @@ -1256,8 +1197,9 @@ def delete_sessions_by_user_ids(project_id, user_ids): def count_all(): with pg_client.PostgresClient(unlimited_query=True) as cur: - row = cur.execute(query="SELECT COUNT(session_id) AS count FROM public.sessions") - return row.get("count", 0) + cur.execute(query="SELECT COUNT(session_id) AS count FROM public.sessions") + row = cur.fetchone() + return row.get("count", 0) if row else 0 def session_exists(project_id, session_id): @@ -1265,7 +1207,8 @@ def session_exists(project_id, session_id): query = cur.mogrify("""SELECT 1 FROM public.sessions WHERE session_id=%(session_id)s - AND project_id=%(project_id)s""", + AND project_id=%(project_id)s + LIMIT 1;""", {"project_id": project_id, "session_id": session_id}) cur.execute(query) row = cur.fetchone() diff --git a/ee/api/chalicelib/core/sessions_devtool.py b/ee/api/chalicelib/core/sessions_devtool.py index 9435c2e24..9961df360 100644 --- a/ee/api/chalicelib/core/sessions_devtool.py +++ b/ee/api/chalicelib/core/sessions_devtool.py @@ -18,11 +18,13 @@ def __get_devtools_keys(project_id, session_id): ] -def get_urls(session_id, project_id, context: schemas_ee.CurrentContext): +def get_urls(session_id, project_id, context: schemas_ee.CurrentContext, check_existence: bool = True): if not permissions.check(security_scopes=SCOPES, context=context): return [] results = [] for k in __get_devtools_keys(project_id=project_id, session_id=session_id): + if check_existence and not s3.exists(bucket=config("sessions_bucket"), key=k): + continue results.append(s3.client.generate_presigned_url( 'get_object', Params={'Bucket': config("sessions_bucket"), 'Key': k}, diff --git a/ee/api/chalicelib/core/sessions_exp.py b/ee/api/chalicelib/core/sessions_exp.py index 0b77563f9..f60090ed4 100644 --- a/ee/api/chalicelib/core/sessions_exp.py +++ b/ee/api/chalicelib/core/sessions_exp.py @@ -3,9 +3,10 @@ from typing import List, Union import schemas import schemas_ee from chalicelib.core import events, metadata, events_ios, \ - sessions_mobs, issues, projects, errors, resources, assist, performance_event, metrics, sessions_devtool, \ + sessions_mobs, issues, projects, resources, assist, performance_event, metrics, sessions_devtool, \ sessions_notes -from chalicelib.utils import pg_client, helper, metrics_helper, ch_client, exp_ch_helper +from chalicelib.utils import pg_client, helper, metrics_helper, ch_client, exp_ch_helper, errors_helper +from chalicelib.utils import sql_helper as sh SESSION_PROJECTION_COLS_CH = """\ s.project_id, @@ -81,7 +82,7 @@ def get_by_id2_pg(project_id, session_id, context: schemas_ee.CurrentContext, fu (SELECT project_key FROM public.projects WHERE project_id = %(project_id)s LIMIT 1) AS project_key, encode(file_key,'hex') AS file_key {"," if len(extra_query) > 0 else ""}{",".join(extra_query)} - {(",json_build_object(" + ",".join([f"'{m}',p.{m}" for m in metadata._get_column_names()]) + ") AS project_metadata") if group_metadata else ''} + {(",json_build_object(" + ",".join([f"'{m}',p.{m}" for m in metadata.column_names()]) + ") AS project_metadata") if group_metadata else ''} FROM public.sessions AS s {"INNER JOIN public.projects AS p USING (project_id)" if group_metadata else ""} WHERE s.project_id = %(project_id)s AND s.session_id = %(session_id)s;""", @@ -105,16 +106,16 @@ def get_by_id2_pg(project_id, session_id, context: schemas_ee.CurrentContext, fu session_id=session_id) data['mobsUrl'] = sessions_mobs.get_ios(session_id=session_id) else: - data['events'] = events.get_by_sessionId2_pg(project_id=project_id, session_id=session_id, - group_clickrage=True) + data['events'] = events.get_by_session_id(project_id=project_id, session_id=session_id, + group_clickrage=True) all_errors = events.get_errors_by_session_id(session_id=session_id, project_id=project_id) data['stackEvents'] = [e for e in all_errors if e['source'] != "js_exception"] # to keep only the first stack # limit the number of errors to reduce the response-body size - data['errors'] = [errors.format_first_stack_frame(e) for e in all_errors + data['errors'] = [errors_helper.format_first_stack_frame(e) for e in all_errors if e['source'] == "js_exception"][:500] - data['userEvents'] = events.get_customs_by_sessionId2_pg(project_id=project_id, - session_id=session_id) + data['userEvents'] = events.get_customs_by_session_id(project_id=project_id, + session_id=session_id) data['domURL'] = sessions_mobs.get_urls(session_id=session_id, project_id=project_id) data['mobsUrl'] = sessions_mobs.get_urls_depercated(session_id=session_id) data['devtoolsURL'] = sessions_devtool.get_urls(session_id=session_id, project_id=project_id, @@ -201,7 +202,7 @@ def _isUndefined_operator(op: schemas.SearchEventOperator): # This function executes the query and return result def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_id, errors_only=False, - error_status=schemas.ErrorStatus.all, count_only=False, issue=None): + error_status=schemas.ErrorStatus.all, count_only=False, issue=None, ids_only=False): full_args, query_part = search_query_parts_ch(data=data, error_status=error_status, errors_only=errors_only, favorite_only=data.bookmarked, issue=issue, project_id=project_id, user_id=user_id) @@ -236,9 +237,9 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_ elif data.group_by_user: g_sort = "count(full_sessions)" if data.order is None: - data.order = schemas.SortOrderType.desc + data.order = schemas.SortOrderType.desc.value else: - data.order = data.order.upper() + data.order = data.order.value if data.sort is not None and data.sort != 'sessionsCount': sort = helper.key_to_snake_case(data.sort) g_sort = f"{'MIN' if data.order == schemas.SortOrderType.desc else 'MAX'}({sort})" @@ -263,9 +264,17 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_ GROUP BY user_id ) AS users_sessions;""", full_args) + elif ids_only: + main_query = cur.format(f"""SELECT DISTINCT ON(s.session_id) s.session_id + {query_part} + ORDER BY s.session_id desc + LIMIT %(sessions_limit)s OFFSET %(sessions_limit_s)s;""", + full_args) else: if data.order is None: - data.order = schemas.SortOrderType.desc + data.order = schemas.SortOrderType.desc.value + else: + data.order = data.order.value sort = 'session_id' if data.sort is not None and data.sort != "session_id": # sort += " " + data.order + "," + helper.key_to_snake_case(data.sort) @@ -299,8 +308,8 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_ print(data.json()) print("--------------------") raise err - if errors_only: - return helper.list_to_camel_case(cur.fetchall()) + if errors_only or ids_only: + return helper.list_to_camel_case(sessions) if len(sessions) > 0: sessions = sessions[0] @@ -335,18 +344,18 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, density: int, view_type: schemas.MetricTimeseriesViewType, metric_type: schemas.MetricType, - metric_of: schemas.TableMetricOfType, metric_value: List): + metric_of: schemas.MetricOfTable, metric_value: List): step_size = int(metrics_helper.__get_step_size(endTimestamp=data.endDate, startTimestamp=data.startDate, density=density)) extra_event = None - if metric_of == schemas.TableMetricOfType.visited_url: + if metric_of == schemas.MetricOfTable.visited_url: extra_event = f"""SELECT DISTINCT ev.session_id, ev.url_path FROM {exp_ch_helper.get_main_events_table(data.startDate)} AS ev WHERE ev.datetime >= toDateTime(%(startDate)s / 1000) AND ev.datetime <= toDateTime(%(endDate)s / 1000) AND ev.project_id = %(project_id)s - AND ev.event_type = 'LOCATION'""" - elif metric_of == schemas.TableMetricOfType.issues and len(metric_value) > 0: + AND ev.EventType = 'LOCATION'""" + elif metric_of == schemas.MetricOfTable.issues and len(metric_value) > 0: data.filters.append(schemas.SessionSearchFilterSchema(value=metric_value, type=schemas.FilterType.issue, operator=schemas.SearchEventOperator._is)) full_args, query_part = search_query_parts_ch(data=data, error_status=None, errors_only=False, @@ -383,21 +392,21 @@ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, d elif metric_type == schemas.MetricType.table: full_args["limit_s"] = 0 full_args["limit_e"] = 200 - if isinstance(metric_of, schemas.TableMetricOfType): + if isinstance(metric_of, schemas.MetricOfTable): main_col = "user_id" extra_col = "s.user_id" extra_where = "" pre_query = "" - if metric_of == schemas.TableMetricOfType.user_country: + if metric_of == schemas.MetricOfTable.user_country: main_col = "user_country" extra_col = "s.user_country" - elif metric_of == schemas.TableMetricOfType.user_device: + elif metric_of == schemas.MetricOfTable.user_device: main_col = "user_device" extra_col = "s.user_device" - elif metric_of == schemas.TableMetricOfType.user_browser: + elif metric_of == schemas.MetricOfTable.user_browser: main_col = "user_browser" extra_col = "s.user_browser" - elif metric_of == schemas.TableMetricOfType.issues: + elif metric_of == schemas.MetricOfTable.issues: main_col = "issue" extra_col = f"arrayJoin(s.issue_types) AS {main_col}" if len(metric_value) > 0: @@ -407,7 +416,7 @@ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, d extra_where.append(f"{main_col} = %({arg_name})s") full_args[arg_name] = metric_value[i] extra_where = f"WHERE ({' OR '.join(extra_where)})" - elif metric_of == schemas.TableMetricOfType.visited_url: + elif metric_of == schemas.MetricOfTable.visited_url: main_col = "url_path" extra_col = "s.url_path" main_query = cur.format(f"""{pre_query} @@ -470,12 +479,13 @@ def __get_event_type(event_type: Union[schemas.EventType, schemas.PerformanceEve } if event_type not in defs: - raise Exception(f"unsupported event_type:{event_type}") + raise Exception(f"unsupported EventType:{event_type}") return defs.get(event_type) # this function generates the query and return the generated-query with the dict of query arguments -def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, project_id, user_id, extra_event=None): +def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_status, errors_only, favorite_only, issue, + project_id, user_id, extra_event=None): ss_constraints = [] full_args = {"project_id": project_id, "startDate": data.startDate, "endDate": data.endDate, "projectId": project_id, "userId": user_id} @@ -624,7 +634,7 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, ss_constraints.append( _multiple_conditions(f"ms.base_referrer {op} toString(%({f_k})s)", f.value, is_not=is_not, value_key=f_k)) - elif filter_type == events.event_type.METADATA.ui_type: + elif filter_type == events.EventType.METADATA.ui_type: # get metadata list only if you need it if meta_keys is None: meta_keys = metadata.get(project_id=project_id) @@ -777,32 +787,32 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, **_multiple_values(event.value, value_key=e_k), **_multiple_values(event.source, value_key=s_k)} - if event_type == events.event_type.CLICK.ui_type: + if event_type == events.EventType.CLICK.ui_type: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " - _column = events.event_type.CLICK.column + _column = events.EventType.CLICK.column event_where.append(f"main.event_type='{__get_event_type(event_type)}'") events_conditions.append({"type": event_where[-1]}) if not is_any: if is_not: event_where.append(_multiple_conditions(f"sub.{_column} {op} %({e_k})s", event.value, value_key=e_k)) - events_conditions_not.append({"type": f"sub.event_type='{__get_event_type(event_type)}'"}) + events_conditions_not.append({"type": f"sub.EventType='{__get_event_type(event_type)}'"}) events_conditions_not[-1]["condition"] = event_where[-1] else: event_where.append(_multiple_conditions(f"main.{_column} {op} %({e_k})s", event.value, value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.event_type.INPUT.ui_type: + elif event_type == events.EventType.INPUT.ui_type: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " - _column = events.event_type.INPUT.column + _column = events.EventType.INPUT.column event_where.append(f"main.event_type='{__get_event_type(event_type)}'") events_conditions.append({"type": event_where[-1]}) if not is_any: if is_not: event_where.append(_multiple_conditions(f"sub.{_column} {op} %({e_k})s", event.value, value_key=e_k)) - events_conditions_not.append({"type": f"sub.event_type='{__get_event_type(event_type)}'"}) + events_conditions_not.append({"type": f"sub.EventType='{__get_event_type(event_type)}'"}) events_conditions_not[-1]["condition"] = event_where[-1] else: event_where.append(_multiple_conditions(f"main.{_column} {op} %({e_k})s", event.value, @@ -813,7 +823,7 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, value_key=f"custom{i}")) full_args = {**full_args, **_multiple_values(event.source, value_key=f"custom{i}")} - elif event_type == events.event_type.LOCATION.ui_type: + elif event_type == events.EventType.LOCATION.ui_type: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " _column = 'url_path' event_where.append(f"main.event_type='{__get_event_type(event_type)}'") @@ -822,28 +832,28 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, if is_not: event_where.append(_multiple_conditions(f"sub.{_column} {op} %({e_k})s", event.value, value_key=e_k)) - events_conditions_not.append({"type": f"sub.event_type='{__get_event_type(event_type)}'"}) + events_conditions_not.append({"type": f"sub.EventType='{__get_event_type(event_type)}'"}) events_conditions_not[-1]["condition"] = event_where[-1] else: event_where.append(_multiple_conditions(f"main.{_column} {op} %({e_k})s", event.value, value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.event_type.CUSTOM.ui_type: + elif event_type == events.EventType.CUSTOM.ui_type: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " - _column = events.event_type.CUSTOM.column + _column = events.EventType.CUSTOM.column event_where.append(f"main.event_type='{__get_event_type(event_type)}'") events_conditions.append({"type": event_where[-1]}) if not is_any: if is_not: event_where.append(_multiple_conditions(f"sub.{_column} {op} %({e_k})s", event.value, value_key=e_k)) - events_conditions_not.append({"type": f"sub.event_type='{__get_event_type(event_type)}'"}) + events_conditions_not.append({"type": f"sub.EventType='{__get_event_type(event_type)}'"}) events_conditions_not[-1]["condition"] = event_where[-1] else: event_where.append(_multiple_conditions(f"main.{_column} {op} %({e_k})s", event.value, value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.event_type.REQUEST.ui_type: + elif event_type == events.EventType.REQUEST.ui_type: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " _column = 'url_path' event_where.append(f"main.event_type='{__get_event_type(event_type)}'") @@ -852,38 +862,38 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, if is_not: event_where.append(_multiple_conditions(f"sub.{_column} {op} %({e_k})s", event.value, value_key=e_k)) - events_conditions_not.append({"type": f"sub.event_type='{__get_event_type(event_type)}'"}) + events_conditions_not.append({"type": f"sub.EventType='{__get_event_type(event_type)}'"}) events_conditions_not[-1]["condition"] = event_where[-1] else: event_where.append(_multiple_conditions(f"main.{_column} {op} %({e_k})s", event.value, value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] - # elif event_type == events.event_type.GRAPHQL.ui_type: + # elif EventType == events.EventType.GRAPHQL.ui_type: # event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main" # event_where.append(f"main.event_type='GRAPHQL'") # events_conditions.append({"type": event_where[-1]}) # if not is_any: # event_where.append( - # _multiple_conditions(f"main.{events.event_type.GRAPHQL.column} {op} %({e_k})s", event.value, + # _multiple_conditions(f"main.{events.EventType.GRAPHQL.column} {op} %({e_k})s", event.value, # value_key=e_k)) # events_conditions[-1]["condition"] = event_where[-1] - elif event_type == events.event_type.STATEACTION.ui_type: + elif event_type == events.EventType.STATEACTION.ui_type: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " - _column = events.event_type.STATEACTION.column + _column = events.EventType.STATEACTION.column event_where.append(f"main.event_type='{__get_event_type(event_type)}'") events_conditions.append({"type": event_where[-1]}) if not is_any: if is_not: event_where.append(_multiple_conditions(f"sub.{_column} {op} %({e_k})s", event.value, value_key=e_k)) - events_conditions_not.append({"type": f"sub.event_type='{__get_event_type(event_type)}'"}) + events_conditions_not.append({"type": f"sub.EventType='{__get_event_type(event_type)}'"}) events_conditions_not[-1]["condition"] = event_where[-1] else: event_where.append(_multiple_conditions(f"main.{_column} {op} %({e_k})s", event.value, value_key=e_k)) events_conditions[-1]["condition"] = event_where[-1] # TODO: isNot for ERROR - elif event_type == events.event_type.ERROR.ui_type: + elif event_type == events.EventType.ERROR.ui_type: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main" events_extra_join = f"SELECT * FROM {MAIN_EVENTS_TABLE} AS main1 WHERE main1.project_id=%(project_id)s" event_where.append(f"main.event_type='{__get_event_type(event_type)}'") @@ -913,7 +923,7 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, if is_not: event_where.append(_multiple_conditions(f"sub.{_column} {op} %({e_k})s", event.value, value_key=e_k)) - events_conditions_not.append({"type": f"sub.event_type='{__get_event_type(event_type)}'"}) + events_conditions_not.append({"type": f"sub.EventType='{__get_event_type(event_type)}'"}) events_conditions_not[-1]["condition"] = event_where[-1] else: event_where.append(_multiple_conditions(f"main.{_column} {op} %({e_k})s", @@ -925,13 +935,13 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, events_conditions[-1]["condition"].append(event_where[-1]) events_conditions[-1]["condition"] = " AND ".join(events_conditions[-1]["condition"]) - # elif event_type == schemas.PerformanceEventType.fetch_duration: - # event_from = event_from % f"{events.event_type.REQUEST.table} AS main " + # elif EventType == schemas.PerformanceEventType.fetch_duration: + # event_from = event_from % f"{events.EventType.REQUEST.table} AS main " # if not is_any: # event_where.append( # _multiple_conditions(f"main.url_path {op} %({e_k})s", # event.value, value_key=e_k)) - # col = performance_event.get_col(event_type) + # col = performance_event.get_col(EventType) # colname = col["column"] # tname = "main" # e_k += "_custom" @@ -959,7 +969,7 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, full_args = {**full_args, **_multiple_values(event.source, value_key=e_k)} event_where.append(f"isNotNull({tname}.{colname}) AND {tname}.{colname}>0 AND " + - _multiple_conditions(f"{tname}.{colname} {event.sourceOperator} %({e_k})s", + _multiple_conditions(f"{tname}.{colname} {event.sourceOperator.value} %({e_k})s", event.source, value_key=e_k)) events_conditions[-1]["condition"].append(event_where[-1]) events_conditions[-1]["condition"] = " AND ".join(events_conditions[-1]["condition"]) @@ -982,14 +992,14 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, full_args = {**full_args, **_multiple_values(event.source, value_key=e_k)} event_where.append(f"isNotNull({tname}.{colname}) AND {tname}.{colname}>0 AND " + - _multiple_conditions(f"{tname}.{colname} {event.sourceOperator} %({e_k})s", + _multiple_conditions(f"{tname}.{colname} {event.sourceOperator.value} %({e_k})s", event.source, value_key=e_k)) events_conditions[-1]["condition"].append(event_where[-1]) events_conditions[-1]["condition"] = " AND ".join(events_conditions[-1]["condition"]) # TODO: no isNot for TimeBetweenEvents elif event_type == schemas.PerformanceEventType.time_between_events: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " - # event_from = event_from % f"{getattr(events.event_type, event.value[0].type).table} AS main INNER JOIN {getattr(events.event_type, event.value[1].type).table} AS main2 USING(session_id) " + # event_from = event_from % f"{getattr(events.EventType, event.value[0].type).table} AS main INNER JOIN {getattr(events.EventType, event.value[1].type).table} AS main2 USING(session_id) " event_where.append(f"main.event_type='{__get_event_type(event.value[0].type)}'") events_conditions.append({"type": event_where[-1]}) event_where.append(f"main.event_type='{__get_event_type(event.value[0].type)}'") @@ -1016,7 +1026,7 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, if not is_any: event_where.append( _multiple_conditions( - f"main.{getattr(events.event_type, event.value[0].type).column} {s_op} %({e_k1})s", + f"main.{getattr(events.EventType, event.value[0].type).column} {s_op} %({e_k1})s", event.value[0].value, value_key=e_k1)) events_conditions[-2]["condition"] = event_where[-1] s_op = __get_sql_operator(event.value[1].operator) @@ -1024,7 +1034,7 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, if not is_any: event_where.append( _multiple_conditions( - f"main.{getattr(events.event_type, event.value[1].type).column} {s_op} %({e_k2})s", + f"main.{getattr(events.EventType, event.value[1].type).column} {s_op} %({e_k2})s", event.value[1].value, value_key=e_k2)) events_conditions[-1]["condition"] = event_where[-1] @@ -1034,8 +1044,8 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, # _multiple_conditions(f"main2.timestamp - main.timestamp {event.sourceOperator} %({e_k})s", # event.source, value_key=e_k)) # events_conditions[-2]["time"] = f"(?t{event.sourceOperator} %({e_k})s)" - events_conditions[-2]["time"] = _multiple_conditions(f"?t{event.sourceOperator}%({e_k})s", event.source, - value_key=e_k) + events_conditions[-2]["time"] = _multiple_conditions(f"?t{event.sourceOperator.value}%({e_k})s", + event.source, value_key=e_k) event_index += 1 # TODO: no isNot for RequestDetails elif event_type == schemas.EventType.request_details: @@ -1060,7 +1070,7 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, apply = True elif f.type == schemas.FetchFilterType._status_code: event_where.append( - _multiple_conditions(f"main.status {f.operator} %({e_k_f})s", f.value, + _multiple_conditions(f"main.status {f.operator.value} %({e_k_f})s", f.value, value_key=e_k_f)) events_conditions[-1]["condition"].append(event_where[-1]) apply = True @@ -1071,7 +1081,8 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, apply = True elif f.type == schemas.FetchFilterType._duration: event_where.append( - _multiple_conditions(f"main.duration {f.operator} %({e_k_f})s", f.value, value_key=e_k_f)) + _multiple_conditions(f"main.duration {f.operator.value} %({e_k_f})s", f.value, + value_key=e_k_f)) events_conditions[-1]["condition"].append(event_where[-1]) apply = True elif f.type == schemas.FetchFilterType._request_body: @@ -1106,7 +1117,7 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, full_args = {**full_args, **_multiple_values(f.value, value_key=e_k_f)} if f.type == schemas.GraphqlFilterType._name: event_where.append( - _multiple_conditions(f"main.{events.event_type.GRAPHQL.column} {op} %({e_k_f})s", f.value, + _multiple_conditions(f"main.{events.EventType.GRAPHQL.column} {op} %({e_k_f})s", f.value, value_key=e_k_f)) events_conditions[-1]["condition"].append(event_where[-1]) elif f.type == schemas.GraphqlFilterType._method: @@ -1165,6 +1176,9 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, ) {"" if or_events else (f"AS event_{event_index} " + ("ON(TRUE)" if event_index > 0 else ""))}\ """) event_index += 1 + # limit THEN-events to 7 in CH because sequenceMatch cannot take more arguments + if event_index == 7 and data.events_order == schemas.SearchEventOrder._then: + break if event_index < 2: data.events_order = schemas.SearchEventOrder._or @@ -1287,7 +1301,7 @@ def search_query_parts_ch(data, error_status, errors_only, favorite_only, issue, # b"s.user_os in ('Chrome OS','Fedora','Firefox OS','Linux','Mac OS X','Ubuntu','Windows')") # if errors_only: - # extra_from += f" INNER JOIN {events.event_type.ERROR.table} AS er USING (session_id) INNER JOIN public.errors AS ser USING (error_id)" + # extra_from += f" INNER JOIN {events.EventType.ERROR.table} AS er USING (session_id) INNER JOIN public.errors AS ser USING (error_id)" # extra_constraints.append("ser.source = 'js_exception'") # extra_constraints.append("ser.project_id = %(project_id)s") # if error_status != schemas.ErrorStatus.all: @@ -1410,39 +1424,6 @@ def search_by_metadata(tenant_id, user_id, m_key, m_value, project_id=None): return results -def search_by_issue(user_id, issue, project_id, start_date, end_date): - constraints = ["s.project_id = %(projectId)s", - "p_issues.context_string = %(issueContextString)s", - "p_issues.type = %(issueType)s"] - if start_date is not None: - constraints.append("start_ts >= %(startDate)s") - if end_date is not None: - constraints.append("start_ts <= %(endDate)s") - with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - f"""SELECT DISTINCT ON(favorite_sessions.session_id, s.session_id) {SESSION_PROJECTION_COLS} - FROM public.sessions AS s - INNER JOIN events_common.issues USING (session_id) - INNER JOIN public.issues AS p_issues USING (issue_id) - LEFT JOIN (SELECT user_id, session_id - FROM public.user_favorite_sessions - WHERE user_id = %(userId)s) AS favorite_sessions - USING (session_id) - WHERE {" AND ".join(constraints)} - ORDER BY s.session_id DESC;""", - { - "issueContextString": issue["contextString"], - "issueType": issue["type"], "userId": user_id, - "projectId": project_id, - "startDate": start_date, - "endDate": end_date - })) - - rows = cur.fetchall() - return helper.list_to_camel_case(rows) - - def get_user_sessions(project_id, user_id, start_date, end_date): with pg_client.PostgresClient() as cur: constraints = ["s.project_id = %(projectId)s", "s.user_id = %(userId)s"] @@ -1548,17 +1529,18 @@ def delete_sessions_by_user_ids(project_id, user_ids): def count_all(): - with pg_client.PostgresClient(unlimited_query=True) as cur: - row = cur.execute(query="SELECT COUNT(session_id) AS count FROM public.sessions") + with ch_client.ClickHouseClient() as cur: + row = cur.execute(query=f"SELECT COUNT(session_id) AS count FROM {exp_ch_helper.get_main_sessions_table()}") return row.get("count", 0) def session_exists(project_id, session_id): with ch_client.ClickHouseClient() as cur: - query = cur.format("""SELECT 1 - FROM public.sessions - WHERE session_id=%(session_id)s - AND project_id=%(project_id)s""", + query = cur.format(f"""SELECT 1 + FROM {exp_ch_helper.get_main_sessions_table()} + WHERE session_id=%(session_id)s + AND project_id=%(project_id)s + LIMIT 1""", {"project_id": project_id, "session_id": session_id}) row = cur.execute(query) return row is not None diff --git a/ee/api/chalicelib/core/sessions_favorite.py b/ee/api/chalicelib/core/sessions_favorite.py index 7af995bad..d8ae4e1f7 100644 --- a/ee/api/chalicelib/core/sessions_favorite.py +++ b/ee/api/chalicelib/core/sessions_favorite.py @@ -62,16 +62,16 @@ def favorite_session(context: schemas_ee.CurrentContext, project_id, session_id) return add_favorite_session(context=context, project_id=project_id, session_id=session_id) -def favorite_session_exists(user_id, session_id): +def favorite_session_exists(session_id, user_id=None): with pg_client.PostgresClient() as cur: cur.execute( cur.mogrify( - """SELECT session_id + f"""SELECT session_id FROM public.user_favorite_sessions WHERE - user_id = %(userId)s - AND session_id = %(sessionId)s""", - {"userId": user_id, "sessionId": session_id}) + session_id = %(session_id)s + {'AND user_id = %(userId)s' if user_id else ''};""", + {"userId": user_id, "session_id": session_id}) ) r = cur.fetchone() return r is not None diff --git a/ee/api/chalicelib/core/sessions_insights.py b/ee/api/chalicelib/core/sessions_insights.py new file mode 100644 index 000000000..532e47e19 --- /dev/null +++ b/ee/api/chalicelib/core/sessions_insights.py @@ -0,0 +1,478 @@ +from typing import Optional + +import schemas +import schemas_ee +from chalicelib.core import metrics +from chalicelib.utils import ch_client + + +def _table_slice(table, index): + col = list() + for row in table: + col.append(row[index]) + return col + + +def _table_where(table, index, value): + new_table = list() + for row in table: + if row[index] == value: + new_table.append(row) + return new_table + + +def _sum_table_index(table, index): + # print(f'index {index}') + s = 0 + count = 0 + for row in table: + v = row[index] + if v is None: + continue + # print(v) + s += v + count += 1 + return s + + +def _mean_table_index(table, index): + s = _sum_table_index(table, index) + c = len(table) + return s / c + + +def _sort_table_index(table, index, reverse=False): + return sorted(table, key=lambda k: k[index], reverse=reverse) + + +def _select_rec(l, selector): + # print('selector:', selector) + # print('list:', l) + if len(selector) == 1: + return l[selector[0]] + else: + s = selector[0] + L = l[s] + type_ = type(s) + if type_ == slice: + return [_select_rec(l_, selector[1:]) for l_ in L] + elif type_ == int: + return [_select_rec(L, selector[1:])] + + +def __get_two_values(response, time_index='hh', name_index='name'): + columns = list(response[0].keys()) + name_index_val = columns.index(name_index) + time_index_value = columns.index(time_index) + + table = [list(r.values()) for r in response] + table_hh1 = list() + table_hh2 = list() + hh_vals = list() + names_hh1 = list() + names_hh2 = list() + for e in table: + if e[time_index_value] not in hh_vals and len(hh_vals) == 2: + break + elif e[time_index_value] not in hh_vals: + hh_vals.append(e[time_index_value]) + + if len(hh_vals) == 1: + table_hh1.append(e) + if e[name_index_val] not in names_hh1: + names_hh1.append(e[name_index_val]) + elif len(hh_vals) == 2: + table_hh2.append(e) + if e[name_index_val] not in names_hh2: + names_hh2.append(e[name_index_val]) + return table_hh1, table_hh2, columns, names_hh1, names_hh2 + + +def query_requests_by_period(project_id, start_time, end_time, filters: Optional[schemas.SessionsSearchPayloadSchema]): + params = { + "project_id": project_id, "startTimestamp": start_time, "endTimestamp": end_time, + "step_size": metrics.__get_step_size(endTimestamp=end_time, startTimestamp=start_time, density=3) + } + params, sub_query = __filter_subquery(project_id=project_id, filters=filters, params=params) + conditions = ["event_type = 'REQUEST'"] + query = f"""WITH toUInt32(toStartOfInterval(toDateTime(%(startTimestamp)s/1000), INTERVAL %(step_size)s second)) AS start, + toUInt32(toStartOfInterval(toDateTime(%(endTimestamp)s/1000), INTERVAL %(step_size)s second)) AS end + SELECT T1.hh, countIf(T2.session_id != 0) as sessions, avg(T2.success) as success_rate, T2.url_host as names, + T2.url_path as source, avg(T2.duration) as avg_duration + FROM (SELECT arrayJoin(arrayMap(x -> toDateTime(x), range(start, end, %(step_size)s))) as hh) AS T1 + LEFT JOIN (SELECT session_id, url_host, url_path, success, message, duration, toStartOfInterval(datetime, INTERVAL %(step_size)s second) as dtime + FROM experimental.events + {sub_query} + WHERE project_id = {project_id} + AND {" AND ".join(conditions)}) AS T2 ON T2.dtime = T1.hh + GROUP BY T1.hh, T2.url_host, T2.url_path + ORDER BY T1.hh DESC;""" + with ch_client.ClickHouseClient() as conn: + query = conn.format(query=query, params=params) + # print("--------") + # print(query) + # print("--------") + res = conn.execute(query=query) + if res is None or sum([r.get("sessions") for r in res]) == 0: + return [] + + table_hh1, table_hh2, columns, this_period_hosts, last_period_hosts = __get_two_values(res, time_index='hh', + name_index='source') + test = [k[4] for k in table_hh1] + # print(f'length {len(test)}, uniques {len(set(test))}') + del res + + new_hosts = [x for x in this_period_hosts if x not in last_period_hosts] + common_names = [x for x in this_period_hosts if x not in new_hosts] + + source_idx = columns.index('source') + duration_idx = columns.index('avg_duration') + # success_idx = columns.index('success_rate') + # delta_duration = dict() + # delta_success = dict() + new_duration_values = dict() + duration_values = dict() + for n in common_names: + d1_tmp = _table_where(table_hh1, source_idx, n) + d2_tmp = _table_where(table_hh2, source_idx, n) + old_duration = _mean_table_index(d2_tmp, duration_idx) + new_duration = _mean_table_index(d1_tmp, duration_idx) + if old_duration == 0: + continue + duration_values[n] = new_duration, old_duration, (new_duration - old_duration) / old_duration + # delta_duration[n] = (_mean_table_index(d1_tmp, duration_idx) - _duration1) / _duration1 + # delta_success[n] = _mean_table_index(d1_tmp, success_idx) - _mean_table_index(d2_tmp, success_idx) + for n in new_hosts: + d1_tmp = _table_where(table_hh1, source_idx, n) + new_duration_values[n] = _mean_table_index(d1_tmp, duration_idx) + + # names_idx = columns.index('names') + total = _sum_table_index(table_hh1, duration_idx) + d1_tmp = _sort_table_index(table_hh1, duration_idx, reverse=True) + _tmp = _table_slice(d1_tmp, duration_idx) + _tmp2 = _table_slice(d1_tmp, source_idx) + + increase = sorted(duration_values.items(), key=lambda k: k[1][-1], reverse=True) + ratio = sorted(zip(_tmp2, _tmp), key=lambda k: k[1], reverse=True) + # names_ = set([k[0] for k in increase[:3]+ratio[:3]]+new_hosts[:3]) + names_ = set([k[0] for k in increase[:3] + ratio[:3]]) # we took out new hosts since they dont give much info + + results = list() + for n in names_: + if n is None: + continue + data_ = {'category': schemas_ee.InsightCategories.network, 'name': n, + 'value': None, 'oldValue': None, 'ratio': None, 'change': None, 'isNew': True} + for n_, v in ratio: + if n == n_: + if n in new_hosts: + data_['value'] = new_duration_values[n] + data_['ratio'] = 100 * v / total + break + for n_, v in increase: + if n == n_: + data_['value'] = v[0] + data_['oldValue'] = v[1] + data_['change'] = 100 * v[2] + data_['isNew'] = False + break + results.append(data_) + return results + + +def __filter_subquery(project_id: int, filters: Optional[schemas.SessionsSearchPayloadSchema], params: dict): + sub_query = "" + if filters and (len(filters.events) > 0 or len(filters.filters)) > 0: + qp_params, sub_query = sessions_exp.search_query_parts_ch(data=filters, project_id=project_id, + error_status=None, + errors_only=True, favorite_only=None, + issue=None, user_id=None) + params = {**params, **qp_params} + sub_query = f"INNER JOIN {sub_query} USING(session_id)" + return params, sub_query + + +def query_most_errors_by_period(project_id, start_time, end_time, + filters: Optional[schemas.SessionsSearchPayloadSchema]): + params = { + "project_id": project_id, "startTimestamp": start_time, "endTimestamp": end_time, + "step_size": metrics.__get_step_size(endTimestamp=end_time, startTimestamp=start_time, density=3) + } + params, sub_query = __filter_subquery(project_id=project_id, filters=filters, params=params) + conditions = ["event_type = 'ERROR'"] + query = f"""WITH toUInt32(toStartOfInterval(toDateTime(%(startTimestamp)s/1000), INTERVAL %(step_size)s second)) AS start, + toUInt32(toStartOfInterval(toDateTime(%(endTimestamp)s/1000), INTERVAL %(step_size)s second)) AS end + SELECT T1.hh, countIf(T2.session_id != 0) as sessions, T2.name as names, + groupUniqArray(T2.source) as sources + FROM (SELECT arrayJoin(arrayMap(x -> toDateTime(x), range(start, end, %(step_size)s))) as hh) AS T1 + LEFT JOIN (SELECT session_id, name, source, message, toStartOfInterval(datetime, INTERVAL %(step_size)s second) as dtime + FROM experimental.events + {sub_query} + WHERE project_id = {project_id} + AND datetime >= toDateTime(%(startTimestamp)s/1000) + AND datetime < toDateTime(%(endTimestamp)s/1000) + AND {" AND ".join(conditions)}) AS T2 ON T2.dtime = T1.hh + GROUP BY T1.hh, T2.name + ORDER BY T1.hh DESC;""" + + with ch_client.ClickHouseClient() as conn: + query = conn.format(query=query, params=params) + # print("--------") + # print(query) + # print("--------") + res = conn.execute(query=query) + if res is None or sum([r.get("sessions") for r in res]) == 0: + return [] + + table_hh1, table_hh2, columns, this_period_errors, last_period_errors = __get_two_values(res, time_index='hh', + name_index='names') + del res + # print(table_hh1) + # print('\n') + # print(table_hh2) + # print('\n') + new_errors = [x for x in this_period_errors if x not in last_period_errors] + common_errors = [x for x in this_period_errors if x not in new_errors] + + sessions_idx = columns.index('sessions') + names_idx = columns.index('names') + + print(_table_where(table_hh1, names_idx, this_period_errors[0])) + + percentage_errors = dict() + total = _sum_table_index(table_hh1, sessions_idx) + # error_increase = dict() + new_error_values = dict() + error_values = dict() + for n in this_period_errors: + if n is None: + continue + percentage_errors[n] = _sum_table_index(_table_where(table_hh1, names_idx, n), sessions_idx) + new_error_values[n] = _sum_table_index(_table_where(table_hh1, names_idx, n), sessions_idx) + for n in common_errors: + if n is None: + continue + sum_old_errors = _sum_table_index(_table_where(table_hh2, names_idx, n), sessions_idx) + if sum_old_errors == 0: + continue + sum_new_errors = _sum_table_index(_table_where(table_hh1, names_idx, n), sessions_idx) + # error_increase[n] = (new_errors - old_errors) / old_errors + error_values[n] = sum_new_errors, sum_old_errors, (sum_new_errors - sum_old_errors) / sum_old_errors + ratio = sorted(percentage_errors.items(), key=lambda k: k[1], reverse=True) + increase = sorted(error_values.items(), key=lambda k: k[1][-1], reverse=True) + names_ = set([k[0] for k in increase[:3] + ratio[:3]] + new_errors[:3]) + + results = list() + for n in names_: + if n is None: + continue + data_ = {'category': schemas_ee.InsightCategories.errors, 'name': n, + 'value': None, 'oldValue': None, 'ratio': None, 'change': None, 'isNew': True} + for n_, v in ratio: + if n == n_: + if n in new_errors: + data_['value'] = new_error_values[n] + data_['ratio'] = 100 * v / total + break + for n_, v in increase: + if n == n_: + data_['value'] = v[0] + data_['oldValue'] = v[1] + data_['change'] = 100 * v[2] + data_['isNew'] = False + break + results.append(data_) + return results + + +def query_cpu_memory_by_period(project_id, start_time, end_time, + filters: Optional[schemas.SessionsSearchPayloadSchema]): + params = { + "project_id": project_id, "startTimestamp": start_time, "endTimestamp": end_time, + "step_size": metrics.__get_step_size(endTimestamp=end_time, startTimestamp=start_time, density=3) + } + params, sub_query = __filter_subquery(project_id=project_id, filters=filters, params=params) + conditions = ["event_type = 'PERFORMANCE'"] + query = f"""WITH toUInt32(toStartOfInterval(toDateTime(%(startTimestamp)s/1000), INTERVAL %(step_size)s second)) AS start, + toUInt32(toStartOfInterval(toDateTime(%(endTimestamp)s/1000), INTERVAL %(step_size)s second)) AS end + SELECT T1.hh, countIf(T2.session_id != 0) as sessions, avg(T2.avg_cpu) as cpu_used, + avg(T2.avg_used_js_heap_size) as memory_used, T2.url_host as names, groupUniqArray(T2.url_path) as sources + FROM (SELECT arrayJoin(arrayMap(x -> toDateTime(x), range(start, end, %(step_size)s))) as hh) AS T1 + LEFT JOIN (SELECT session_id, url_host, url_path, avg_used_js_heap_size, avg_cpu, toStartOfInterval(datetime, INTERVAL %(step_size)s second) as dtime + FROM experimental.events + {sub_query} + WHERE project_id = {project_id} + AND {" AND ".join(conditions)}) AS T2 ON T2.dtime = T1.hh + GROUP BY T1.hh, T2.url_host + ORDER BY T1.hh DESC;""" + with ch_client.ClickHouseClient() as conn: + query = conn.format(query=query, params=params) + # print("--------") + # print(query) + # print("--------") + res = conn.execute(query=query) + if res is None or sum([r.get("sessions") for r in res]) == 0: + return [] + + table_hh1, table_hh2, columns, this_period_resources, last_period_resources = __get_two_values(res, time_index='hh', + name_index='names') + + print(f'TB1\n{table_hh1}') + print(f'TB2\n{table_hh2}') + del res + + memory_idx = columns.index('memory_used') + cpu_idx = columns.index('cpu_used') + + mem_newvalue = _mean_table_index(table_hh1, memory_idx) + mem_oldvalue = _mean_table_index(table_hh2, memory_idx) + cpu_newvalue = _mean_table_index(table_hh2, cpu_idx) + cpu_oldvalue = _mean_table_index(table_hh2, cpu_idx) + + cpu_ratio = 0 + mem_ratio = 0 + if mem_newvalue == 0: + mem_newvalue = None + mem_ratio = None + if mem_oldvalue == 0: + mem_oldvalue = None + mem_ratio = None + if cpu_newvalue == 0: + cpu_newvalue = None + cpu_ratio = None + if cpu_oldvalue == 0: + cpu_oldvalue = None + cpu_ratio = None + + output = list() + if cpu_oldvalue is not None or cpu_newvalue is not None: + output.append({'category': schemas_ee.InsightCategories.resources, + 'name': 'cpu', + 'value': cpu_newvalue, + 'oldValue': cpu_oldvalue, + 'change': 100 * ( + cpu_newvalue - cpu_oldvalue) / cpu_oldvalue if cpu_ratio is not None else cpu_ratio, + 'isNew': True if cpu_newvalue is not None and cpu_oldvalue is None else False}) + if mem_oldvalue is not None or mem_newvalue is not None: + output.append({'category': schemas_ee.InsightCategories.resources, + 'name': 'memory', + 'value': mem_newvalue, + 'oldValue': mem_oldvalue, + 'change': 100 * ( + mem_newvalue - mem_oldvalue) / mem_oldvalue if mem_ratio is not None else mem_ratio, + 'isNew': True if mem_newvalue is not None and mem_oldvalue is None else False}) + return output + + +from chalicelib.core import sessions_exp + + +def query_click_rage_by_period(project_id, start_time, end_time, + filters: Optional[schemas.SessionsSearchPayloadSchema]): + params = { + "project_id": project_id, "startTimestamp": start_time, "endTimestamp": end_time, + "step_size": metrics.__get_step_size(endTimestamp=end_time, startTimestamp=start_time, density=3)} + params, sub_query = __filter_subquery(project_id=project_id, filters=filters, params=params) + conditions = ["issue_type = 'click_rage'", "event_type = 'ISSUE'"] + query = f"""WITH toUInt32(toStartOfInterval(toDateTime(%(startTimestamp)s/1000), INTERVAL %(step_size)s second)) AS start, + toUInt32(toStartOfInterval(toDateTime(%(endTimestamp)s/1000), INTERVAL %(step_size)s second)) AS end + SELECT T1.hh, countIf(T2.session_id != 0) as sessions, groupUniqArray(T2.url_host) as names, T2.url_path as sources + FROM (SELECT arrayJoin(arrayMap(x -> toDateTime(x), range(start, end, %(step_size)s))) as hh) AS T1 + LEFT JOIN (SELECT session_id, url_host, url_path, toStartOfInterval(datetime, INTERVAL %(step_size)s second ) as dtime + FROM experimental.events + {sub_query} + WHERE project_id = %(project_id)s + AND datetime >= toDateTime(%(startTimestamp)s/1000) + AND datetime < toDateTime(%(endTimestamp)s/1000) + AND {" AND ".join(conditions)}) AS T2 ON T2.dtime = T1.hh + GROUP BY T1.hh, T2.url_path + ORDER BY T1.hh DESC;""" + with ch_client.ClickHouseClient() as conn: + query = conn.format(query=query, params=params) + # print("--------") + # print(query) + # print("--------") + res = conn.execute(query=query) + if res is None or sum([r.get("sessions") for r in res]) == 0: + return [] + + table_hh1, table_hh2, columns, this_period_rage, last_period_rage = __get_two_values(res, time_index='hh', + name_index='sources') + del res + + new_names = [x for x in this_period_rage if x not in last_period_rage] + common_names = [x for x in this_period_rage if x not in new_names] + + sessions_idx = columns.index('sessions') + names_idx = columns.index('sources') + + # raged_increment = dict() + raged_values = dict() + new_raged_values = dict() + # TODO verify line (188) _tmp = table_hh2[:, sessions_idx][n].sum() + for n in common_names: + if n is None: + continue + _oldvalue = _sum_table_index(_table_where(table_hh2, names_idx, n), sessions_idx) + _newvalue = _sum_table_index(_table_where(table_hh1, names_idx, n), sessions_idx) + # raged_increment[n] = (_newvalue - _oldvalue) / _oldvalue + raged_values[n] = _newvalue, _oldvalue, (_newvalue - _oldvalue) / _oldvalue + + for n in new_names: + if n is None: + continue + _newvalue = _sum_table_index(_table_where(table_hh1, names_idx, n), sessions_idx) + new_raged_values[n] = _newvalue + + total = _sum_table_index(table_hh1, sessions_idx) + names, ratio = _table_slice(table_hh1, names_idx), _table_slice(table_hh1, sessions_idx) + ratio = sorted(zip(names, ratio), key=lambda k: k[1], reverse=True) + increase = sorted(raged_values.items(), key=lambda k: k[1][-1], reverse=True) + names_ = set([k[0] for k in increase[:3] + ratio[:3]] + new_names[:3]) + + results = list() + for n in names_: + if n is None: + continue + data_ = {'category': schemas_ee.InsightCategories.rage, 'name': n, + 'value': None, 'oldValue': None, 'ratio': None, 'change': None, 'isNew': True} + for n_, v in ratio: + if n == n_: + if n in new_names: + data_['value'] = new_raged_values[n] + data_['ratio'] = 100 * v / total + break + for n_, v in increase: + if n == n_: + data_['value'] = v[0] + data_['oldValue'] = v[1] + data_['change'] = 100 * v[2] + data_['isNew'] = False + break + results.append(data_) + return results + + +def fetch_selected(project_id, data: schemas_ee.GetInsightsSchema): + output = list() + if data.metricValue is None or len(data.metricValue) == 0: + data.metricValue = [] + for v in schemas_ee.InsightCategories: + data.metricValue.append(v) + filters = None + if len(data.series) > 0: + filters = data.series[0].filter + + if schemas_ee.InsightCategories.errors in data.metricValue: + output += query_most_errors_by_period(project_id=project_id, start_time=data.startTimestamp, + end_time=data.endTimestamp, filters=filters) + if schemas_ee.InsightCategories.network in data.metricValue: + output += query_requests_by_period(project_id=project_id, start_time=data.startTimestamp, + end_time=data.endTimestamp, filters=filters) + if schemas_ee.InsightCategories.rage in data.metricValue: + output += query_click_rage_by_period(project_id=project_id, start_time=data.startTimestamp, + end_time=data.endTimestamp, filters=filters) + if schemas_ee.InsightCategories.resources in data.metricValue: + output += query_cpu_memory_by_period(project_id=project_id, start_time=data.startTimestamp, + end_time=data.endTimestamp, filters=filters) + return output diff --git a/ee/api/chalicelib/core/sessions_notes.py b/ee/api/chalicelib/core/sessions_notes.py index de1f83854..0e58d276b 100644 --- a/ee/api/chalicelib/core/sessions_notes.py +++ b/ee/api/chalicelib/core/sessions_notes.py @@ -3,16 +3,17 @@ from urllib.parse import urljoin from decouple import config import schemas -from chalicelib.core import sessions +from chalicelib.core.collaboration_msteams import MSTeams from chalicelib.core.collaboration_slack import Slack from chalicelib.utils import pg_client, helper +from chalicelib.utils import sql_helper as sh from chalicelib.utils.TimeUTC import TimeUTC def get_note(tenant_id, project_id, user_id, note_id, share=None): with pg_client.PostgresClient() as cur: - query = cur.mogrify(f"""SELECT sessions_notes.*, users.name AS creator_name - {",(SELECT name FROM users WHERE tenant_id=%(tenant_id)s AND user_id=%(share)s) AS share_name" if share else ""} + query = cur.mogrify(f"""SELECT sessions_notes.*, users.name AS user_name + {",(SELECT name FROM users WHERE tenant_id=%(tenant_id)s AND user_id=%(share)s AND deleted_at ISNULL) AS share_name" if share else ""} FROM sessions_notes INNER JOIN users USING (user_id) WHERE sessions_notes.project_id = %(project_id)s AND sessions_notes.note_id = %(note_id)s @@ -32,9 +33,8 @@ def get_note(tenant_id, project_id, user_id, note_id, share=None): def get_session_notes(tenant_id, project_id, session_id, user_id): with pg_client.PostgresClient() as cur: - query = cur.mogrify(f"""SELECT sessions_notes.* - FROM sessions_notes - INNER JOIN users USING (user_id) + query = cur.mogrify(f"""SELECT sessions_notes.*, users.name AS user_name + FROM sessions_notes INNER JOIN users USING (user_id) WHERE sessions_notes.project_id = %(project_id)s AND sessions_notes.deleted_at IS NULL AND sessions_notes.session_id = %(session_id)s @@ -59,8 +59,8 @@ def get_all_notes_by_project_id(tenant_id, project_id, user_id, data: schemas.Se if data.tags and len(data.tags) > 0: k = "tag_value" conditions.append( - sessions._multiple_conditions(f"%({k})s = sessions_notes.tag", data.tags, value_key=k)) - extra_params = sessions._multiple_values(data.tags, value_key=k) + sh.multi_conditions(f"%({k})s = sessions_notes.tag", data.tags, value_key=k)) + extra_params = sh.multi_values(data.tags, value_key=k) if data.shared_only: conditions.append("sessions_notes.is_public AND users.tenant_id = %(tenant_id)s") elif data.mine_only: @@ -68,11 +68,10 @@ def get_all_notes_by_project_id(tenant_id, project_id, user_id, data: schemas.Se else: conditions.append( "(sessions_notes.user_id = %(user_id)s OR sessions_notes.is_public AND users.tenant_id = %(tenant_id)s)") - query = cur.mogrify(f"""SELECT sessions_notes.* - FROM sessions_notes - INNER JOIN users USING (user_id) + query = cur.mogrify(f"""SELECT sessions_notes.*, users.name AS user_name + FROM sessions_notes INNER JOIN users USING (user_id) WHERE {" AND ".join(conditions)} - ORDER BY created_at {data.order} + ORDER BY created_at {data.order.value} LIMIT {data.limit} OFFSET {data.limit * (data.page - 1)};""", {"project_id": project_id, "user_id": user_id, "tenant_id": tenant_id, **extra_params}) @@ -88,8 +87,9 @@ def create(tenant_id, user_id, project_id, session_id, data: schemas.SessionNote with pg_client.PostgresClient() as cur: query = cur.mogrify(f"""INSERT INTO public.sessions_notes (message, user_id, tag, session_id, project_id, timestamp, is_public) VALUES (%(message)s, %(user_id)s, %(tag)s, %(session_id)s, %(project_id)s, %(timestamp)s, %(is_public)s) - RETURNING *;""", - {"user_id": user_id, "project_id": project_id, "session_id": session_id, **data.dict()}) + RETURNING *,(SELECT name FROM users WHERE users.user_id=%(user_id)s AND users.tenant_id=%(tenant_id)s) AS user_name;""", + {"user_id": user_id, "project_id": project_id, "session_id": session_id, **data.dict(), + "tenant_id": tenant_id}) cur.execute(query) result = helper.dict_to_camel_case(cur.fetchone()) if result: @@ -117,8 +117,9 @@ def edit(tenant_id, user_id, project_id, note_id, data: schemas.SessionUpdateNot AND user_id = %(user_id)s AND note_id = %(note_id)s AND deleted_at ISNULL - RETURNING *;""", - {"project_id": project_id, "user_id": user_id, "note_id": note_id, **data.dict()}) + RETURNING *,(SELECT name FROM users WHERE users.user_id=%(user_id)s AND users.tenant_id=%(tenant_id)s) AS user_name;""", + {"project_id": project_id, "user_id": user_id, "note_id": note_id, **data.dict(), + "tenant_id": tenant_id}) ) row = helper.dict_to_camel_case(cur.fetchone()) if row: @@ -159,7 +160,7 @@ def share_to_slack(tenant_id, user_id, project_id, note_id, webhook_id): blocks.append({"type": "context", "elements": [{"type": "plain_text", "text": f"Tag: *{note['tag']}*"}]}) - bottom = f"Created by {note['creatorName'].capitalize()}" + bottom = f"Created by {note['userName'].capitalize()}" if user_id != note["userId"]: bottom += f"\nSent by {note['shareName']}: " blocks.append({"type": "context", @@ -170,3 +171,60 @@ def share_to_slack(tenant_id, user_id, project_id, note_id, webhook_id): webhook_id=webhook_id, body={"blocks": blocks} ) + + +def share_to_msteams(tenant_id, user_id, project_id, note_id, webhook_id): + note = get_note(tenant_id=tenant_id, project_id=project_id, user_id=user_id, note_id=note_id, share=user_id) + if note is None: + return {"errors": ["Note not found"]} + session_url = urljoin(config('SITE_URL'), f"{note['projectId']}/session/{note['sessionId']}?note={note['noteId']}") + if note["timestamp"] > 0: + session_url += f"&jumpto={note['timestamp']}" + title = f"[Note for session {note['sessionId']}]({session_url})" + + blocks = [{ + "type": "TextBlock", + "text": title, + "style": "heading", + "size": "Large" + }, + { + "type": "TextBlock", + "spacing": "Small", + "text": note["message"] + } + ] + if note["tag"]: + blocks.append({"type": "TextBlock", + "spacing": "Small", + "text": f"Tag: *{note['tag']}*", + "size": "Small"}) + bottom = f"Created by {note['userName'].capitalize()}" + if user_id != note["userId"]: + bottom += f"\nSent by {note['shareName']}: " + blocks.append({"type": "TextBlock", + "spacing": "Default", + "text": bottom, + "size": "Small", + "fontType": "Monospace"}) + return MSTeams.send_raw( + tenant_id=tenant_id, + webhook_id=webhook_id, + body={"type": "message", + "attachments": [ + {"contentType": "application/vnd.microsoft.card.adaptive", + "contentUrl": None, + "content": { + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.5", + "body": [{ + "type": "ColumnSet", + "style": "emphasis", + "separator": True, + "bleed": True, + "columns": [{"width": "stretch", + "items": blocks, + "type": "Column"}] + }]}} + ]}) diff --git a/ee/api/chalicelib/core/signals.py b/ee/api/chalicelib/core/signals.py new file mode 100644 index 000000000..72822d0af --- /dev/null +++ b/ee/api/chalicelib/core/signals.py @@ -0,0 +1,12 @@ +import schemas_ee +import logging +from chalicelib.utils import events_queue + + +def handle_frontend_signals_queued(project_id: int, user_id: int, data: schemas_ee.SignalsSchema): + try: + events_queue.global_queue.put((project_id, user_id, data)) + return {'data': 'insertion succeded'} + except Exception as e: + logging.info(f'Error while inserting: {e}') + return {'errors': [e]} diff --git a/ee/api/chalicelib/core/significance.py b/ee/api/chalicelib/core/significance.py index e3d6cc735..4e02eee41 100644 --- a/ee/api/chalicelib/core/significance.py +++ b/ee/api/chalicelib/core/significance.py @@ -2,7 +2,7 @@ __author__ = "AZNAUROV David" __maintainer__ = "KRAIEM Taha Yassine" from decouple import config - +from chalicelib.utils import sql_helper as sh import schemas from chalicelib.core import events, metadata @@ -56,33 +56,33 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]: continue f["value"] = helper.values_for_operator(value=f["value"], op=f["operator"]) # filter_args = _multiple_values(f["value"]) - op = sessions.__get_sql_operator(f["operator"]) + op = sh.get_sql_operator(f["operator"]) filter_type = f["type"] # values[f_k] = sessions.__get_sql_value_multiple(f["value"]) f_k = f"f_value{i}" values = {**values, - **sessions._multiple_values(helper.values_for_operator(value=f["value"], op=f["operator"]), - value_key=f_k)} + **sh.multi_values(helper.values_for_operator(value=f["value"], op=f["operator"]), + value_key=f_k)} if filter_type == schemas.FilterType.user_browser: # op = sessions.__get_sql_operator_multiple(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_browser {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_browser {op} %({f_k})s', f["value"], value_key=f_k)) elif filter_type in [schemas.FilterType.user_os, schemas.FilterType.user_os_ios]: # op = sessions.__get_sql_operator_multiple(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_os {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_os {op} %({f_k})s', f["value"], value_key=f_k)) elif filter_type in [schemas.FilterType.user_device, schemas.FilterType.user_device_ios]: # op = sessions.__get_sql_operator_multiple(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_device {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_device {op} %({f_k})s', f["value"], value_key=f_k)) elif filter_type in [schemas.FilterType.user_country, schemas.FilterType.user_country_ios]: # op = sessions.__get_sql_operator_multiple(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_country {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_country {op} %({f_k})s', f["value"], value_key=f_k)) elif filter_type == schemas.FilterType.duration: if len(f["value"]) > 0 and f["value"][0] is not None: first_stage_extra_constraints.append(f's.duration >= %(minDuration)s') @@ -92,36 +92,36 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]: values["maxDuration"] = f["value"][1] elif filter_type == schemas.FilterType.referrer: # events_query_part = events_query_part + f"INNER JOIN events.pages AS p USING(session_id)" - filter_extra_from = [f"INNER JOIN {events.event_type.LOCATION.table} AS p USING(session_id)"] + filter_extra_from = [f"INNER JOIN {events.EventType.LOCATION.table} AS p USING(session_id)"] # op = sessions.__get_sql_operator_multiple(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f"p.base_referrer {op} %({f_k})s", f["value"], value_key=f_k)) - elif filter_type == events.event_type.METADATA.ui_type: + sh.multi_conditions(f"p.base_referrer {op} %({f_k})s", f["value"], value_key=f_k)) + elif filter_type == events.EventType.METADATA.ui_type: if meta_keys is None: meta_keys = metadata.get(project_id=project_id) meta_keys = {m["key"]: m["index"] for m in meta_keys} # op = sessions.__get_sql_operator(f["operator"]) if f.get("key") in meta_keys.keys(): first_stage_extra_constraints.append( - sessions._multiple_conditions( + sh.multi_conditions( f's.{metadata.index_to_colname(meta_keys[f["key"]])} {op} %({f_k})s', f["value"], value_key=f_k)) # values[f_k] = helper.string_to_sql_like_with_op(f["value"][0], op) elif filter_type in [schemas.FilterType.user_id, schemas.FilterType.user_id_ios]: # op = sessions.__get_sql_operator(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_id {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_id {op} %({f_k})s', f["value"], value_key=f_k)) # values[f_k] = helper.string_to_sql_like_with_op(f["value"][0], op) elif filter_type in [schemas.FilterType.user_anonymous_id, schemas.FilterType.user_anonymous_id_ios]: # op = sessions.__get_sql_operator(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_anonymous_id {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_anonymous_id {op} %({f_k})s', f["value"], value_key=f_k)) # values[f_k] = helper.string_to_sql_like_with_op(f["value"][0], op) elif filter_type in [schemas.FilterType.rev_id, schemas.FilterType.rev_id_ios]: # op = sessions.__get_sql_operator(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.rev_id {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.rev_id {op} %({f_k})s', f["value"], value_key=f_k)) # values[f_k] = helper.string_to_sql_like_with_op(f["value"][0], op) i = -1 for s in stages: @@ -131,7 +131,7 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]: if not isinstance(s["value"], list): s["value"] = [s["value"]] - is_any = sessions._isAny_opreator(s["operator"]) + is_any = sh.isAny_opreator(s["operator"]) if not is_any and isinstance(s["value"], list) and len(s["value"]) == 0: continue i += 1 @@ -139,41 +139,42 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]: extra_from = filter_extra_from + ["INNER JOIN public.sessions AS s USING (session_id)"] else: extra_from = [] - op = sessions.__get_sql_operator(s["operator"]) - event_type = s["type"].upper() - if event_type == events.event_type.CLICK.ui_type: - next_table = events.event_type.CLICK.table - next_col_name = events.event_type.CLICK.column - elif event_type == events.event_type.INPUT.ui_type: - next_table = events.event_type.INPUT.table - next_col_name = events.event_type.INPUT.column - elif event_type == events.event_type.LOCATION.ui_type: - next_table = events.event_type.LOCATION.table - next_col_name = events.event_type.LOCATION.column - elif event_type == events.event_type.CUSTOM.ui_type: - next_table = events.event_type.CUSTOM.table - next_col_name = events.event_type.CUSTOM.column + op = sh.get_sql_operator(s["operator"]) + # event_type = s["type"].upper() + event_type = s["type"] + if event_type == events.EventType.CLICK.ui_type: + next_table = events.EventType.CLICK.table + next_col_name = events.EventType.CLICK.column + elif event_type == events.EventType.INPUT.ui_type: + next_table = events.EventType.INPUT.table + next_col_name = events.EventType.INPUT.column + elif event_type == events.EventType.LOCATION.ui_type: + next_table = events.EventType.LOCATION.table + next_col_name = events.EventType.LOCATION.column + elif event_type == events.EventType.CUSTOM.ui_type: + next_table = events.EventType.CUSTOM.table + next_col_name = events.EventType.CUSTOM.column # IOS -------------- - elif event_type == events.event_type.CLICK_IOS.ui_type: - next_table = events.event_type.CLICK_IOS.table - next_col_name = events.event_type.CLICK_IOS.column - elif event_type == events.event_type.INPUT_IOS.ui_type: - next_table = events.event_type.INPUT_IOS.table - next_col_name = events.event_type.INPUT_IOS.column - elif event_type == events.event_type.VIEW_IOS.ui_type: - next_table = events.event_type.VIEW_IOS.table - next_col_name = events.event_type.VIEW_IOS.column - elif event_type == events.event_type.CUSTOM_IOS.ui_type: - next_table = events.event_type.CUSTOM_IOS.table - next_col_name = events.event_type.CUSTOM_IOS.column + elif event_type == events.EventType.CLICK_IOS.ui_type: + next_table = events.EventType.CLICK_IOS.table + next_col_name = events.EventType.CLICK_IOS.column + elif event_type == events.EventType.INPUT_IOS.ui_type: + next_table = events.EventType.INPUT_IOS.table + next_col_name = events.EventType.INPUT_IOS.column + elif event_type == events.EventType.VIEW_IOS.ui_type: + next_table = events.EventType.VIEW_IOS.table + next_col_name = events.EventType.VIEW_IOS.column + elif event_type == events.EventType.CUSTOM_IOS.ui_type: + next_table = events.EventType.CUSTOM_IOS.table + next_col_name = events.EventType.CUSTOM_IOS.column else: - print("=================UNDEFINED") + print(f"=================UNDEFINED:{event_type}") continue - values = {**values, **sessions._multiple_values(helper.values_for_operator(value=s["value"], op=s["operator"]), - value_key=f"value{i + 1}")} - if sessions.__is_negation_operator(op) and i > 0: - op = sessions.__reverse_sql_operator(op) + values = {**values, **sh.multi_values(helper.values_for_operator(value=s["value"], op=s["operator"]), + value_key=f"value{i + 1}")} + if sh.is_negation_operator(op) and i > 0: + op = sh.reverse_sql_operator(op) main_condition = "left_not.session_id ISNULL" extra_from.append(f"""LEFT JOIN LATERAL (SELECT session_id FROM {next_table} AS s_main @@ -184,8 +185,8 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]: if is_any: main_condition = "TRUE" else: - main_condition = sessions._multiple_conditions(f"main.{next_col_name} {op} %(value{i + 1})s", - values=s["value"], value_key=f"value{i + 1}") + main_condition = sh.multi_conditions(f"main.{next_col_name} {op} %(value{i + 1})s", + values=s["value"], value_key=f"value{i + 1}") n_stages_query.append(f""" (SELECT main.session_id, {"MIN(main.timestamp)" if i + 1 < len(stages) else "MAX(main.timestamp)"} AS stage{i + 1}_timestamp @@ -326,7 +327,7 @@ def get_transitions_and_issues_of_each_type(rows: List[RealDictRow], all_issues, transitions ::: if transited from the first stage to the last - 1 else - 0 - errors ::: a dictionary where the keys are all unique issues (currently context-wise) + errors ::: a dictionary WHERE the keys are all unique issues (currently context-wise) the values are lists if an issue happened between the first stage to the last - 1 else - 0 diff --git a/ee/api/chalicelib/core/significance_exp.py b/ee/api/chalicelib/core/significance_exp.py index 59cb93cd7..4e02eee41 100644 --- a/ee/api/chalicelib/core/significance_exp.py +++ b/ee/api/chalicelib/core/significance_exp.py @@ -2,7 +2,7 @@ __author__ = "AZNAUROV David" __maintainer__ = "KRAIEM Taha Yassine" from decouple import config - +from chalicelib.utils import sql_helper as sh import schemas from chalicelib.core import events, metadata @@ -56,33 +56,33 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]: continue f["value"] = helper.values_for_operator(value=f["value"], op=f["operator"]) # filter_args = _multiple_values(f["value"]) - op = sessions.__get_sql_operator(f["operator"]) + op = sh.get_sql_operator(f["operator"]) filter_type = f["type"] # values[f_k] = sessions.__get_sql_value_multiple(f["value"]) f_k = f"f_value{i}" values = {**values, - **sessions._multiple_values(helper.values_for_operator(value=f["value"], op=f["operator"]), - value_key=f_k)} + **sh.multi_values(helper.values_for_operator(value=f["value"], op=f["operator"]), + value_key=f_k)} if filter_type == schemas.FilterType.user_browser: # op = sessions.__get_sql_operator_multiple(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_browser {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_browser {op} %({f_k})s', f["value"], value_key=f_k)) elif filter_type in [schemas.FilterType.user_os, schemas.FilterType.user_os_ios]: # op = sessions.__get_sql_operator_multiple(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_os {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_os {op} %({f_k})s', f["value"], value_key=f_k)) elif filter_type in [schemas.FilterType.user_device, schemas.FilterType.user_device_ios]: # op = sessions.__get_sql_operator_multiple(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_device {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_device {op} %({f_k})s', f["value"], value_key=f_k)) elif filter_type in [schemas.FilterType.user_country, schemas.FilterType.user_country_ios]: # op = sessions.__get_sql_operator_multiple(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_country {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_country {op} %({f_k})s', f["value"], value_key=f_k)) elif filter_type == schemas.FilterType.duration: if len(f["value"]) > 0 and f["value"][0] is not None: first_stage_extra_constraints.append(f's.duration >= %(minDuration)s') @@ -92,36 +92,36 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]: values["maxDuration"] = f["value"][1] elif filter_type == schemas.FilterType.referrer: # events_query_part = events_query_part + f"INNER JOIN events.pages AS p USING(session_id)" - filter_extra_from = [f"INNER JOIN {events.event_type.LOCATION.table} AS p USING(session_id)"] + filter_extra_from = [f"INNER JOIN {events.EventType.LOCATION.table} AS p USING(session_id)"] # op = sessions.__get_sql_operator_multiple(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f"p.base_referrer {op} %({f_k})s", f["value"], value_key=f_k)) - elif filter_type == events.event_type.METADATA.ui_type: + sh.multi_conditions(f"p.base_referrer {op} %({f_k})s", f["value"], value_key=f_k)) + elif filter_type == events.EventType.METADATA.ui_type: if meta_keys is None: meta_keys = metadata.get(project_id=project_id) meta_keys = {m["key"]: m["index"] for m in meta_keys} # op = sessions.__get_sql_operator(f["operator"]) if f.get("key") in meta_keys.keys(): first_stage_extra_constraints.append( - sessions._multiple_conditions( + sh.multi_conditions( f's.{metadata.index_to_colname(meta_keys[f["key"]])} {op} %({f_k})s', f["value"], value_key=f_k)) # values[f_k] = helper.string_to_sql_like_with_op(f["value"][0], op) elif filter_type in [schemas.FilterType.user_id, schemas.FilterType.user_id_ios]: # op = sessions.__get_sql_operator(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_id {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_id {op} %({f_k})s', f["value"], value_key=f_k)) # values[f_k] = helper.string_to_sql_like_with_op(f["value"][0], op) elif filter_type in [schemas.FilterType.user_anonymous_id, schemas.FilterType.user_anonymous_id_ios]: # op = sessions.__get_sql_operator(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.user_anonymous_id {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.user_anonymous_id {op} %({f_k})s', f["value"], value_key=f_k)) # values[f_k] = helper.string_to_sql_like_with_op(f["value"][0], op) elif filter_type in [schemas.FilterType.rev_id, schemas.FilterType.rev_id_ios]: # op = sessions.__get_sql_operator(f["operator"]) first_stage_extra_constraints.append( - sessions._multiple_conditions(f's.rev_id {op} %({f_k})s', f["value"], value_key=f_k)) + sh.multi_conditions(f's.rev_id {op} %({f_k})s', f["value"], value_key=f_k)) # values[f_k] = helper.string_to_sql_like_with_op(f["value"][0], op) i = -1 for s in stages: @@ -131,7 +131,7 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]: if not isinstance(s["value"], list): s["value"] = [s["value"]] - is_any = sessions._isAny_opreator(s["operator"]) + is_any = sh.isAny_opreator(s["operator"]) if not is_any and isinstance(s["value"], list) and len(s["value"]) == 0: continue i += 1 @@ -139,41 +139,42 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]: extra_from = filter_extra_from + ["INNER JOIN public.sessions AS s USING (session_id)"] else: extra_from = [] - op = sessions.__get_sql_operator(s["operator"]) - event_type = s["type"].upper() - if event_type == events.event_type.CLICK.ui_type: - next_table = events.event_type.CLICK.table - next_col_name = events.event_type.CLICK.column - elif event_type == events.event_type.INPUT.ui_type: - next_table = events.event_type.INPUT.table - next_col_name = events.event_type.INPUT.column - elif event_type == events.event_type.LOCATION.ui_type: - next_table = events.event_type.LOCATION.table - next_col_name = events.event_type.LOCATION.column - elif event_type == events.event_type.CUSTOM.ui_type: - next_table = events.event_type.CUSTOM.table - next_col_name = events.event_type.CUSTOM.column + op = sh.get_sql_operator(s["operator"]) + # event_type = s["type"].upper() + event_type = s["type"] + if event_type == events.EventType.CLICK.ui_type: + next_table = events.EventType.CLICK.table + next_col_name = events.EventType.CLICK.column + elif event_type == events.EventType.INPUT.ui_type: + next_table = events.EventType.INPUT.table + next_col_name = events.EventType.INPUT.column + elif event_type == events.EventType.LOCATION.ui_type: + next_table = events.EventType.LOCATION.table + next_col_name = events.EventType.LOCATION.column + elif event_type == events.EventType.CUSTOM.ui_type: + next_table = events.EventType.CUSTOM.table + next_col_name = events.EventType.CUSTOM.column # IOS -------------- - elif event_type == events.event_type.CLICK_IOS.ui_type: - next_table = events.event_type.CLICK_IOS.table - next_col_name = events.event_type.CLICK_IOS.column - elif event_type == events.event_type.INPUT_IOS.ui_type: - next_table = events.event_type.INPUT_IOS.table - next_col_name = events.event_type.INPUT_IOS.column - elif event_type == events.event_type.VIEW_IOS.ui_type: - next_table = events.event_type.VIEW_IOS.table - next_col_name = events.event_type.VIEW_IOS.column - elif event_type == events.event_type.CUSTOM_IOS.ui_type: - next_table = events.event_type.CUSTOM_IOS.table - next_col_name = events.event_type.CUSTOM_IOS.column + elif event_type == events.EventType.CLICK_IOS.ui_type: + next_table = events.EventType.CLICK_IOS.table + next_col_name = events.EventType.CLICK_IOS.column + elif event_type == events.EventType.INPUT_IOS.ui_type: + next_table = events.EventType.INPUT_IOS.table + next_col_name = events.EventType.INPUT_IOS.column + elif event_type == events.EventType.VIEW_IOS.ui_type: + next_table = events.EventType.VIEW_IOS.table + next_col_name = events.EventType.VIEW_IOS.column + elif event_type == events.EventType.CUSTOM_IOS.ui_type: + next_table = events.EventType.CUSTOM_IOS.table + next_col_name = events.EventType.CUSTOM_IOS.column else: - print("=================UNDEFINED") + print(f"=================UNDEFINED:{event_type}") continue - values = {**values, **sessions._multiple_values(helper.values_for_operator(value=s["value"], op=s["operator"]), - value_key=f"value{i + 1}")} - if sessions.__is_negation_operator(op) and i > 0: - op = sessions.__reverse_sql_operator(op) + values = {**values, **sh.multi_values(helper.values_for_operator(value=s["value"], op=s["operator"]), + value_key=f"value{i + 1}")} + if sh.is_negation_operator(op) and i > 0: + op = sh.reverse_sql_operator(op) main_condition = "left_not.session_id ISNULL" extra_from.append(f"""LEFT JOIN LATERAL (SELECT session_id FROM {next_table} AS s_main @@ -184,13 +185,11 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]: if is_any: main_condition = "TRUE" else: - main_condition = sessions._multiple_conditions(f"main.{next_col_name} {op} %(value{i + 1})s", - values=s["value"], value_key=f"value{i + 1}") + main_condition = sh.multi_conditions(f"main.{next_col_name} {op} %(value{i + 1})s", + values=s["value"], value_key=f"value{i + 1}") n_stages_query.append(f""" (SELECT main.session_id, - {"MIN(main.timestamp)" if i + 1 < len(stages) else "MAX(main.timestamp)"} AS stage{i + 1}_timestamp, - '{event_type}' AS type, - '{s["operator"]}' AS operator + {"MIN(main.timestamp)" if i + 1 < len(stages) else "MAX(main.timestamp)"} AS stage{i + 1}_timestamp FROM {next_table} AS main {" ".join(extra_from)} WHERE main.timestamp >= {f"T{i}.stage{i}_timestamp" if i > 0 else "%(startTimestamp)s"} {f"AND main.session_id=T1.session_id" if i > 0 else ""} @@ -198,45 +197,55 @@ def get_stages_and_events(filter_d, project_id) -> List[RealDictRow]: {(" AND " + " AND ".join(stage_constraints)) if len(stage_constraints) > 0 else ""} {(" AND " + " AND ".join(first_stage_extra_constraints)) if len(first_stage_extra_constraints) > 0 and i == 0 else ""} GROUP BY main.session_id) - AS T{i + 1} {"USING (session_id)" if i > 0 else ""} + AS T{i + 1} {"ON (TRUE)" if i > 0 else ""} """) - if len(n_stages_query) == 0: + n_stages = len(n_stages_query) + if n_stages == 0: return [] n_stages_query = " LEFT JOIN LATERAL ".join(n_stages_query) n_stages_query += ") AS stages_t" n_stages_query = f""" - SELECT stages_and_issues_t.*,sessions.session_id, sessions.user_uuid FROM ( + SELECT stages_and_issues_t.*, sessions.user_uuid + FROM ( SELECT * FROM ( - SELECT * FROM - {n_stages_query} + SELECT T1.session_id, {",".join([f"stage{i + 1}_timestamp" for i in range(n_stages)])} + FROM {n_stages_query} LEFT JOIN LATERAL - ( - SELECT * FROM - (SELECT ISE.session_id, - ISS.type as issue_type, + ( SELECT ISS.type as issue_type, ISE.timestamp AS issue_timestamp, - ISS.context_string as issue_context, + COALESCE(ISS.context_string,'') as issue_context, ISS.issue_id as issue_id FROM events_common.issues AS ISE INNER JOIN issues AS ISS USING (issue_id) WHERE ISE.timestamp >= stages_t.stage1_timestamp AND ISE.timestamp <= stages_t.stage{i + 1}_timestamp AND ISS.project_id=%(project_id)s - {"AND ISS.type IN %(issueTypes)s" if len(filter_issues) > 0 else ""}) AS base_t - ) AS issues_t - USING (session_id)) AS stages_and_issues_t - inner join sessions USING(session_id); + AND ISE.session_id = stages_t.session_id + AND ISS.type!='custom' -- ignore custom issues because they are massive + {"AND ISS.type IN %(issueTypes)s" if len(filter_issues) > 0 else ""} + LIMIT 10 -- remove the limit to get exact stats + ) AS issues_t ON (TRUE) + ) AS stages_and_issues_t INNER JOIN sessions USING(session_id); """ # LIMIT 10000 params = {"project_id": project_id, "startTimestamp": filter_d["startDate"], "endTimestamp": filter_d["endDate"], "issueTypes": tuple(filter_issues), **values} with pg_client.PostgresClient() as cur: + query = cur.mogrify(n_stages_query, params) # print("---------------------------------------------------") - # print(cur.mogrify(n_stages_query, params)) + # print(query) # print("---------------------------------------------------") - cur.execute(cur.mogrify(n_stages_query, params)) - rows = cur.fetchall() + try: + cur.execute(query) + rows = cur.fetchall() + except Exception as err: + print("--------- FUNNEL SEARCH QUERY EXCEPTION -----------") + print(query.decode('UTF-8')) + print("--------- PAYLOAD -----------") + print(filter_d) + print("--------------------") + raise err return rows @@ -298,13 +307,27 @@ def pearson_corr(x: list, y: list): return r, confidence, False -def get_transitions_and_issues_of_each_type(rows: List[RealDictRow], all_issues_with_context, first_stage, last_stage): +# def tuple_or(t: tuple): +# x = 0 +# for el in t: +# x |= el # | is for bitwise OR +# return x +# +# The following function is correct optimization of the previous function because t is a list of 0,1 +def tuple_or(t: tuple): + for el in t: + if el > 0: + return 1 + return 0 + + +def get_transitions_and_issues_of_each_type(rows: List[RealDictRow], all_issues, first_stage, last_stage): """ Returns two lists with binary values 0/1: transitions ::: if transited from the first stage to the last - 1 else - 0 - errors ::: a dictionary where the keys are all unique issues (currently context-wise) + errors ::: a dictionary WHERE the keys are all unique issues (currently context-wise) the values are lists if an issue happened between the first stage to the last - 1 else - 0 @@ -317,12 +340,6 @@ def get_transitions_and_issues_of_each_type(rows: List[RealDictRow], all_issues_ transitions = [] n_sess_affected = 0 errors = {} - for issue in all_issues_with_context: - split = issue.split('__^__') - errors[issue] = { - "errors": [], - "issue_type": split[0], - "context": split[1]} for row in rows: t = 0 @@ -330,38 +347,26 @@ def get_transitions_and_issues_of_each_type(rows: List[RealDictRow], all_issues_ last_ts = row[f'stage{last_stage}_timestamp'] if first_ts is None: continue - elif first_ts is not None and last_ts is not None: + elif last_ts is not None: t = 1 transitions.append(t) ic_present = False - for issue_type_with_context in errors: + for error_id in all_issues: + if error_id not in errors: + errors[error_id] = [] ic = 0 - issue_type = errors[issue_type_with_context]["issue_type"] - context = errors[issue_type_with_context]["context"] - if row['issue_type'] is not None: + row_issue_id = row['issue_id'] + if row_issue_id is not None: if last_ts is None or (first_ts < row['issue_timestamp'] < last_ts): - context_in_row = row['issue_context'] if row['issue_context'] is not None else '' - if issue_type == row['issue_type'] and context == context_in_row: + if error_id == row_issue_id: ic = 1 ic_present = True - errors[issue_type_with_context]["errors"].append(ic) + errors[error_id].append(ic) if ic_present and t: n_sess_affected += 1 - # def tuple_or(t: tuple): - # x = 0 - # for el in t: - # x |= el - # return x - def tuple_or(t: tuple): - for el in t: - if el > 0: - return 1 - return 0 - - errors = {key: errors[key]["errors"] for key in errors} all_errors = [tuple_or(t) for t in zip(*errors.values())] return transitions, errors, all_errors, n_sess_affected @@ -377,10 +382,9 @@ def get_affected_users_for_all_issues(rows, first_stage, last_stage): """ affected_users = defaultdict(lambda: set()) affected_sessions = defaultdict(lambda: set()) - contexts = defaultdict(lambda: None) + all_issues = {} n_affected_users_dict = defaultdict(lambda: None) n_affected_sessions_dict = defaultdict(lambda: None) - all_issues_with_context = set() n_issues_dict = defaultdict(lambda: 0) issues_by_session = defaultdict(lambda: 0) @@ -396,15 +400,13 @@ def get_affected_users_for_all_issues(rows, first_stage, last_stage): # check that the issue exists and belongs to subfunnel: if iss is not None and (row[f'stage{last_stage}_timestamp'] is None or (row[f'stage{first_stage}_timestamp'] < iss_ts < row[f'stage{last_stage}_timestamp'])): - context_string = row['issue_context'] if row['issue_context'] is not None else '' - issue_with_context = iss + '__^__' + context_string - contexts[issue_with_context] = {"context": context_string, "id": row["issue_id"]} - all_issues_with_context.add(issue_with_context) - n_issues_dict[issue_with_context] += 1 + if row["issue_id"] not in all_issues: + all_issues[row["issue_id"]] = {"context": row['issue_context'], "issue_type": row["issue_type"]} + n_issues_dict[row["issue_id"]] += 1 if row['user_uuid'] is not None: - affected_users[issue_with_context].add(row['user_uuid']) + affected_users[row["issue_id"]].add(row['user_uuid']) - affected_sessions[issue_with_context].add(row['session_id']) + affected_sessions[row["issue_id"]].add(row['session_id']) issues_by_session[row[f'session_id']] += 1 if len(affected_users) > 0: @@ -415,29 +417,28 @@ def get_affected_users_for_all_issues(rows, first_stage, last_stage): n_affected_sessions_dict.update({ iss: len(affected_sessions[iss]) for iss in affected_sessions }) - return all_issues_with_context, n_issues_dict, n_affected_users_dict, n_affected_sessions_dict, contexts + return all_issues, n_issues_dict, n_affected_users_dict, n_affected_sessions_dict def count_sessions(rows, n_stages): session_counts = {i: set() for i in range(1, n_stages + 1)} - for ind, row in enumerate(rows): + for row in rows: for i in range(1, n_stages + 1): if row[f"stage{i}_timestamp"] is not None: session_counts[i].add(row[f"session_id"]) + session_counts = {i: len(session_counts[i]) for i in session_counts} return session_counts def count_users(rows, n_stages): - users_in_stages = defaultdict(lambda: set()) - - for ind, row in enumerate(rows): + users_in_stages = {i: set() for i in range(1, n_stages + 1)} + for row in rows: for i in range(1, n_stages + 1): if row[f"stage{i}_timestamp"] is not None: users_in_stages[i].add(row["user_uuid"]) users_count = {i: len(users_in_stages[i]) for i in range(1, n_stages + 1)} - return users_count @@ -490,18 +491,18 @@ def get_issues(stages, rows, first_stage=None, last_stage=None, drop_only=False) last_stage = n_stages n_critical_issues = 0 - issues_dict = dict({"significant": [], - "insignificant": []}) + issues_dict = {"significant": [], + "insignificant": []} session_counts = count_sessions(rows, n_stages) drop = session_counts[first_stage] - session_counts[last_stage] - all_issues_with_context, n_issues_dict, affected_users_dict, affected_sessions, contexts = get_affected_users_for_all_issues( + all_issues, n_issues_dict, affected_users_dict, affected_sessions = get_affected_users_for_all_issues( rows, first_stage, last_stage) transitions, errors, all_errors, n_sess_affected = get_transitions_and_issues_of_each_type(rows, - all_issues_with_context, + all_issues, first_stage, last_stage) - print("len(transitions) =", len(transitions)) + del rows if any(all_errors): total_drop_corr, conf, is_sign = pearson_corr(transitions, all_errors) @@ -514,33 +515,35 @@ def get_issues(stages, rows, first_stage=None, last_stage=None, drop_only=False) if drop_only: return total_drop_due_to_issues - for issue in all_issues_with_context: + for issue_id in all_issues: - if not any(errors[issue]): + if not any(errors[issue_id]): continue - r, confidence, is_sign = pearson_corr(transitions, errors[issue]) + r, confidence, is_sign = pearson_corr(transitions, errors[issue_id]) if r is not None and drop is not None and is_sign: - lost_conversions = int(r * affected_sessions[issue]) + lost_conversions = int(r * affected_sessions[issue_id]) else: lost_conversions = None if r is None: r = 0 - split = issue.split('__^__') issues_dict['significant' if is_sign else 'insignificant'].append({ - "type": split[0], - "title": helper.get_issue_title(split[0]), - "affected_sessions": affected_sessions[issue], - "unaffected_sessions": session_counts[1] - affected_sessions[issue], + "type": all_issues[issue_id]["issue_type"], + "title": helper.get_issue_title(all_issues[issue_id]["issue_type"]), + "affected_sessions": affected_sessions[issue_id], + "unaffected_sessions": session_counts[1] - affected_sessions[issue_id], "lost_conversions": lost_conversions, - "affected_users": affected_users_dict[issue], + "affected_users": affected_users_dict[issue_id], "conversion_impact": round(r * 100), - "context_string": contexts[issue]["context"], - "issue_id": contexts[issue]["id"] + "context_string": all_issues[issue_id]["context"], + "issue_id": issue_id }) if is_sign: - n_critical_issues += n_issues_dict[issue] + n_critical_issues += n_issues_dict[issue_id] + # To limit the number of returned issues to the frontend + issues_dict["significant"] = issues_dict["significant"][:20] + issues_dict["insignificant"] = issues_dict["insignificant"][:20] return n_critical_issues, issues_dict, total_drop_due_to_issues @@ -566,8 +569,7 @@ def get_top_insights(filter_d, project_id): }] counts = sessions.search_sessions(data=schemas.SessionsSearchCountSchema.parse_obj(filter_d), - project_id=project_id, - user_id=None, count_only=True) + project_id=project_id, user_id=None, count_only=True) output[0]["sessionsCount"] = counts["countSessions"] output[0]["usersCount"] = counts["countUsers"] return output, 0 diff --git a/ee/api/chalicelib/core/tenants.py b/ee/api/chalicelib/core/tenants.py index 7f8e73ca8..30a87bd29 100644 --- a/ee/api/chalicelib/core/tenants.py +++ b/ee/api/chalicelib/core/tenants.py @@ -6,84 +6,76 @@ from chalicelib.utils import pg_client def get_by_tenant_key(tenant_key): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - f"""SELECT - t.tenant_id, - t.name, - t.api_key, - t.created_at, - '{license.EDITION}' AS edition, - openreplay_version() AS version_number, - t.opt_out - FROM public.tenants AS t - WHERE t.tenant_key = %(tenant_key)s AND t.deleted_at ISNULL - LIMIT 1;""", - {"tenant_key": tenant_key}) - ) + query = cur.mogrify(f"""SELECT tenants.tenant_id, + tenants.name, + tenants.api_key, + tenants.created_at, + '{license.EDITION}' AS edition, + openreplay_version() AS version_number, + tenants.opt_out, + tenants.tenant_key + FROM public.tenants + WHERE tenants.tenant_key = %(tenant_key)s + AND tenants.deleted_at ISNULL + LIMIT 1;""", + {"tenant_key": tenant_key}) + cur.execute(query=query) return helper.dict_to_camel_case(cur.fetchone()) def get_by_tenant_id(tenant_id): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - f"""SELECT - t.tenant_id, - t.name, - t.api_key, - t.created_at, - '{license.EDITION}' AS edition, - openreplay_version() AS version_number, - t.opt_out, - t.tenant_key - FROM public.tenants AS t - WHERE t.tenant_id = %(tenantId)s AND t.deleted_at ISNULL - LIMIT 1;""", - {"tenantId": tenant_id}) - ) + query = cur.mogrify(f"""SELECT tenants.tenant_id, + tenants.name, + tenants.api_key, + tenants.created_at, + '{license.EDITION}' AS edition, + openreplay_version() AS version_number, + tenants.opt_out, + tenants.tenant_key + FROM public.tenants + WHERE tenants.tenant_id = %(tenantId)s + AND tenants.deleted_at ISNULL + LIMIT 1;""", + {"tenantId": tenant_id}) + cur.execute(query=query) return helper.dict_to_camel_case(cur.fetchone()) def get_by_api_key(api_key): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - f"""SELECT - t.tenant_id, - t.name, - t.created_at - FROM public.tenants AS t - WHERE t.api_key = %(api_key)s AND t.deleted_at ISNULL - LIMIT 1;""", - {"api_key": api_key}) - ) + query = cur.mogrify(f"""SELECT tenants.tenant_id, + tenants.name, + tenants.created_at + FROM public.tenants + WHERE tenants.api_key = %(api_key)s + AND tenants.deleted_at ISNULL + LIMIT 1;""", + {"api_key": api_key}) + cur.execute(query=query) return helper.dict_to_camel_case(cur.fetchone()) def generate_new_api_key(tenant_id): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - f"""UPDATE public.tenants - SET api_key=generate_api_key(20) - WHERE tenant_id= %(tenant_id)s AND deleted_at ISNULL - RETURNING api_key;""", - {"tenant_id": tenant_id}) - ) + query = cur.mogrify(f"""UPDATE public.tenants + SET api_key=generate_api_key(20) + WHERE tenant_id= %(tenant_id)s + AND deleted_at ISNULL + RETURNING api_key;""", + {"tenant_id": tenant_id}) + cur.execute(query=query) return helper.dict_to_camel_case(cur.fetchone()) def edit_client(tenant_id, changes): with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify(f"""\ - UPDATE public.tenants - SET {", ".join([f"{helper.key_to_snake_case(k)} = %({k})s" for k in changes.keys()])} - WHERE tenant_id= %(tenant_id)s AND deleted_at ISNULL - RETURNING name, opt_out;""", - {"tenantId": tenant_id, **changes}) - ) + query = cur.mogrify(f"""UPDATE public.tenants + SET {", ".join([f"{helper.key_to_snake_case(k)} = %({k})s" for k in changes.keys()])} + WHERE tenant_id= %(tenant_id)s AND deleted_at ISNULL + RETURNING name, opt_out;""", + {"tenant_id": tenant_id, **changes}) + cur.execute(query=query) return helper.dict_to_camel_case(cur.fetchone()) @@ -92,7 +84,7 @@ def update(tenant_id, user_id, data: schemas.UpdateTenantSchema): if not admin["admin"] and not admin["superAdmin"]: return {"errors": ["unauthorized, needs admin or owner"]} - if "name" not in data and "optOut" not in data: + if data.name is None and data.opt_out is None: return {"errors": ["please provide 'name' of 'optOut' attribute for update"]} changes = {} if data.name is not None and len(data.name) > 0: diff --git a/ee/api/chalicelib/core/traces.py b/ee/api/chalicelib/core/traces.py index 14f26e0b6..52866cc99 100644 --- a/ee/api/chalicelib/core/traces.py +++ b/ee/api/chalicelib/core/traces.py @@ -179,10 +179,10 @@ def get_all(tenant_id, data: schemas_ee.TrailSearchPayloadSchema): COALESCE(JSONB_AGG(full_traces ORDER BY rn) FILTER (WHERE rn > %(p_start)s AND rn <= %(p_end)s), '[]'::JSONB) AS sessions FROM (SELECT traces.*,users.email,users.name AS username, - ROW_NUMBER() OVER (ORDER BY traces.created_at {data.order}) AS rn + ROW_NUMBER() OVER (ORDER BY traces.created_at {data.order.value}) AS rn FROM traces LEFT JOIN users USING (user_id) WHERE {" AND ".join(conditions)} - ORDER BY traces.created_at {data.order}) AS full_traces;""", params) + ORDER BY traces.created_at {data.order.value}) AS full_traces;""", params) ) rows = cur.fetchone() return helper.dict_to_camel_case(rows) diff --git a/ee/api/chalicelib/core/users.py b/ee/api/chalicelib/core/users.py index b55600da2..ff357113f 100644 --- a/ee/api/chalicelib/core/users.py +++ b/ee/api/chalicelib/core/users.py @@ -25,7 +25,7 @@ def create_new_member(tenant_id, email, invitation_token, admin, name, owner=Fal (SELECT COALESCE((SELECT role_id FROM roles WHERE tenant_id = %(tenant_id)s AND role_id = %(role_id)s), (SELECT role_id FROM roles WHERE tenant_id = %(tenant_id)s AND name = 'Member' LIMIT 1), (SELECT role_id FROM roles WHERE tenant_id = %(tenant_id)s AND name != 'Owner' LIMIT 1)))) - RETURNING tenant_id,user_id,email,role,name, role_id + RETURNING tenant_id,user_id,email,role,name,created_at, role_id ), au AS (INSERT INTO public.basic_authentication (user_id, invitation_token, invited_at) VALUES ((SELECT user_id FROM u), %(invitation_token)s, timezone('utc'::text, now())) @@ -36,6 +36,7 @@ def create_new_member(tenant_id, email, invitation_token, admin, name, owner=Fal u.email, u.role, u.name, + u.created_at, (CASE WHEN u.role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin, (CASE WHEN u.role = 'admin' THEN TRUE ELSE FALSE END) AS admin, (CASE WHEN u.role = 'member' THEN TRUE ELSE FALSE END) AS member, @@ -49,10 +50,11 @@ def create_new_member(tenant_id, email, invitation_token, admin, name, owner=Fal "role": "owner" if owner else "admin" if admin else "member", "name": name, "data": json.dumps({"lastAnnouncementView": TimeUTC.now()}), "invitation_token": invitation_token, "role_id": role_id}) - cur.execute( - query - ) - return helper.dict_to_camel_case(cur.fetchone()) + cur.execute(query) + row = helper.dict_to_camel_case(cur.fetchone()) + if row: + row["createdAt"] = TimeUTC.datetime_to_timestamp(row["createdAt"]) + return row def restore_member(tenant_id, user_id, email, invitation_token, admin, name, owner=False, role_id=None): @@ -76,13 +78,11 @@ def restore_member(tenant_id, user_id, email, invitation_token, admin, name, own (CASE WHEN role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin, (CASE WHEN role = 'admin' THEN TRUE ELSE FALSE END) AS admin, (CASE WHEN role = 'member' THEN TRUE ELSE FALSE END) AS member, - role_id;""", + created_at,role_id;""", {"tenant_id": tenant_id, "user_id": user_id, "email": email, "role": "owner" if owner else "admin" if admin else "member", "name": name, "role_id": role_id}) - cur.execute( - query - ) + cur.execute(query) result = cur.fetchone() query = cur.mogrify("""\ UPDATE public.basic_authentication @@ -93,10 +93,9 @@ def restore_member(tenant_id, user_id, email, invitation_token, admin, name, own WHERE user_id=%(user_id)s RETURNING invitation_token;""", {"user_id": user_id, "invitation_token": invitation_token}) - cur.execute( - query - ) + cur.execute(query) result["invitation_token"] = cur.fetchone()["invitation_token"] + result["created_at"] = TimeUTC.datetime_to_timestamp(result["created_at"]) return helper.dict_to_camel_case(result) @@ -212,18 +211,20 @@ def create_member(tenant_id, user_id, data, background_tasks: BackgroundTasks): if user: return {"errors": ["user already exists"]} name = data.get("name", None) - if name is not None and len(name) == 0: - return {"errors": ["invalid user name"]} - if name is None: + if name is None or len(name) == 0: name = data["email"] role_id = data.get("roleId") if role_id is None: role_id = roles.get_role_by_name(tenant_id=tenant_id, name="member").get("roleId") invitation_token = __generate_invitation_token() user = get_deleted_user_by_email(email=data["email"]) - if user is not None: + if user is not None and user["tenantId"] == tenant_id: new_member = restore_member(tenant_id=tenant_id, email=data["email"], invitation_token=invitation_token, admin=data.get("admin", False), name=name, user_id=user["userId"], role_id=role_id) + elif user is not None: + __hard_delete_user(user_id=user["userId"]) + new_member = create_new_member(tenant_id=tenant_id, email=data["email"], invitation_token=invitation_token, + admin=data.get("admin", False), name=name, role_id=role_id) else: new_member = create_new_member(tenant_id=tenant_id, email=data["email"], invitation_token=invitation_token, admin=data.get("admin", False), name=name, role_id=role_id) @@ -543,6 +544,9 @@ def change_password(tenant_id, user_id, email, old_password, new_password): item = get(tenant_id=tenant_id, user_id=user_id) if item is None: return {"errors": ["access denied"]} + if item["origin"] is not None and config("enforce_SSO", cast=bool, default=False) \ + and not item["superAdmin"] and helper.is_saml2_available(): + return {"errors": ["Please use your SSO to change your password, enforced by admin"]} if item["origin"] is not None and item["hasPassword"] is False: return {"errors": ["cannot change your password because you are logged-in from an SSO service"]} if old_password == new_password: @@ -554,24 +558,8 @@ def change_password(tenant_id, user_id, email, old_password, new_password): user = update(tenant_id=tenant_id, user_id=user_id, changes=changes) r = authenticate(user['email'], new_password) - tenant_id = r.pop("tenantId") - r["limits"] = { - "teamMember": -1, - "projects": -1, - "metadata": metadata.get_remaining_metadata_with_count(tenant_id)} - - c = tenants.get_by_tenant_id(tenant_id) - c.pop("createdAt") - c["projects"] = projects.get_projects(tenant_id=tenant_id, recording_state=True, recorded=True, - stack_integrations=True, user_id=user_id) - c["smtp"] = helper.has_smtp() - c["iceServers"] = assist.get_ice_servers() return { - 'jwt': r.pop('jwt'), - 'data': { - "user": r, - "client": c, - } + 'jwt': r.pop('jwt') } @@ -603,19 +591,6 @@ def set_password_invitation(tenant_id, user_id, new_password): } -def count_members(tenant_id): - with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify( - """SELECT - COUNT(user_id) - FROM public.users WHERE tenant_id = %(tenant_id)s AND deleted_at IS NULL;""", - {"tenant_id": tenant_id}) - ) - r = cur.fetchone() - return r["count"] - - def email_exists(email): with pg_client.PostgresClient() as cur: cur.execute( @@ -678,12 +653,12 @@ def auth_exists(user_id, tenant_id, jwt_iat, jwt_aud): ) r = cur.fetchone() return r is not None \ - and r.get("jwt_iat") is not None \ - and (abs(jwt_iat - TimeUTC.datetime_to_timestamp(r["jwt_iat"]) // 1000) <= 1 \ - or (jwt_aud.startswith("plugin") \ - and (r["changed_at"] is None \ - or jwt_iat >= (TimeUTC.datetime_to_timestamp(r["changed_at"]) // 1000))) - ) + and r.get("jwt_iat") is not None \ + and (abs(jwt_iat - TimeUTC.datetime_to_timestamp(r["jwt_iat"]) // 1000) <= 1 \ + or (jwt_aud.startswith("plugin") \ + and (r["changed_at"] is None \ + or jwt_iat >= (TimeUTC.datetime_to_timestamp(r["changed_at"]) // 1000))) + ) def change_jwt_iat(user_id): @@ -741,14 +716,19 @@ def authenticate(email, password, for_change_password=False): if for_change_password: return True r = helper.dict_to_camel_case(r) + if config("enforce_SSO", cast=bool, default=False) and helper.is_saml2_available(): + return {"errors": ["must sign-in with SSO, enforced by admin"]} + jwt_iat = change_jwt_iat(r['userId']) + iat = TimeUTC.datetime_to_timestamp(jwt_iat) return { - "jwt": authorizers.generate_jwt(r['userId'], r['tenantId'], - TimeUTC.datetime_to_timestamp(jwt_iat), + "jwt": authorizers.generate_jwt(r['userId'], r['tenantId'], iat=iat, aud=f"front:{helper.get_stage_name()}"), "email": email, **r } + if config("enforce_SSO", cast=bool, default=False) and helper.is_saml2_available(): + return {"errors": ["must sign-in with SSO, enforced by admin"]} return None @@ -776,7 +756,7 @@ def authenticate_sso(email, internal_id, exp=None): r = helper.dict_to_camel_case(r) jwt_iat = TimeUTC.datetime_to_timestamp(change_jwt_iat(r['userId'])) return authorizers.generate_jwt(r['userId'], r['tenantId'], - jwt_iat, aud=f"front:{helper.get_stage_name()}", + iat=jwt_iat, aud=f"front:{helper.get_stage_name()}", exp=(exp + jwt_iat // 1000) if exp is not None else None) return None @@ -864,3 +844,12 @@ def restore_sso_user(user_id, tenant_id, email, admin, name, origin, role_id, in query ) return helper.dict_to_camel_case(cur.fetchone()) + + +def __hard_delete_user(user_id): + with pg_client.PostgresClient() as cur: + query = cur.mogrify( + f"""DELETE FROM public.users + WHERE users.user_id = %(user_id)s AND users.deleted_at IS NOT NULL ;""", + {"user_id": user_id}) + cur.execute(query) diff --git a/ee/api/chalicelib/core/webhook.py b/ee/api/chalicelib/core/webhook.py index 4ef0be6a9..d1e70d3e7 100644 --- a/ee/api/chalicelib/core/webhook.py +++ b/ee/api/chalicelib/core/webhook.py @@ -1,7 +1,11 @@ import logging +from typing import Optional import requests +from fastapi import HTTPException +from starlette import status +import schemas from chalicelib.utils import pg_client, helper from chalicelib.utils.TimeUTC import TimeUTC @@ -12,7 +16,7 @@ def get_by_id(webhook_id): cur.mogrify("""\ SELECT w.* FROM public.webhooks AS w - where w.webhook_id =%(webhook_id)s AND deleted_at ISNULL;""", + WHERE w.webhook_id =%(webhook_id)s AND deleted_at ISNULL;""", {"webhook_id": webhook_id}) ) w = helper.dict_to_camel_case(cur.fetchone()) @@ -21,15 +25,14 @@ def get_by_id(webhook_id): return w -def get(tenant_id, webhook_id): +def get_webhook(tenant_id, webhook_id, webhook_type='webhook'): with pg_client.PostgresClient() as cur: cur.execute( - cur.mogrify("""\ - SELECT - webhook_id AS integration_id, webhook_id AS id, w.* - FROM public.webhooks AS w - where w.webhook_id =%(webhook_id)s AND w.tenant_id =%(tenant_id)s AND deleted_at ISNULL;""", - {"webhook_id": webhook_id, "tenant_id": tenant_id}) + cur.mogrify("""SELECT w.* + FROM public.webhooks AS w + WHERE w.webhook_id =%(webhook_id)s AND w.tenant_id =%(tenant_id)s + AND deleted_at ISNULL AND type=%(webhook_type)s;""", + {"webhook_id": webhook_id, "webhook_type": webhook_type, "tenant_id": tenant_id}) ) w = helper.dict_to_camel_case(cur.fetchone()) if w: @@ -40,9 +43,7 @@ def get(tenant_id, webhook_id): def get_by_type(tenant_id, webhook_type): with pg_client.PostgresClient() as cur: cur.execute( - cur.mogrify("""\ - SELECT - w.webhook_id AS integration_id, w.webhook_id AS id,w.webhook_id,w.endpoint,w.auth_header,w.type,w.index,w.name,w.created_at + cur.mogrify("""SELECT w.webhook_id,w.webhook_id,w.endpoint,w.auth_header,w.type,w.index,w.name,w.created_at FROM public.webhooks AS w WHERE w.tenant_id =%(tenant_id)s AND w.type =%(type)s @@ -58,25 +59,15 @@ def get_by_type(tenant_id, webhook_type): def get_by_tenant(tenant_id, replace_none=False): with pg_client.PostgresClient() as cur: cur.execute( - cur.mogrify("""\ - SELECT - webhook_id AS integration_id, webhook_id AS id,w.* - FROM public.webhooks AS w - where - w.tenant_id =%(tenant_id)s - AND deleted_at ISNULL;""", + cur.mogrify("""SELECT w.* + FROM public.webhooks AS w + WHERE w.tenant_id =%(tenant_id)s + AND deleted_at ISNULL;""", {"tenant_id": tenant_id}) ) all = helper.list_to_camel_case(cur.fetchall()) - if replace_none: - for w in all: - w["createdAt"] = TimeUTC.datetime_to_timestamp(w["createdAt"]) - for k in w.keys(): - if w[k] is None: - w[k] = '' - else: - for w in all: - w["createdAt"] = TimeUTC.datetime_to_timestamp(w["createdAt"]) + for w in all: + w["createdAt"] = TimeUTC.datetime_to_timestamp(w["createdAt"]) return all @@ -89,7 +80,7 @@ def update(tenant_id, webhook_id, changes, replace_none=False): UPDATE public.webhooks SET {','.join(sub_query)} WHERE tenant_id =%(tenant_id)s AND webhook_id =%(id)s AND deleted_at ISNULL - RETURNING webhook_id AS integration_id, webhook_id AS id,*;""", + RETURNING *;""", {"tenant_id": tenant_id, "id": webhook_id, **changes}) ) w = helper.dict_to_camel_case(cur.fetchone()) @@ -106,7 +97,7 @@ def add(tenant_id, endpoint, auth_header=None, webhook_type='webhook', name="", query = cur.mogrify("""\ INSERT INTO public.webhooks(tenant_id, endpoint,auth_header,type,name) VALUES (%(tenant_id)s, %(endpoint)s, %(auth_header)s, %(type)s,%(name)s) - RETURNING webhook_id AS integration_id, webhook_id AS id,*;""", + RETURNING *;""", {"tenant_id": tenant_id, "endpoint": endpoint, "auth_header": auth_header, "type": webhook_type, "name": name}) cur.execute( @@ -121,7 +112,27 @@ def add(tenant_id, endpoint, auth_header=None, webhook_type='webhook', name="", return w +def exists_by_name(tenant_id: int, name: str, exclude_id: Optional[int], + webhook_type: str = schemas.WebhookType.webhook) -> bool: + with pg_client.PostgresClient() as cur: + query = cur.mogrify(f"""SELECT EXISTS(SELECT 1 + FROM public.webhooks + WHERE name ILIKE %(name)s + AND deleted_at ISNULL + AND tenant_id=%(tenant_id)s + AND type=%(webhook_type)s + {"AND webhook_id!=%(exclude_id)s" if exclude_id else ""}) AS exists;""", + {"tenant_id": tenant_id, "name": name, "exclude_id": exclude_id, + "webhook_type": webhook_type}) + cur.execute(query) + row = cur.fetchone() + return row["exists"] + + def add_edit(tenant_id, data, replace_none=None): + if "name" in data and len(data["name"]) > 0 \ + and exists_by_name(name=data["name"], exclude_id=data.get("webhookId"), tenant_id=tenant_id): + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"name already exists.") if data.get("webhookId") is not None: return update(tenant_id=tenant_id, webhook_id=data["webhookId"], changes={"endpoint": data["endpoint"], diff --git a/ee/api/chalicelib/utils/ch_client.py b/ee/api/chalicelib/utils/ch_client.py index 514820212..576bbc590 100644 --- a/ee/api/chalicelib/utils/ch_client.py +++ b/ee/api/chalicelib/utils/ch_client.py @@ -20,7 +20,8 @@ class ClickHouseClient: def __init__(self): self.__client = clickhouse_driver.Client(host=config("ch_host"), - database="default", + database=config("ch_database",default="default", cast=str), + password=config("ch_password",default="", cast=str), port=config("ch_port", cast=int), settings=settings) \ if self.__client is None else self.__client diff --git a/ee/api/chalicelib/utils/events_queue.py b/ee/api/chalicelib/utils/events_queue.py new file mode 100644 index 000000000..d95938857 --- /dev/null +++ b/ee/api/chalicelib/utils/events_queue.py @@ -0,0 +1,80 @@ +import json +import queue +import logging + +from chalicelib.utils import pg_client + +global_queue = None + +class EventQueue(): + + def __init__(self, test=False, queue_max_length=100): + self.events = queue.Queue() + self.events.maxsize = queue_max_length + self.test = test + + def flush(self, conn): + events = list() + params = dict() + # while not self.events.empty(): + # project_id, user_id, element = self.events.get() + # events.append("({project_id}, {user_id}, {timestamp}, '{action}', '{source}', '{category}', '{data}')".format( + # project_id=project_id, user_id=user_id, timestamp=element.timestamp, action=element.action, source=element.source, category=element.category, data=json.dumps(element.data))) + i = 0 + while not self.events.empty(): + project_id, user_id, element = self.events.get() + params[f'project_id_{i}'] = project_id + params[f'user_id_{i}'] = user_id + for _key, _val in element.dict().items(): + if _key == 'data': + params[f'{_key}_{i}'] = json.dumps(_val) + else: + params[f'{_key}_{i}'] = _val + events.append(f"(%(project_id_{i})s, %(user_id_{i})s, %(timestamp_{i})s, %(action_{i})s, %(source_{i})s, %(category_{i})s, %(data_{i})s::jsonb)") + i += 1 + if i == 0: + return 0 + if self.test: + print(events) + return 1 + conn.execute( + conn.mogrify(f"""INSERT INTO public.frontend_signals (project_id, user_id, timestamp, action, source, category, data) + VALUES {' , '.join(events)}""", params) + ) + return 1 + + def force_flush(self): + if not self.events.empty(): + try: + with pg_client.PostgresClient() as conn: + self.flush(conn) + except Exception as e: + logging.info(f'Error: {e}') + + def put(self, element): + if self.events.full(): + try: + with pg_client.PostgresClient() as conn: + self.flush(conn) + except Exception as e: + logging.info(f'Error: {e}') + self.events.put(element) + self.events.task_done() + +async def init(test=False): + global global_queue + global_queue = EventQueue(test=test) + logging.info("> queue initialized") + +async def terminate(): + global global_queue + if global_queue is not None: + global_queue.force_flush() + logging.info('> queue fulshed') + +# def __process_schema(trace): +# data = trace.dict() +# data["parameters"] = json.dumps(trace.parameters) if trace.parameters is not None and len( +# trace.parameters.keys()) > 0 else None +# data["payload"] = json.dumps(trace.payload) if trace.payload is not None and len(trace.payload.keys()) > 0 else None +# return data diff --git a/ee/api/chalicelib/utils/exp_ch_helper.py b/ee/api/chalicelib/utils/exp_ch_helper.py index 91b3a9c1d..3295de117 100644 --- a/ee/api/chalicelib/utils/exp_ch_helper.py +++ b/ee/api/chalicelib/utils/exp_ch_helper.py @@ -8,19 +8,19 @@ if config("EXP_7D_MV", cast=bool, default=True): print(">>> Using experimental last 7 days materialized views") -def get_main_events_table(timestamp): +def get_main_events_table(timestamp=0): return "experimental.events_l7d_mv" \ if config("EXP_7D_MV", cast=bool, default=True) \ and timestamp >= TimeUTC.now(delta_days=-7) else "experimental.events" -def get_main_sessions_table(timestamp): +def get_main_sessions_table(timestamp=0): return "experimental.sessions_l7d_mv" \ if config("EXP_7D_MV", cast=bool, default=True) \ and timestamp >= TimeUTC.now(delta_days=-7) else "experimental.sessions" -def get_main_resources_table(timestamp): +def get_main_resources_table(timestamp=0): return "experimental.resources_l7d_mv" \ if config("EXP_7D_MV", cast=bool, default=True) \ and timestamp >= TimeUTC.now(delta_days=-7) else "experimental.resources" diff --git a/ee/api/chalicelib/utils/s3_extra.py b/ee/api/chalicelib/utils/s3_extra.py index 0e594c890..d561aa6bf 100644 --- a/ee/api/chalicelib/utils/s3_extra.py +++ b/ee/api/chalicelib/utils/s3_extra.py @@ -7,6 +7,10 @@ def tag_session(file_key, tag_key='retention', tag_value='vault'): return tag_file(file_key=file_key, bucket=config("sessions_bucket"), tag_key=tag_key, tag_value=tag_value) +def tag_record(file_key, tag_key='retention', tag_value='vault'): + return tag_file(file_key=file_key, bucket=config('ASSIST_RECORDS_BUCKET'), tag_key=tag_key, tag_value=tag_value) + + def tag_file(file_key, bucket, tag_key, tag_value): return s3.client.put_object_tagging( Bucket=bucket, diff --git a/ee/api/clean.sh b/ee/api/clean-dev.sh similarity index 91% rename from ee/api/clean.sh rename to ee/api/clean-dev.sh index 602a47222..acc91e7b7 100755 --- a/ee/api/clean.sh +++ b/ee/api/clean-dev.sh @@ -4,14 +4,17 @@ rm -rf ./chalicelib/core/alerts.py #exp rm -rf ./chalicelib/core/alerts_processor.py rm -rf ./chalicelib/core/announcements.py rm -rf ./chalicelib/core/autocomplete.py +rm -rf ./chalicelib/core/click_maps.py +rm -rf ./chalicelib/core/collaboration_base.py +rm -rf ./chalicelib/core/collaboration_msteams.py rm -rf ./chalicelib/core/collaboration_slack.py rm -rf ./chalicelib/core/countries.py #exp rm -rf ./chalicelib/core/errors.py rm -rf ./chalicelib/core/errors_favorite.py #exp rm -rf ./chalicelib/core/events.py rm -rf ./chalicelib/core/events_ios.py -#exp rm -rf ./chalicelib/core/dashboards.py -#exp rm -rf ./chalicelib/core/funnels.py +rm -rf ./chalicelib/core/dashboards.py +rm -rf ./chalicelib/core/funnels.py rm -rf ./chalicelib/core/integration_base.py rm -rf ./chalicelib/core/integration_base_issue.py rm -rf ./chalicelib/core/integration_github.py @@ -36,7 +39,6 @@ rm -rf ./chalicelib/core/sessions_assignments.py #exp rm -rf ./chalicelib/core/sessions_metas.py rm -rf ./chalicelib/core/sessions_mobs.py #exp rm -rf ./chalicelib/core/significance.py -rm -rf ./chalicelib/core/slack.py rm -rf ./chalicelib/core/socket_ios.py rm -rf ./chalicelib/core/sourcemaps.py rm -rf ./chalicelib/core/sourcemaps_parser.py @@ -48,6 +50,7 @@ rm -rf ./chalicelib/utils/captcha.py rm -rf ./chalicelib/utils/dev.py rm -rf ./chalicelib/utils/email_handler.py rm -rf ./chalicelib/utils/email_helper.py +rm -rf ./chalicelib/utils/errors_helper.py rm -rf ./chalicelib/utils/event_filter_definition.py rm -rf ./chalicelib/utils/github_client_v3.py rm -rf ./chalicelib/utils/helper.py @@ -56,6 +59,7 @@ rm -rf ./chalicelib/utils/metrics_helper.py rm -rf ./chalicelib/utils/pg_client.py rm -rf ./chalicelib/utils/s3.py rm -rf ./chalicelib/utils/smtp.py +rm -rf ./chalicelib/utils/sql_helper.py rm -rf ./chalicelib/utils/strings.py rm -rf ./chalicelib/utils/TimeUTC.py rm -rf ./routers/app/__init__.py diff --git a/ee/api/env.default b/ee/api/env.default index f5574a8a1..cdbc3d256 100644 --- a/ee/api/env.default +++ b/ee/api/env.default @@ -32,6 +32,7 @@ JWT_EXPIRATION=2592000 JWT_ISSUER=openreplay-ee jwt_secret="SET A RANDOM STRING HERE" ASSIST_URL=http://assist-openreplay.app.svc.cluster.local:9001/assist/%s +ASSIST_KEY= assist=/sockets-live assistList=/sockets-list pg_dbname=postgres @@ -45,6 +46,7 @@ PG_MAXCONN=50 PG_RETRY_MAX=50 PG_RETRY_INTERVAL=2 PG_POOL=true +ASSIST_RECORDS_BUCKET=records sessions_bucket=mobs sessions_region=us-east-1 sourcemaps_bucket=sourcemaps diff --git a/ee/api/requirements-alerts.txt b/ee/api/requirements-alerts.txt index 02042a778..250882623 100644 --- a/ee/api/requirements-alerts.txt +++ b/ee/api/requirements-alerts.txt @@ -1,18 +1,18 @@ -requests==2.28.1 -urllib3==1.26.12 -boto3==1.26.14 +requests==2.28.2 +urllib3==1.26.14 +boto3==1.26.70 pyjwt==2.6.0 psycopg2-binary==2.9.5 -elasticsearch==8.5.1 +elasticsearch==8.6.1 jira==3.4.1 -fastapi==0.87.0 +fastapi==0.92.0 uvicorn[standard]==0.20.0 -python-decouple==3.6 -pydantic[email]==1.10.2 -apscheduler==3.9.1.post1 +python-decouple==3.7 +pydantic[email]==1.10.4 +apscheduler==3.10.0 -clickhouse-driver==0.2.4 +clickhouse-driver==0.2.5 python-multipart==0.0.5 \ No newline at end of file diff --git a/ee/api/requirements-crons.txt b/ee/api/requirements-crons.txt index 02042a778..5f3742cdd 100644 --- a/ee/api/requirements-crons.txt +++ b/ee/api/requirements-crons.txt @@ -1,18 +1,13 @@ -requests==2.28.1 -urllib3==1.26.12 -boto3==1.26.14 +requests==2.28.2 +urllib3==1.26.14 +boto3==1.26.70 pyjwt==2.6.0 psycopg2-binary==2.9.5 -elasticsearch==8.5.1 +elasticsearch==8.6.1 jira==3.4.1 -fastapi==0.87.0 -uvicorn[standard]==0.20.0 -python-decouple==3.6 -pydantic[email]==1.10.2 -apscheduler==3.9.1.post1 +apscheduler==3.10.0 -clickhouse-driver==0.2.4 -python-multipart==0.0.5 \ No newline at end of file +clickhouse-driver==0.2.5 \ No newline at end of file diff --git a/ee/api/requirements.txt b/ee/api/requirements.txt index ac4f27a9d..c8b76e700 100644 --- a/ee/api/requirements.txt +++ b/ee/api/requirements.txt @@ -1,19 +1,19 @@ -requests==2.28.1 -urllib3==1.26.12 -boto3==1.26.14 +requests==2.28.2 +urllib3==1.26.14 +boto3==1.26.70 pyjwt==2.6.0 psycopg2-binary==2.9.5 -elasticsearch==8.5.1 +elasticsearch==8.6.1 jira==3.4.1 -fastapi==0.87.0 +fastapi==0.92.0 uvicorn[standard]==0.20.0 -python-decouple==3.6 -pydantic[email]==1.10.2 -apscheduler==3.9.1.post1 +python-decouple==3.7 +pydantic[email]==1.10.4 +apscheduler==3.10.0 -clickhouse-driver==0.2.4 -python3-saml==1.14.0 -python-multipart==0.0.5 \ No newline at end of file +clickhouse-driver==0.2.5 +python3-saml==1.15.0 +python-multipart==0.0.5 diff --git a/ee/api/routers/core_dynamic.py b/ee/api/routers/core_dynamic.py index fb24aec96..8c8aa55b6 100644 --- a/ee/api/routers/core_dynamic.py +++ b/ee/api/routers/core_dynamic.py @@ -1,13 +1,13 @@ from typing import Optional, Union from decouple import config -from fastapi import Body, Depends, BackgroundTasks +from fastapi import Body, Depends, BackgroundTasks, Request from starlette.responses import RedirectResponse, FileResponse import schemas import schemas_ee from chalicelib.core import sessions, assist, heatmaps, sessions_favorite, sessions_assignments, errors, errors_viewed, \ - errors_favorite, sessions_notes + errors_favorite, sessions_notes, click_maps from chalicelib.core import sessions_viewed from chalicelib.core import tenants, users, projects, license from chalicelib.core import webhook @@ -16,6 +16,7 @@ from chalicelib.utils import SAML2_helper from chalicelib.utils import helper from chalicelib.utils.TimeUTC import TimeUTC from or_dependencies import OR_context, OR_scope +from routers import saml from routers.base import get_routers from schemas_ee import Permissions @@ -23,7 +24,7 @@ public_app, app, app_apikey = get_routers() @public_app.get('/signup', tags=['signup']) -def get_all_signup(): +async def get_all_signup(): return {"data": {"tenants": tenants.tenants_exists(), "sso": SAML2_helper.is_saml2_available(), "ssoProvider": SAML2_helper.get_saml2_provider(), @@ -31,7 +32,7 @@ def get_all_signup(): @app.get('/account', tags=['accounts']) -def get_account(context: schemas.CurrentContext = Depends(OR_context)): +async def get_account(context: schemas.CurrentContext = Depends(OR_context)): r = users.get(tenant_id=context.tenant_id, user_id=context.user_id) t = tenants.get_by_tenant_id(context.tenant_id) if t is not None: @@ -50,33 +51,16 @@ def get_account(context: schemas.CurrentContext = Depends(OR_context)): @app.post('/account', tags=["account"]) -def edit_account(data: schemas_ee.EditUserSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def edit_account(data: schemas_ee.EditUserSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return users.edit(tenant_id=context.tenant_id, user_id_to_update=context.user_id, changes=data, editor_id=context.user_id) -@app.get('/projects/limit', tags=['projects']) -def get_projects_limit(context: schemas.CurrentContext = Depends(OR_context)): - return {"data": { - "current": projects.count_by_tenant(tenant_id=context.tenant_id), - "remaining": -1 - }} - - -@app.get('/projects/{projectId}', tags=['projects']) -def get_project(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): - data = projects.get_project(tenant_id=context.tenant_id, project_id=projectId, include_last_session=True, - include_gdpr=True) - if data is None: - return {"errors": ["project not found"]} - return {"data": data} - - @app.post('/integrations/slack', tags=['integrations']) @app.put('/integrations/slack', tags=['integrations']) -def add_slack_client(data: schemas.AddSlackSchema, context: schemas.CurrentContext = Depends(OR_context)): - n = Slack.add_channel(tenant_id=context.tenant_id, url=data.url, name=data.name) +async def add_slack_client(data: schemas.AddCollaborationSchema, context: schemas.CurrentContext = Depends(OR_context)): + n = Slack.add(tenant_id=context.tenant_id, data=data) if n is None: return { "errors": ["We couldn't send you a test message on your Slack channel. Please verify your webhook url."] @@ -85,10 +69,12 @@ def add_slack_client(data: schemas.AddSlackSchema, context: schemas.CurrentConte @app.post('/integrations/slack/{integrationId}', tags=['integrations']) -def edit_slack_integration(integrationId: int, data: schemas.EditSlackSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def edit_slack_integration(integrationId: int, data: schemas.EditCollaborationSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): if len(data.url) > 0: - old = webhook.get(tenant_id=context.tenant_id, webhook_id=integrationId) + old = Slack.get_integration(tenant_id=context.tenant_id, integration_id=integrationId) + if not old: + return {"errors": ["Slack integration not found."]} if old["endpoint"] != data.url: if not Slack.say_hello(data.url): return { @@ -100,14 +86,16 @@ def edit_slack_integration(integrationId: int, data: schemas.EditSlackSchema = B @app.post('/client/members', tags=["client"]) -def add_member(background_tasks: BackgroundTasks, data: schemas_ee.CreateMemberSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def add_member(background_tasks: BackgroundTasks, data: schemas_ee.CreateMemberSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return users.create_member(tenant_id=context.tenant_id, user_id=context.user_id, data=data.dict(), background_tasks=background_tasks) @public_app.get('/users/invitation', tags=['users']) -def process_invitation_link(token: str): +async def process_invitation_link(token: str, request: Request): + if config("enforce_SSO", cast=bool, default=False) and helper.is_saml2_available(): + return await saml.start_sso(request=request) if token is None or len(token) < 64: return {"errors": ["please provide a valid invitation"]} user = users.get_by_invitation_token(token) @@ -124,7 +112,7 @@ def process_invitation_link(token: str): @public_app.post('/password/reset', tags=["users"]) -def change_password_by_invitation(data: schemas.EditPasswordByInvitationSchema = Body(...)): +async def change_password_by_invitation(data: schemas.EditPasswordByInvitationSchema = Body(...)): if data is None or len(data.invitation) < 64 or len(data.passphrase) < 8: return {"errors": ["please provide a valid invitation & pass"]} user = users.get_by_invitation_token(token=data.invitation, pass_token=data.passphrase) @@ -137,15 +125,15 @@ def change_password_by_invitation(data: schemas.EditPasswordByInvitationSchema = @app.put('/client/members/{memberId}', tags=["client"]) -def edit_member(memberId: int, data: schemas_ee.EditMemberSchema, - context: schemas.CurrentContext = Depends(OR_context)): +async def edit_member(memberId: int, data: schemas_ee.EditMemberSchema, + context: schemas.CurrentContext = Depends(OR_context)): return users.edit_member(tenant_id=context.tenant_id, editor_id=context.user_id, changes=data, user_id_to_update=memberId) @app.get('/metadata/session_search', tags=["metadata"]) -def search_sessions_by_metadata(key: str, value: str, projectId: Optional[int] = None, - context: schemas.CurrentContext = Depends(OR_context)): +async def search_sessions_by_metadata(key: str, value: str, projectId: Optional[int] = None, + context: schemas.CurrentContext = Depends(OR_context)): if key is None or value is None or len(value) == 0 and len(key) == 0: return {"errors": ["please provide a key&value for search"]} @@ -161,20 +149,15 @@ def search_sessions_by_metadata(key: str, value: str, projectId: Optional[int] = m_key=key, project_id=projectId)} -@public_app.get('/general_stats', tags=["private"], include_in_schema=False) -def get_general_stats(): - return {"data": {"sessions:": sessions.count_all()}} - - @app.get('/projects', tags=['projects']) -def get_projects(context: schemas.CurrentContext = Depends(OR_context)): +async def get_projects(context: schemas.CurrentContext = Depends(OR_context)): return {"data": projects.get_projects(tenant_id=context.tenant_id, recording_state=True, gdpr=True, recorded=True, stack_integrations=True, user_id=context.user_id)} @app.get('/{projectId}/sessions/{sessionId}', tags=["sessions"], dependencies=[OR_scope(Permissions.session_replay)]) -def get_session(projectId: int, sessionId: Union[int, str], background_tasks: BackgroundTasks, - context: schemas.CurrentContext = Depends(OR_context)): +async def get_session(projectId: int, sessionId: Union[int, str], background_tasks: BackgroundTasks, + context: schemas.CurrentContext = Depends(OR_context)): if isinstance(sessionId, str): return {"errors": ["session not found"]} data = sessions.get_by_id2_pg(project_id=projectId, session_id=sessionId, full_data=True, @@ -191,8 +174,8 @@ def get_session(projectId: int, sessionId: Union[int, str], background_tasks: Ba @app.get('/{projectId}/sessions/{sessionId}/errors/{errorId}/sourcemaps', tags=["sessions", "sourcemaps"], dependencies=[OR_scope(Permissions.dev_tools)]) -def get_error_trace(projectId: int, sessionId: int, errorId: str, - context: schemas.CurrentContext = Depends(OR_context)): +async def get_error_trace(projectId: int, sessionId: int, errorId: str, + context: schemas.CurrentContext = Depends(OR_context)): data = errors.get_trace(project_id=projectId, error_id=errorId) if "errors" in data: return data @@ -202,20 +185,20 @@ def get_error_trace(projectId: int, sessionId: int, errorId: str, @app.post('/{projectId}/errors/search', tags=['errors'], dependencies=[OR_scope(Permissions.dev_tools)]) -def errors_search(projectId: int, data: schemas.SearchErrorsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def errors_search(projectId: int, data: schemas.SearchErrorsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return {"data": errors.search(data, projectId, user_id=context.user_id)} @app.get('/{projectId}/errors/stats', tags=['errors'], dependencies=[OR_scope(Permissions.dev_tools)]) -def errors_stats(projectId: int, startTimestamp: int, endTimestamp: int, - context: schemas.CurrentContext = Depends(OR_context)): +async def errors_stats(projectId: int, startTimestamp: int, endTimestamp: int, + context: schemas.CurrentContext = Depends(OR_context)): return errors.stats(projectId, user_id=context.user_id, startTimestamp=startTimestamp, endTimestamp=endTimestamp) @app.get('/{projectId}/errors/{errorId}', tags=['errors'], dependencies=[OR_scope(Permissions.dev_tools)]) -def errors_get_details(projectId: int, errorId: str, background_tasks: BackgroundTasks, density24: int = 24, - density30: int = 30, context: schemas.CurrentContext = Depends(OR_context)): +async def errors_get_details(projectId: int, errorId: str, background_tasks: BackgroundTasks, density24: int = 24, + density30: int = 30, context: schemas.CurrentContext = Depends(OR_context)): data = errors.get_details(project_id=projectId, user_id=context.user_id, error_id=errorId, **{"density24": density24, "density30": density30}) if data.get("data") is not None: @@ -225,17 +208,17 @@ def errors_get_details(projectId: int, errorId: str, background_tasks: Backgroun @app.get('/{projectId}/errors/{errorId}/stats', tags=['errors'], dependencies=[OR_scope(Permissions.dev_tools)]) -def errors_get_details_right_column(projectId: int, errorId: str, startDate: int = TimeUTC.now(-7), - endDate: int = TimeUTC.now(), density: int = 7, - context: schemas.CurrentContext = Depends(OR_context)): +async def errors_get_details_right_column(projectId: int, errorId: str, startDate: int = TimeUTC.now(-7), + endDate: int = TimeUTC.now(), density: int = 7, + context: schemas.CurrentContext = Depends(OR_context)): data = errors.get_details_chart(project_id=projectId, user_id=context.user_id, error_id=errorId, **{"startDate": startDate, "endDate": endDate, "density": density}) return data @app.get('/{projectId}/errors/{errorId}/sourcemaps', tags=['errors'], dependencies=[OR_scope(Permissions.dev_tools)]) -def errors_get_details_sourcemaps(projectId: int, errorId: str, - context: schemas.CurrentContext = Depends(OR_context)): +async def errors_get_details_sourcemaps(projectId: int, errorId: str, + context: schemas.CurrentContext = Depends(OR_context)): data = errors.get_trace(project_id=projectId, error_id=errorId) if "errors" in data: return data @@ -245,8 +228,9 @@ def errors_get_details_sourcemaps(projectId: int, errorId: str, @app.get('/{projectId}/errors/{errorId}/{action}', tags=["errors"], dependencies=[OR_scope(Permissions.dev_tools)]) -def add_remove_favorite_error(projectId: int, errorId: str, action: str, startDate: int = TimeUTC.now(-7), - endDate: int = TimeUTC.now(), context: schemas.CurrentContext = Depends(OR_context)): +async def add_remove_favorite_error(projectId: int, errorId: str, action: str, startDate: int = TimeUTC.now(-7), + endDate: int = TimeUTC.now(), + context: schemas.CurrentContext = Depends(OR_context)): if action == "favorite": return errors_favorite.favorite_error(project_id=projectId, user_id=context.user_id, error_id=errorId) elif action == "sessions": @@ -262,12 +246,12 @@ def add_remove_favorite_error(projectId: int, errorId: str, action: str, startDa @app.get('/{projectId}/assist/sessions/{sessionId}', tags=["assist"], dependencies=[OR_scope(Permissions.assist_live)]) -def get_live_session(projectId: int, sessionId: str, background_tasks: BackgroundTasks, - context: schemas_ee.CurrentContext = Depends(OR_context)): +async def get_live_session(projectId: int, sessionId: str, background_tasks: BackgroundTasks, + context: schemas_ee.CurrentContext = Depends(OR_context)): data = assist.get_live_session_by_id(project_id=projectId, session_id=sessionId) if data is None: - data = sessions.get_by_id2_pg(context=context, project_id=projectId, session_id=sessionId, full_data=True, - include_fav_viewed=True, group_metadata=True, live=False) + data = sessions.get_by_id2_pg(context=context, project_id=projectId, session_id=sessionId, + full_data=True, include_fav_viewed=True, group_metadata=True, live=False) if data is None: return {"errors": ["session not found"]} if data.get("inDB"): @@ -278,8 +262,8 @@ def get_live_session(projectId: int, sessionId: str, background_tasks: Backgroun @app.get('/{projectId}/unprocessed/{sessionId}/dom.mob', tags=["assist"], dependencies=[OR_scope(Permissions.assist_live, Permissions.session_replay)]) -def get_live_session_replay_file(projectId: int, sessionId: Union[int, str], - context: schemas.CurrentContext = Depends(OR_context)): +async def get_live_session_replay_file(projectId: int, sessionId: Union[int, str], + context: schemas.CurrentContext = Depends(OR_context)): not_found = {"errors": ["Replay file not found"]} if isinstance(sessionId, str): print(f"{sessionId} not a valid number.") @@ -299,8 +283,8 @@ def get_live_session_replay_file(projectId: int, sessionId: Union[int, str], @app.get('/{projectId}/unprocessed/{sessionId}/devtools.mob', tags=["assist"], dependencies=[OR_scope(Permissions.assist_live, Permissions.session_replay, Permissions.dev_tools)]) -def get_live_session_devtools_file(projectId: int, sessionId: Union[int, str], - context: schemas.CurrentContext = Depends(OR_context)): +async def get_live_session_devtools_file(projectId: int, sessionId: Union[int, str], + context: schemas.CurrentContext = Depends(OR_context)): not_found = {"errors": ["Devtools file not found"]} if isinstance(sessionId, str): print(f"{sessionId} not a valid number.") @@ -319,22 +303,22 @@ def get_live_session_devtools_file(projectId: int, sessionId: Union[int, str], @app.post('/{projectId}/heatmaps/url', tags=["heatmaps"], dependencies=[OR_scope(Permissions.session_replay)]) -def get_heatmaps_by_url(projectId: int, data: schemas.GetHeatmapPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): - return {"data": heatmaps.get_by_url(project_id=projectId, data=data.dict())} +async def get_heatmaps_by_url(projectId: int, data: schemas.GetHeatmapPayloadSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + return {"data": heatmaps.get_by_url(project_id=projectId, data=data)} @app.get('/{projectId}/sessions/{sessionId}/favorite', tags=["sessions"], dependencies=[OR_scope(Permissions.session_replay)]) -def add_remove_favorite_session2(projectId: int, sessionId: int, - context: schemas_ee.CurrentContext = Depends(OR_context)): +async def add_remove_favorite_session2(projectId: int, sessionId: int, + context: schemas_ee.CurrentContext = Depends(OR_context)): return { "data": sessions_favorite.favorite_session(context=context, project_id=projectId, session_id=sessionId)} @app.get('/{projectId}/sessions/{sessionId}/assign', tags=["sessions"], dependencies=[OR_scope(Permissions.session_replay)]) -def assign_session(projectId: int, sessionId, context: schemas.CurrentContext = Depends(OR_context)): +async def assign_session(projectId: int, sessionId, context: schemas.CurrentContext = Depends(OR_context)): data = sessions_assignments.get_by_session(project_id=projectId, session_id=sessionId, tenant_id=context.tenant_id, user_id=context.user_id) @@ -347,8 +331,8 @@ def assign_session(projectId: int, sessionId, context: schemas.CurrentContext = @app.get('/{projectId}/sessions/{sessionId}/assign/{issueId}', tags=["sessions", "issueTracking"], dependencies=[OR_scope(Permissions.session_replay)]) -def assign_session(projectId: int, sessionId: int, issueId: str, - context: schemas.CurrentContext = Depends(OR_context)): +async def assign_session(projectId: int, sessionId: int, issueId: str, + context: schemas.CurrentContext = Depends(OR_context)): data = sessions_assignments.get(project_id=projectId, session_id=sessionId, assignment_id=issueId, tenant_id=context.tenant_id, user_id=context.user_id) if "errors" in data: @@ -360,8 +344,9 @@ def assign_session(projectId: int, sessionId: int, issueId: str, @app.post('/{projectId}/sessions/{sessionId}/assign/{issueId}/comment', tags=["sessions", "issueTracking"], dependencies=[OR_scope(Permissions.session_replay)]) -def comment_assignment(projectId: int, sessionId: int, issueId: str, data: schemas.CommentAssignmentSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def comment_assignment(projectId: int, sessionId: int, issueId: str, + data: schemas.CommentAssignmentSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): data = sessions_assignments.comment(tenant_id=context.tenant_id, project_id=projectId, session_id=sessionId, assignment_id=issueId, user_id=context.user_id, message=data.message) @@ -374,8 +359,8 @@ def comment_assignment(projectId: int, sessionId: int, issueId: str, data: schem @app.post('/{projectId}/sessions/{sessionId}/notes', tags=["sessions", "notes"], dependencies=[OR_scope(Permissions.session_replay)]) -def create_note(projectId: int, sessionId: int, data: schemas.SessionNoteSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def create_note(projectId: int, sessionId: int, data: schemas.SessionNoteSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): if not sessions.session_exists(project_id=projectId, session_id=sessionId): return {"errors": ["Session not found"]} data = sessions_notes.create(tenant_id=context.tenant_id, project_id=projectId, @@ -389,7 +374,7 @@ def create_note(projectId: int, sessionId: int, data: schemas.SessionNoteSchema @app.get('/{projectId}/sessions/{sessionId}/notes', tags=["sessions", "notes"], dependencies=[OR_scope(Permissions.session_replay)]) -def get_session_notes(projectId: int, sessionId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def get_session_notes(projectId: int, sessionId: int, context: schemas.CurrentContext = Depends(OR_context)): data = sessions_notes.get_session_notes(tenant_id=context.tenant_id, project_id=projectId, session_id=sessionId, user_id=context.user_id) if "errors" in data: @@ -401,8 +386,8 @@ def get_session_notes(projectId: int, sessionId: int, context: schemas.CurrentCo @app.post('/{projectId}/notes/{noteId}', tags=["sessions", "notes"], dependencies=[OR_scope(Permissions.session_replay)]) -def edit_note(projectId: int, noteId: int, data: schemas.SessionUpdateNoteSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def edit_note(projectId: int, noteId: int, data: schemas.SessionUpdateNoteSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): data = sessions_notes.edit(tenant_id=context.tenant_id, project_id=projectId, user_id=context.user_id, note_id=noteId, data=data) if "errors" in data.keys(): @@ -414,24 +399,38 @@ def edit_note(projectId: int, noteId: int, data: schemas.SessionUpdateNoteSchema @app.delete('/{projectId}/notes/{noteId}', tags=["sessions", "notes"], dependencies=[OR_scope(Permissions.session_replay)]) -def delete_note(projectId: int, noteId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_note(projectId: int, noteId: int, context: schemas.CurrentContext = Depends(OR_context)): data = sessions_notes.delete(tenant_id=context.tenant_id, project_id=projectId, user_id=context.user_id, note_id=noteId) return data -@app.get('/{projectId}/notes/{noteId}/slack/{webhookId}', tags=["sessions", "notes"]) -def share_note_to_slack(projectId: int, noteId: int, webhookId: int, - context: schemas.CurrentContext = Depends(OR_context)): +@app.get('/{projectId}/notes/{noteId}/slack/{webhookId}', tags=["sessions", "notes"], + dependencies=[OR_scope(Permissions.session_replay)]) +async def share_note_to_slack(projectId: int, noteId: int, webhookId: int, + context: schemas.CurrentContext = Depends(OR_context)): return sessions_notes.share_to_slack(tenant_id=context.tenant_id, project_id=projectId, user_id=context.user_id, note_id=noteId, webhook_id=webhookId) +@app.get('/{projectId}/notes/{noteId}/msteams/{webhookId}', tags=["sessions", "notes"]) +async def share_note_to_msteams(projectId: int, noteId: int, webhookId: int, + context: schemas.CurrentContext = Depends(OR_context)): + return sessions_notes.share_to_msteams(tenant_id=context.tenant_id, project_id=projectId, user_id=context.user_id, + note_id=noteId, webhook_id=webhookId) + + @app.post('/{projectId}/notes', tags=["sessions", "notes"], dependencies=[OR_scope(Permissions.session_replay)]) -def get_all_notes(projectId: int, data: schemas.SearchNoteSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def get_all_notes(projectId: int, data: schemas.SearchNoteSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): data = sessions_notes.get_all_notes_by_project_id(tenant_id=context.tenant_id, project_id=projectId, user_id=context.user_id, data=data) if "errors" in data: return data return {'data': data} + + +@app.post('/{projectId}/click_maps/search', tags=["click maps"], dependencies=[OR_scope(Permissions.session_replay)]) +async def click_map_search(projectId: int, data: schemas.FlatClickMapSessionsSearch = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + return {"data": click_maps.search_short_session(user_id=context.user_id, data=data, project_id=projectId)} diff --git a/ee/api/routers/crons/core_dynamic_crons.py b/ee/api/routers/crons/core_dynamic_crons.py index 1d8320eb7..0ea096546 100644 --- a/ee/api/routers/crons/core_dynamic_crons.py +++ b/ee/api/routers/crons/core_dynamic_crons.py @@ -1,8 +1,11 @@ -from chalicelib.core import telemetry, unlock -from chalicelib.core import jobs -from chalicelib.core import weekly_report as weekly_report_script +from apscheduler.triggers.cron import CronTrigger +from apscheduler.triggers.interval import IntervalTrigger from decouple import config +from chalicelib.core import jobs +from chalicelib.core import telemetry, unlock +from chalicelib.core import weekly_report as weekly_report_script + async def run_scheduled_jobs() -> None: jobs.execute_jobs() @@ -23,13 +26,16 @@ def unlock_cron() -> None: cron_jobs = [ - {"func": unlock_cron, "trigger": "cron", "hour": "*"} + {"func": unlock_cron, "trigger": "cron", "hour": "*"}, ] -SINGLE_CRONS = [{"func": telemetry_cron, "trigger": "cron", "day_of_week": "*"}, - {"func": run_scheduled_jobs, "trigger": "interval", "seconds": 60, "misfire_grace_time": 20}, - {"func": weekly_report, "trigger": "cron", "day_of_week": "mon", "hour": 5, - "misfire_grace_time": 60 * 60}] +SINGLE_CRONS = [{"func": telemetry_cron, "trigger": CronTrigger(day_of_week="*"), + "misfire_grace_time": 60 * 60, "max_instances": 1}, + {"func": run_scheduled_jobs, "trigger": IntervalTrigger(minutes=60), + "misfire_grace_time": 20, "max_instances": 1}, + {"func": weekly_report, "trigger": CronTrigger(day_of_week="mon", hour=5), + "misfire_grace_time": 60 * 60, "max_instances": 1} + ] if config("LOCAL_CRONS", default=False, cast=bool): cron_jobs += SINGLE_CRONS diff --git a/ee/api/routers/crons/ee_crons.py b/ee/api/routers/crons/ee_crons.py new file mode 100644 index 000000000..088424386 --- /dev/null +++ b/ee/api/routers/crons/ee_crons.py @@ -0,0 +1,12 @@ +from apscheduler.triggers.interval import IntervalTrigger + +from chalicelib.utils import events_queue + + +async def pg_events_queue() -> None: + events_queue.global_queue.force_flush() + + +ee_cron_jobs = [ + {"func": pg_events_queue, "trigger": IntervalTrigger(minutes=5), "misfire_grace_time": 20, "max_instances": 1}, +] diff --git a/ee/api/routers/ee.py b/ee/api/routers/ee.py index dc08ac569..4a6181bcb 100644 --- a/ee/api/routers/ee.py +++ b/ee/api/routers/ee.py @@ -1,12 +1,12 @@ -from chalicelib.core import roles, traces -from chalicelib.core import unlock +from chalicelib.core import roles, traces, assist_records, sessions +from chalicelib.core import unlock, signals +from chalicelib.core import sessions_insights from chalicelib.utils import assist_helper unlock.check() from or_dependencies import OR_context from routers.base import get_routers -import schemas import schemas_ee from fastapi import Depends, Body @@ -14,7 +14,7 @@ public_app, app, app_apikey = get_routers() @app.get('/client/roles', tags=["client", "roles"]) -def get_roles(context: schemas.CurrentContext = Depends(OR_context)): +async def get_roles(context: schemas_ee.CurrentContext = Depends(OR_context)): return { 'data': roles.get_roles(tenant_id=context.tenant_id) } @@ -22,7 +22,7 @@ def get_roles(context: schemas.CurrentContext = Depends(OR_context)): @app.post('/client/roles', tags=["client", "roles"]) @app.put('/client/roles', tags=["client", "roles"]) -def add_role(data: schemas_ee.RolePayloadSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): +async def add_role(data: schemas_ee.RolePayloadSchema = Body(...), context: schemas_ee.CurrentContext = Depends(OR_context)): data = roles.create(tenant_id=context.tenant_id, user_id=context.user_id, data=data) if "errors" in data: return data @@ -34,8 +34,8 @@ def add_role(data: schemas_ee.RolePayloadSchema = Body(...), context: schemas.Cu @app.post('/client/roles/{roleId}', tags=["client", "roles"]) @app.put('/client/roles/{roleId}', tags=["client", "roles"]) -def edit_role(roleId: int, data: schemas_ee.RolePayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def edit_role(roleId: int, data: schemas_ee.RolePayloadSchema = Body(...), + context: schemas_ee.CurrentContext = Depends(OR_context)): data = roles.update(tenant_id=context.tenant_id, user_id=context.user_id, role_id=roleId, data=data) if "errors" in data: return data @@ -46,7 +46,7 @@ def edit_role(roleId: int, data: schemas_ee.RolePayloadSchema = Body(...), @app.delete('/client/roles/{roleId}', tags=["client", "roles"]) -def delete_role(roleId: int, context: schemas.CurrentContext = Depends(OR_context)): +async def delete_role(roleId: int, context: schemas_ee.CurrentContext = Depends(OR_context)): data = roles.delete(tenant_id=context.tenant_id, user_id=context.user_id, role_id=roleId) if "errors" in data: return data @@ -56,18 +56,79 @@ def delete_role(roleId: int, context: schemas.CurrentContext = Depends(OR_contex @app.get('/assist/credentials', tags=["assist"]) -def get_assist_credentials(): +async def get_assist_credentials(): return {"data": assist_helper.get_full_config()} @app.post('/trails', tags=["traces", "trails"]) -def get_trails(data: schemas_ee.TrailSearchPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +async def get_trails(data: schemas_ee.TrailSearchPayloadSchema = Body(...), + context: schemas_ee.CurrentContext = Depends(OR_context)): return { 'data': traces.get_all(tenant_id=context.tenant_id, data=data) } @app.post('/trails/actions', tags=["traces", "trails"]) -def get_available_trail_actions(context: schemas.CurrentContext = Depends(OR_context)): +async def get_available_trail_actions(context: schemas_ee.CurrentContext = Depends(OR_context)): return {'data': traces.get_available_actions(tenant_id=context.tenant_id)} + + +@app.put('/{projectId}/assist/save', tags=["assist"]) +async def sign_record_for_upload(projectId: int, data: schemas_ee.AssistRecordPayloadSchema = Body(...), + context: schemas_ee.CurrentContext = Depends(OR_context)): + if not sessions.session_exists(project_id=projectId, session_id=data.session_id): + return {"errors": ["Session not found"]} + return {"data": assist_records.presign_record(project_id=projectId, data=data, context=context)} + + +@app.put('/{projectId}/assist/save/done', tags=["assist"]) +async def save_record_after_upload(projectId: int, data: schemas_ee.AssistRecordSavePayloadSchema = Body(...), + context: schemas_ee.CurrentContext = Depends(OR_context)): + if not sessions.session_exists(project_id=projectId, session_id=data.session_id): + return {"errors": ["Session not found"]} + return {"data": {"URL": assist_records.save_record(project_id=projectId, data=data, context=context)}} + + +@app.post('/{projectId}/assist/records', tags=["assist"]) +async def search_records(projectId: int, data: schemas_ee.AssistRecordSearchPayloadSchema = Body(...), + context: schemas_ee.CurrentContext = Depends(OR_context)): + return {"data": assist_records.search_records(project_id=projectId, data=data, context=context)} + + +@app.get('/{projectId}/assist/records/{recordId}', tags=["assist"]) +async def get_record(projectId: int, recordId: int, context: schemas_ee.CurrentContext = Depends(OR_context)): + return {"data": assist_records.get_record(project_id=projectId, record_id=recordId, context=context)} + + +@app.post('/{projectId}/assist/records/{recordId}', tags=["assist"]) +async def update_record(projectId: int, recordId: int, data: schemas_ee.AssistRecordUpdatePayloadSchema = Body(...), + context: schemas_ee.CurrentContext = Depends(OR_context)): + result = assist_records.update_record(project_id=projectId, record_id=recordId, data=data, context=context) + if "errors" in result: + return result + return {"data": result} + + +@app.delete('/{projectId}/assist/records/{recordId}', tags=["assist"]) +async def delete_record(projectId: int, recordId: int, context: schemas_ee.CurrentContext = Depends(OR_context)): + result = assist_records.delete_record(project_id=projectId, record_id=recordId, context=context) + if "errors" in result: + return result + return {"data": result} + + +@app.post('/{projectId}/signals', tags=['signals']) +async def send_interactions(projectId: int, data: schemas_ee.SignalsSchema = Body(...), + context: schemas_ee.CurrentContext = Depends(OR_context)): + data = signals.handle_frontend_signals_queued(project_id=projectId, user_id=context.user_id, data=data) + + if "errors" in data: + return data + return {'data': data} + + +@app.post('/{projectId}/dashboard/insights', tags=["insights"]) +@app.post('/{projectId}/dashboard/insights', tags=["insights"]) +async def sessions_search(projectId: int, data: schemas_ee.GetInsightsSchema = Body(...), + context: schemas_ee.CurrentContext = Depends(OR_context)): + return {'data': sessions_insights.fetch_selected(data=data, project_id=projectId)} diff --git a/ee/api/routers/subs/dashboard.py b/ee/api/routers/subs/dashboard.py deleted file mode 100644 index 206a234ca..000000000 --- a/ee/api/routers/subs/dashboard.py +++ /dev/null @@ -1,400 +0,0 @@ -from fastapi import Body - -import schemas -from chalicelib.core import metadata -from chalicelib.core import metrics -from chalicelib.utils import helper -from or_dependencies import OR_scope -from routers.base import get_routers -from schemas_ee import Permissions - -public_app, app, app_apikey = get_routers([OR_scope(Permissions.metrics)]) - - -@app.get('/{projectId}/dashboard/metadata', tags=["dashboard", "metrics"]) -def get_metadata_map(projectId: int): - metamap = [] - for m in metadata.get(project_id=projectId): - metamap.append({"name": m["key"], "key": f"metadata{m['index']}"}) - return {"data": metamap} - - -@app.post('/{projectId}/dashboard/sessions', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/sessions', tags=["dashboard", "metrics"]) -def get_dashboard_processed_sessions(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_processed_sessions(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/errors', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/errors', tags=["dashboard", "metrics"]) -def get_dashboard_errors(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_errors(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/errors_trend', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/errors_trend', tags=["dashboard", "metrics"]) -def get_dashboard_errors_trend(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_errors_trend(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/application_activity', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/application_activity', tags=["dashboard", "metrics"]) -def get_dashboard_application_activity(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_application_activity(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/page_metrics', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/page_metrics', tags=["dashboard", "metrics"]) -def get_dashboard_page_metrics(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_page_metrics(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/user_activity', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/user_activity', tags=["dashboard", "metrics"]) -def get_dashboard_user_activity(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_user_activity(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/performance', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/performance', tags=["dashboard", "metrics"]) -def get_dashboard_performance(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_performance(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/slowest_images', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/slowest_images', tags=["dashboard", "metrics"]) -def get_dashboard_slowest_images(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_slowest_images(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/missing_resources', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/missing_resources', tags=["dashboard", "metrics"]) -def get_performance_sessions(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_missing_resources_trend(project_id=projectId, **data.dict())} - - -@app.post('/{projectId}/dashboard/network', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/network', tags=["dashboard", "metrics"]) -def get_network_widget(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_network(project_id=projectId, **data.dict())} - - -@app.get('/{projectId}/dashboard/{widget}/search', tags=["dashboard", "metrics"]) -def get_dashboard_autocomplete(projectId: int, widget: str, q: str, type: str = "", platform: str = None, - key: str = ""): - if q is None or len(q) == 0: - return {"data": []} - q = '^' + q - - if widget in ['performance']: - data = metrics.search(q, type, project_id=projectId, - platform=platform, performance=True) - elif widget in ['pages', 'pages_dom_buildtime', 'top_metrics', 'time_to_render', - 'impacted_sessions_by_slow_pages', 'pages_response_time']: - data = metrics.search(q, type, project_id=projectId, - platform=platform, pages_only=True) - elif widget in ['resources_loading_time']: - data = metrics.search(q, type, project_id=projectId, - platform=platform, performance=False) - elif widget in ['time_between_events', 'events']: - data = metrics.search(q, type, project_id=projectId, - platform=platform, performance=False, events_only=True) - elif widget in ['metadata']: - data = metrics.search(q, None, project_id=projectId, - platform=platform, metadata=True, key=key) - else: - return {"errors": [f"unsupported widget: {widget}"]} - return {'data': data} - - -# 1 -@app.post('/{projectId}/dashboard/slowest_resources', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/slowest_resources', tags=["dashboard", "metrics"]) -def get_dashboard_slowest_resources(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_slowest_resources(project_id=projectId, **data.dict())} - - -# 2 -@app.post('/{projectId}/dashboard/resources_loading_time', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/resources_loading_time', tags=["dashboard", "metrics"]) -def get_dashboard_resources(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_resources_loading_time(project_id=projectId, **data.dict())} - - -# 3 -@app.post('/{projectId}/dashboard/pages_dom_buildtime', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/pages_dom_buildtime', tags=["dashboard", "metrics"]) -def get_dashboard_pages_dom(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_pages_dom_build_time(project_id=projectId, **data.dict())} - - -# 4 -@app.post('/{projectId}/dashboard/busiest_time_of_day', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/busiest_time_of_day', tags=["dashboard", "metrics"]) -def get_dashboard_busiest_time_of_day(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_busiest_time_of_day(project_id=projectId, **data.dict())} - - -# 5 -@app.post('/{projectId}/dashboard/sessions_location', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/sessions_location', tags=["dashboard", "metrics"]) -def get_dashboard_sessions_location(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_sessions_location(project_id=projectId, **data.dict())} - - -# 6 -@app.post('/{projectId}/dashboard/speed_location', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/speed_location', tags=["dashboard", "metrics"]) -def get_dashboard_speed_location(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_speed_index_location(project_id=projectId, **data.dict())} - - -# 7 -@app.post('/{projectId}/dashboard/pages_response_time', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/pages_response_time', tags=["dashboard", "metrics"]) -def get_dashboard_pages_response_time(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_pages_response_time(project_id=projectId, **data.dict())} - - -# 8 -@app.post('/{projectId}/dashboard/pages_response_time_distribution', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/pages_response_time_distribution', tags=["dashboard", "metrics"]) -def get_dashboard_pages_response_time_distribution(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_pages_response_time_distribution(project_id=projectId, **data.dict())} - - -# 9 -@app.post('/{projectId}/dashboard/top_metrics', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/top_metrics', tags=["dashboard", "metrics"]) -def get_dashboard_top_metrics(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_top_metrics(project_id=projectId, **data.dict())} - - -# 10 -@app.post('/{projectId}/dashboard/time_to_render', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/time_to_render', tags=["dashboard", "metrics"]) -def get_dashboard_time_to_render(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_time_to_render(project_id=projectId, **data.dict())} - - -# 11 -@app.post('/{projectId}/dashboard/impacted_sessions_by_slow_pages', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/impacted_sessions_by_slow_pages', tags=["dashboard", "metrics"]) -def get_dashboard_impacted_sessions_by_slow_pages(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_impacted_sessions_by_slow_pages(project_id=projectId, **data.dict())} - - -# 12 -@app.post('/{projectId}/dashboard/memory_consumption', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/memory_consumption', tags=["dashboard", "metrics"]) -def get_dashboard_memory_consumption(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_memory_consumption(project_id=projectId, **data.dict())} - - -# 12.1 -@app.post('/{projectId}/dashboard/fps', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/fps', tags=["dashboard", "metrics"]) -def get_dashboard_avg_fps(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_avg_fps(project_id=projectId, **data.dict())} - - -# 12.2 -@app.post('/{projectId}/dashboard/cpu', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/cpu', tags=["dashboard", "metrics"]) -def get_dashboard_avg_cpu(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_avg_cpu(project_id=projectId, **data.dict())} - - -# 13 -@app.post('/{projectId}/dashboard/crashes', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/crashes', tags=["dashboard", "metrics"]) -def get_dashboard_impacted_sessions_by_slow_pages(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_crashes(project_id=projectId, **data.dict())} - - -# 14 -@app.post('/{projectId}/dashboard/domains_errors', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/domains_errors', tags=["dashboard", "metrics"]) -def get_dashboard_domains_errors(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_domains_errors(project_id=projectId, **data.dict())} - - -# 14.1 -@app.post('/{projectId}/dashboard/domains_errors_4xx', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/domains_errors_4xx', tags=["dashboard", "metrics"]) -def get_dashboard_domains_errors_4xx(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_domains_errors_4xx(project_id=projectId, **data.dict())} - - -# 14.2 -@app.post('/{projectId}/dashboard/domains_errors_5xx', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/domains_errors_5xx', tags=["dashboard", "metrics"]) -def get_dashboard_domains_errors_5xx(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_domains_errors_5xx(project_id=projectId, **data.dict())} - - -# 15 -@app.post('/{projectId}/dashboard/slowest_domains', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/slowest_domains', tags=["dashboard", "metrics"]) -def get_dashboard_slowest_domains(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_slowest_domains(project_id=projectId, **data.dict())} - - -# 16 -@app.post('/{projectId}/dashboard/errors_per_domains', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/errors_per_domains', tags=["dashboard", "metrics"]) -def get_dashboard_errors_per_domains(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_errors_per_domains(project_id=projectId, **data.dict())} - - -# 17 -@app.post('/{projectId}/dashboard/sessions_per_browser', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/sessions_per_browser', tags=["dashboard", "metrics"]) -def get_dashboard_sessions_per_browser(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_sessions_per_browser(project_id=projectId, **data.dict())} - - -# 18 -@app.post('/{projectId}/dashboard/calls_errors', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/calls_errors', tags=["dashboard", "metrics"]) -def get_dashboard_calls_errors(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_calls_errors(project_id=projectId, **data.dict())} - - -# 18.1 -@app.post('/{projectId}/dashboard/calls_errors_4xx', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/calls_errors_4xx', tags=["dashboard", "metrics"]) -def get_dashboard_calls_errors_4xx(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_calls_errors_4xx(project_id=projectId, **data.dict())} - - -# 18.2 -@app.post('/{projectId}/dashboard/calls_errors_5xx', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/calls_errors_5xx', tags=["dashboard", "metrics"]) -def get_dashboard_calls_errors_5xx(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_calls_errors_5xx(project_id=projectId, **data.dict())} - - -# 19 -@app.post('/{projectId}/dashboard/errors_per_type', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/errors_per_type', tags=["dashboard", "metrics"]) -def get_dashboard_errors_per_type(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_errors_per_type(project_id=projectId, **data.dict())} - - -# 20 -@app.post('/{projectId}/dashboard/resources_by_party', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/resources_by_party', tags=["dashboard", "metrics"]) -def get_dashboard_resources_by_party(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_resources_by_party(project_id=projectId, **data.dict())} - - -# 21 -@app.post('/{projectId}/dashboard/resource_type_vs_response_end', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/resource_type_vs_response_end', tags=["dashboard", "metrics"]) -def get_dashboard_errors_per_resource_type(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.resource_type_vs_response_end(project_id=projectId, **data.dict())} - - -# 22 -@app.post('/{projectId}/dashboard/resources_vs_visually_complete', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/resources_vs_visually_complete', tags=["dashboard", "metrics"]) -def get_dashboard_resources_vs_visually_complete(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_resources_vs_visually_complete(project_id=projectId, **data.dict())} - - -# 23 -@app.post('/{projectId}/dashboard/impacted_sessions_by_js_errors', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/impacted_sessions_by_js_errors', tags=["dashboard", "metrics"]) -def get_dashboard_impacted_sessions_by_js_errors(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_impacted_sessions_by_js_errors(project_id=projectId, **data.dict())} - - -# 24 -@app.post('/{projectId}/dashboard/resources_count_by_type', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/resources_count_by_type', tags=["dashboard", "metrics"]) -def get_dashboard_resources_count_by_type(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - return {"data": metrics.get_resources_count_by_type(project_id=projectId, **data.dict())} - - -# # 25 -# @app.post('/{projectId}/dashboard/time_between_events', tags=["dashboard", "metrics"]) -# @app.get('/{projectId}/dashboard/time_between_events', tags=["dashboard", "metrics"]) -# def get_dashboard_resources_count_by_type(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): -# return {"errors": ["please choose 2 events"]} - - -@app.post('/{projectId}/dashboard/overview', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/overview', tags=["dashboard", "metrics"]) -def get_dashboard_group(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - results = [ - {"key": "count_sessions", - "data": metrics.get_processed_sessions(project_id=projectId, **data.dict())}, - *helper.explode_widget(data={**metrics.get_application_activity(project_id=projectId, **data.dict()), - "chart": metrics.get_performance(project_id=projectId, **data.dict()) - .get("chart", [])}), - *helper.explode_widget(data=metrics.get_page_metrics(project_id=projectId, **data.dict())), - *helper.explode_widget(data=metrics.get_user_activity(project_id=projectId, **data.dict())), - {"key": "avg_pages_dom_buildtime", - "data": metrics.get_pages_dom_build_time(project_id=projectId, **data.dict())}, - {"key": "avg_pages_response_time", - "data": metrics.get_pages_response_time(project_id=projectId, **data.dict()) - }, - *helper.explode_widget(metrics.get_top_metrics(project_id=projectId, **data.dict())), - {"key": "avg_time_to_render", "data": metrics.get_time_to_render(project_id=projectId, **data.dict())}, - {"key": "avg_used_js_heap_size", "data": metrics.get_memory_consumption(project_id=projectId, **data.dict())}, - {"key": "avg_cpu", "data": metrics.get_avg_cpu(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_fps, - "data": metrics.get_avg_fps(project_id=projectId, **data.dict())} - ] - results = sorted(results, key=lambda r: r["key"]) - return {"data": results} - - -@app.post('/{projectId}/dashboard/overview2', tags=["dashboard", "metrics"]) -@app.get('/{projectId}/dashboard/overview2', tags=["dashboard", "metrics"]) -def get_dashboard_group(projectId: int, data: schemas.MetricPayloadSchema = Body(...)): - results = [ - {"key": schemas.TemplatePredefinedKeys.count_sessions, - "data": metrics.get_processed_sessions(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_image_load_time, - "data": metrics.get_application_activity_avg_image_load_time(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_page_load_time, - "data": metrics.get_application_activity_avg_page_load_time(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_request_load_time, - "data": metrics.get_application_activity_avg_request_load_time(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_dom_content_load_start, - "data": metrics.get_page_metrics_avg_dom_content_load_start(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_first_contentful_pixel, - "data": metrics.get_page_metrics_avg_first_contentful_pixel(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_visited_pages, - "data": metrics.get_user_activity_avg_visited_pages(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_session_duration, - "data": metrics.get_user_activity_avg_session_duration(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_pages_dom_buildtime, - "data": metrics.get_pages_dom_build_time(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_pages_response_time, - "data": metrics.get_pages_response_time(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_response_time, - "data": metrics.get_top_metrics_avg_response_time(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_first_paint, - "data": metrics.get_top_metrics_avg_first_paint(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_dom_content_loaded, - "data": metrics.get_top_metrics_avg_dom_content_loaded(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_till_first_bit, - "data": metrics.get_top_metrics_avg_till_first_bit(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_time_to_interactive, - "data": metrics.get_top_metrics_avg_time_to_interactive(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.count_requests, - "data": metrics.get_top_metrics_count_requests(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_time_to_render, - "data": metrics.get_time_to_render(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_used_js_heap_size, - "data": metrics.get_memory_consumption(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_cpu, - "data": metrics.get_avg_cpu(project_id=projectId, **data.dict())}, - {"key": schemas.TemplatePredefinedKeys.avg_fps, - "data": metrics.get_avg_fps(project_id=projectId, **data.dict())} - ] - results = sorted(results, key=lambda r: r["key"]) - return {"data": results} diff --git a/ee/api/routers/subs/metrics.py b/ee/api/routers/subs/metrics.py index 2d296251b..274c8e256 100644 --- a/ee/api/routers/subs/metrics.py +++ b/ee/api/routers/subs/metrics.py @@ -1,6 +1,9 @@ -from fastapi import Body, Depends +from typing import Union + +from fastapi import Body, Depends, Request import schemas +import schemas_ee from chalicelib.core import dashboards, custom_metrics, funnels from or_dependencies import OR_context, OR_scope from routers.base import get_routers @@ -47,11 +50,12 @@ def pin_dashboard(projectId: int, dashboardId: int, context: schemas.CurrentCont return {"data": dashboards.pin_dashboard(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId)} +@app.post('/{projectId}/dashboards/{dashboardId}/cards', tags=["cards"]) @app.post('/{projectId}/dashboards/{dashboardId}/widgets', tags=["dashboard"]) @app.put('/{projectId}/dashboards/{dashboardId}/widgets', tags=["dashboard"]) -def add_widget_to_dashboard(projectId: int, dashboardId: int, - data: schemas.AddWidgetToDashboardPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def add_card_to_dashboard(projectId: int, dashboardId: int, + data: schemas.AddWidgetToDashboardPayloadSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return {"data": dashboards.add_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, data=data)} @@ -59,7 +63,7 @@ def add_widget_to_dashboard(projectId: int, dashboardId: int, @app.post('/{projectId}/dashboards/{dashboardId}/metrics', tags=["dashboard"]) @app.put('/{projectId}/dashboards/{dashboardId}/metrics', tags=["dashboard"]) def create_metric_and_add_to_dashboard(projectId: int, dashboardId: int, - data: schemas.CreateCustomMetricsSchema = Body(...), + data: schemas_ee.CreateCardSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): return {"data": dashboards.create_metric_add_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, data=data)} @@ -81,43 +85,41 @@ def remove_widget_from_dashboard(projectId: int, dashboardId: int, widgetId: int widget_id=widgetId) -@app.post('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}/chart', tags=["dashboard"]) -def get_widget_chart(projectId: int, dashboardId: int, widgetId: int, - data: schemas.CustomMetricChartPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): - data = dashboards.make_chart_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, - widget_id=widgetId, data=data) - if data is None: - return {"errors": ["widget not found"]} - return {"data": data} - - -@app.get('/{projectId}/metrics/templates', tags=["dashboard"]) -def get_templates(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): - return {"data": dashboards.get_templates(project_id=projectId, user_id=context.user_id)} +# @app.post('/{projectId}/dashboards/{dashboardId}/widgets/{widgetId}/chart', tags=["dashboard"]) +# def get_widget_chart(projectId: int, dashboardId: int, widgetId: int, +# data: schemas.CardChartSchema = Body(...), +# context: schemas.CurrentContext = Depends(OR_context)): +# data = dashboards.make_chart_widget(project_id=projectId, user_id=context.user_id, dashboard_id=dashboardId, +# widget_id=widgetId, data=data) +# if data is None: +# return {"errors": ["widget not found"]} +# return {"data": data} +@app.post('/{projectId}/cards/try', tags=["cards"]) @app.post('/{projectId}/metrics/try', tags=["dashboard"]) @app.put('/{projectId}/metrics/try', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/try', tags=["customMetrics"]) @app.put('/{projectId}/custom_metrics/try', tags=["customMetrics"]) -def try_custom_metric(projectId: int, data: schemas.TryCustomMetricsPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def try_card(projectId: int, data: schemas_ee.CreateCardSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return {"data": custom_metrics.merged_live(project_id=projectId, data=data, user_id=context.user_id)} +@app.post('/{projectId}/cards/try/sessions', tags=["cards"]) @app.post('/{projectId}/metrics/try/sessions', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/try/sessions', tags=["customMetrics"]) -def try_custom_metric_sessions(projectId: int, data: schemas.CustomMetricSessionsPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def try_card_sessions(projectId: int, data: schemas.CardSessionsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): data = custom_metrics.try_sessions(project_id=projectId, user_id=context.user_id, data=data) return {"data": data} +@app.post('/{projectId}/cards/try/issues', tags=["cards"]) @app.post('/{projectId}/metrics/try/issues', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/try/issues', tags=["customMetrics"]) -def try_custom_metric_funnel_issues(projectId: int, data: schemas.CustomMetricSessionsPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def try_card_funnel_issues(projectId: int, data: schemas.CardSessionsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): if len(data.series) == 0: return {"data": []} data.series[0].filter.startDate = data.startTimestamp @@ -126,46 +128,72 @@ def try_custom_metric_funnel_issues(projectId: int, data: schemas.CustomMetricSe return {"data": data} +@app.get('/{projectId}/cards', tags=["cards"]) +@app.get('/{projectId}/metrics', tags=["dashboard"]) +@app.get('/{projectId}/custom_metrics', tags=["customMetrics"]) +def get_cards(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): + return {"data": custom_metrics.get_all(project_id=projectId, user_id=context.user_id)} + + +@app.post('/{projectId}/cards', tags=["cards"]) @app.post('/{projectId}/metrics', tags=["dashboard"]) @app.put('/{projectId}/metrics', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics', tags=["customMetrics"]) @app.put('/{projectId}/custom_metrics', tags=["customMetrics"]) -def add_custom_metric(projectId: int, data: schemas.CreateCustomMetricsSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def create_card(projectId: int, data: schemas_ee.CreateCardSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): return custom_metrics.create(project_id=projectId, user_id=context.user_id, data=data) -@app.get('/{projectId}/metrics', tags=["dashboard"]) -@app.get('/{projectId}/custom_metrics', tags=["customMetrics"]) -def get_custom_metrics(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): - return {"data": custom_metrics.get_all(project_id=projectId, user_id=context.user_id)} +@app.post('/{projectId}/cards/search', tags=["cards"]) +@app.post('/{projectId}/metrics/search', tags=["dashboard"]) +@app.post('/{projectId}/custom_metrics/search', tags=["customMetrics"]) +def search_cards(projectId: int, data: schemas.SearchCardsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + return {"data": custom_metrics.search_all(project_id=projectId, user_id=context.user_id, data=data)} +@app.get('/{projectId}/cards/{metric_id}', tags=["cards"]) @app.get('/{projectId}/metrics/{metric_id}', tags=["dashboard"]) @app.get('/{projectId}/custom_metrics/{metric_id}', tags=["customMetrics"]) -def get_custom_metric(projectId: int, metric_id: str, context: schemas.CurrentContext = Depends(OR_context)): - data = custom_metrics.get(project_id=projectId, user_id=context.user_id, metric_id=metric_id) +def get_card(projectId: int, metric_id: Union[int, str], context: schemas.CurrentContext = Depends(OR_context)): + if not isinstance(metric_id, int): + return {"errors": ["invalid card_id"]} + data = custom_metrics.get_card(project_id=projectId, user_id=context.user_id, metric_id=metric_id) if data is None: - return {"errors": ["custom metric not found"]} + return {"errors": ["card not found"]} return {"data": data} +# @app.get('/{projectId}/cards/{metric_id}/thumbnail', tags=["cards"]) +# def sign_thumbnail_for_upload(projectId: int, metric_id: Union[int, str], +# context: schemas.CurrentContext = Depends(OR_context)): +# if not isinstance(metric_id, int): +# return {"errors": ["invalid card_id"]} +# return custom_metrics.add_thumbnail(metric_id=metric_id, user_id=context.user_id, project_id=projectId) + + +@app.post('/{projectId}/cards/{metric_id}/sessions', tags=["cards"]) @app.post('/{projectId}/metrics/{metric_id}/sessions', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/sessions', tags=["customMetrics"]) -def get_custom_metric_sessions(projectId: int, metric_id: int, - data: schemas.CustomMetricSessionsPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def get_card_sessions(projectId: int, metric_id: int, + data: schemas.CardSessionsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): data = custom_metrics.get_sessions(project_id=projectId, user_id=context.user_id, metric_id=metric_id, data=data) if data is None: return {"errors": ["custom metric not found"]} return {"data": data} +@app.post('/{projectId}/cards/{metric_id}/issues', tags=["cards"]) @app.post('/{projectId}/metrics/{metric_id}/issues', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/issues', tags=["customMetrics"]) -def get_custom_metric_funnel_issues(projectId: int, metric_id: int, - data: schemas.CustomMetricSessionsPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): +def get_card_funnel_issues(projectId: int, metric_id: Union[int, str], + data: schemas.CardSessionsSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + if not isinstance(metric_id, int): + return {"errors": [f"invalid card_id: {metric_id}"]} + data = custom_metrics.get_funnel_issues(project_id=projectId, user_id=context.user_id, metric_id=metric_id, data=data) if data is None: @@ -173,10 +201,11 @@ def get_custom_metric_funnel_issues(projectId: int, metric_id: int, return {"data": data} +@app.post('/{projectId}/cards/{metric_id}/issues/{issueId}/sessions', tags=["dashboard"]) @app.post('/{projectId}/metrics/{metric_id}/issues/{issueId}/sessions', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/issues/{issueId}/sessions', tags=["customMetrics"]) def get_metric_funnel_issue_sessions(projectId: int, metric_id: int, issueId: str, - data: schemas.CustomMetricSessionsPayloadSchema = Body(...), + data: schemas.CardSessionsSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): data = custom_metrics.get_funnel_sessions_by_issue(project_id=projectId, user_id=context.user_id, metric_id=metric_id, issue_id=issueId, data=data) @@ -185,10 +214,11 @@ def get_metric_funnel_issue_sessions(projectId: int, metric_id: int, issueId: st return {"data": data} +@app.post('/{projectId}/cards/{metric_id}/errors', tags=["dashboard"]) @app.post('/{projectId}/metrics/{metric_id}/errors', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/errors', tags=["customMetrics"]) def get_custom_metric_errors_list(projectId: int, metric_id: int, - data: schemas.CustomMetricSessionsPayloadSchema = Body(...), + data: schemas.CardSessionsSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): data = custom_metrics.get_errors_list(project_id=projectId, user_id=context.user_id, metric_id=metric_id, data=data) @@ -197,22 +227,22 @@ def get_custom_metric_errors_list(projectId: int, metric_id: int, return {"data": data} +@app.post('/{projectId}/cards/{metric_id}/chart', tags=["card"]) @app.post('/{projectId}/metrics/{metric_id}/chart', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/chart', tags=["customMetrics"]) -def get_custom_metric_chart(projectId: int, metric_id: int, data: schemas.CustomMetricChartPayloadSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): - data = dashboards.make_chart_metrics(project_id=projectId, user_id=context.user_id, metric_id=metric_id, - data=data) - if data is None: - return {"errors": ["custom metric not found"]} +def get_card_chart(projectId: int, metric_id: int, request: Request, data: schemas.CardChartSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + data = custom_metrics.make_chart_from_card(project_id=projectId, user_id=context.user_id, metric_id=metric_id, + data=data) return {"data": data} +@app.post('/{projectId}/cards/{metric_id}', tags=["dashboard"]) @app.post('/{projectId}/metrics/{metric_id}', tags=["dashboard"]) @app.put('/{projectId}/metrics/{metric_id}', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}', tags=["customMetrics"]) @app.put('/{projectId}/custom_metrics/{metric_id}', tags=["customMetrics"]) -def update_custom_metric(projectId: int, metric_id: int, data: schemas.UpdateCustomMetricsSchema = Body(...), +def update_custom_metric(projectId: int, metric_id: int, data: schemas_ee.UpdateCardSchema = Body(...), context: schemas.CurrentContext = Depends(OR_context)): data = custom_metrics.update(project_id=projectId, user_id=context.user_id, metric_id=metric_id, data=data) if data is None: @@ -220,6 +250,7 @@ def update_custom_metric(projectId: int, metric_id: int, data: schemas.UpdateCus return {"data": data} +@app.post('/{projectId}/cards/{metric_id}/status', tags=["dashboard"]) @app.post('/{projectId}/metrics/{metric_id}/status', tags=["dashboard"]) @app.put('/{projectId}/metrics/{metric_id}/status', tags=["dashboard"]) @app.post('/{projectId}/custom_metrics/{metric_id}/status', tags=["customMetrics"]) @@ -232,6 +263,7 @@ def update_custom_metric_state(projectId: int, metric_id: int, status=data.active)} +@app.delete('/{projectId}/cards/{metric_id}', tags=["dashboard"]) @app.delete('/{projectId}/metrics/{metric_id}', tags=["dashboard"]) @app.delete('/{projectId}/custom_metrics/{metric_id}', tags=["customMetrics"]) def delete_custom_metric(projectId: int, metric_id: int, context: schemas.CurrentContext = Depends(OR_context)): diff --git a/ee/api/schemas_ee.py b/ee/api/schemas_ee.py index 9690eb334..f5a3b8de0 100644 --- a/ee/api/schemas_ee.py +++ b/ee/api/schemas_ee.py @@ -1,10 +1,11 @@ -from typing import Optional, List, Literal +from enum import Enum +from typing import Optional, List, Union, Literal from pydantic import BaseModel, Field, EmailStr +from pydantic import root_validator, validator import schemas from chalicelib.utils.TimeUTC import TimeUTC -from enum import Enum class Permissions(str, Enum): @@ -21,11 +22,37 @@ class CurrentContext(schemas.CurrentContext): class RolePayloadSchema(BaseModel): - name: str = Field(...) - description: Optional[str] = Field(None) + name: str = Field(..., min_length=1, max_length=40) + description: Optional[str] = Field(default=None) permissions: List[Permissions] = Field(...) - all_projects: bool = Field(True) - projects: List[int] = Field([]) + all_projects: bool = Field(default=True) + projects: List[int] = Field(default=[]) + _transform_name = validator('name', pre=True, allow_reuse=True)(schemas.remove_whitespace) + + class Config: + alias_generator = schemas.attribute_to_camel_case + + +class SignalsSchema(BaseModel): + timestamp: int = Field(...) + action: str = Field(...) + source: str = Field(...) + category: str = Field(...) + data: dict = Field(default={}) + + +class InsightCategories(str, Enum): + errors = "errors" + network = "network" + rage = "rage" + resources = "resources" + + +class GetInsightsSchema(schemas._TimedSchema): + startTimestamp: int = Field(default=TimeUTC.now(-7)) + endTimestamp: int = Field(default=TimeUTC.now()) + metricValue: List[InsightCategories] = Field(default=[]) + series: List[schemas.CardCreateSeriesSchema] = Field(default=[]) class Config: alias_generator = schemas.attribute_to_camel_case @@ -53,7 +80,15 @@ class TrailSearchPayloadSchema(schemas._PaginatedSchema): user_id: Optional[int] = Field(default=None) query: Optional[str] = Field(default=None) action: Optional[str] = Field(default=None) - order: Literal["asc", "desc"] = Field(default="desc") + order: schemas.SortOrderType = Field(default=schemas.SortOrderType.desc) + + @root_validator(pre=True) + def transform_order(cls, values): + if values.get("order") is None: + values["order"] = schemas.SortOrderType.desc + else: + values["order"] = values["order"].upper() + return values class Config: alias_generator = schemas.attribute_to_camel_case @@ -81,3 +116,68 @@ class SessionModel(BaseModel): userDeviceType: str userAnonymousId: Optional[str] metadata: dict = Field(default={}) + + +class AssistRecordUpdatePayloadSchema(BaseModel): + name: str = Field(..., min_length=1) + _transform_name = validator('name', pre=True, allow_reuse=True)(schemas.remove_whitespace) + + +class AssistRecordPayloadSchema(AssistRecordUpdatePayloadSchema): + duration: int = Field(...) + session_id: int = Field(...) + + class Config: + alias_generator = schemas.attribute_to_camel_case + + +class AssistRecordSavePayloadSchema(AssistRecordPayloadSchema): + key: str = Field(...) + + +class AssistRecordSearchPayloadSchema(schemas._PaginatedSchema): + limit: int = Field(default=200, gt=0) + startDate: int = Field(default=TimeUTC.now(-7)) + endDate: int = Field(default=TimeUTC.now(1)) + user_id: Optional[int] = Field(default=None) + query: Optional[str] = Field(default=None) + order: Literal["asc", "desc"] = Field(default="desc") + + class Config: + alias_generator = schemas.attribute_to_camel_case + + +# TODO: move these to schema when Insights is supported on PG +class MetricOfInsights(str, Enum): + issue_categories = "issueCategories" + + +class CreateCardSchema(schemas.CreateCardSchema): + metric_of: Union[schemas.MetricOfTimeseries, schemas.MetricOfTable, \ + schemas.MetricOfErrors, schemas.MetricOfPerformance, \ + schemas.MetricOfResources, schemas.MetricOfWebVitals, \ + schemas.MetricOfClickMap, MetricOfInsights] = Field(default=schemas.MetricOfTable.user_id) + metric_value: List[Union[schemas.IssueType, InsightCategories]] = Field(default=[]) + + @root_validator + def restrictions(cls, values): + return values + + @root_validator + def validator(cls, values): + values = super().validator(values) + if values.get("metric_type") == schemas.MetricType.insights: + assert values.get("view_type") == schemas.MetricOtherViewType.list_chart, \ + f"viewType must be 'list' for metricOf:{values.get('metric_of')}" + assert isinstance(values.get("metric_of"), MetricOfInsights), \ + f"metricOf must be of type {MetricOfInsights} for metricType:{schemas.MetricType.insights}" + if values.get("metric_value") is not None and len(values.get("metric_value")) > 0: + for i in values.get("metric_value"): + assert isinstance(i, InsightCategories), \ + f"metricValue should be of type [InsightCategories] for metricType:{schemas.MetricType.insights}" + + return values + + +class UpdateCardSchema(CreateCardSchema): + series: List[schemas.CardUpdateSeriesSchema] = Field(...) diff --git a/backend/internal/db/datasaver/fts.go b/ee/backend/internal/db/datasaver/fts.go similarity index 79% rename from backend/internal/db/datasaver/fts.go rename to ee/backend/internal/db/datasaver/fts.go index c0250c4d2..1ff546d27 100644 --- a/backend/internal/db/datasaver/fts.go +++ b/ee/backend/internal/db/datasaver/fts.go @@ -6,7 +6,9 @@ import ( "openreplay/backend/pkg/messages" ) -type FetchEventFTS struct { +type NetworkRequestFTS struct { + SessionID uint64 `json:"session_id"` + ProjectID uint32 `json:"project_id"` Method string `json:"method"` URL string `json:"url"` Request string `json:"request"` @@ -17,6 +19,8 @@ type FetchEventFTS struct { } type PageEventFTS struct { + SessionID uint64 `json:"session_id"` + ProjectID uint32 `json:"project_id"` MessageID uint64 `json:"message_id"` Timestamp uint64 `json:"timestamp"` URL string `json:"url"` @@ -36,14 +40,16 @@ type PageEventFTS struct { TimeToInteractive uint64 `json:"time_to_interactive"` } -type GraphQLEventFTS struct { +type GraphQLFTS struct { + SessionID uint64 `json:"session_id"` + ProjectID uint32 `json:"project_id"` OperationKind string `json:"operation_kind"` OperationName string `json:"operation_name"` Variables string `json:"variables"` Response string `json:"response"` } -func (s *Saver) sendToFTS(msg messages.Message, sessionID uint64) { +func (s *Saver) SendToFTS(msg messages.Message, projID uint32) { // Skip, if FTS is disabled if s.producer == nil { return @@ -56,18 +62,10 @@ func (s *Saver) sendToFTS(msg messages.Message, sessionID uint64) { switch m := msg.(type) { // Common - case *messages.Fetch: - event, err = json.Marshal(FetchEventFTS{ - Method: m.Method, - URL: m.URL, - Request: m.Request, - Response: m.Response, - Status: m.Status, - Timestamp: m.Timestamp, - Duration: m.Duration, - }) - case *messages.FetchEvent: - event, err = json.Marshal(FetchEventFTS{ + case *messages.NetworkRequest: + event, err = json.Marshal(NetworkRequestFTS{ + SessionID: msg.SessionID(), + ProjectID: projID, Method: m.Method, URL: m.URL, Request: m.Request, @@ -78,6 +76,8 @@ func (s *Saver) sendToFTS(msg messages.Message, sessionID uint64) { }) case *messages.PageEvent: event, err = json.Marshal(PageEventFTS{ + SessionID: msg.SessionID(), + ProjectID: projID, MessageID: m.MessageID, Timestamp: m.Timestamp, URL: m.URL, @@ -97,14 +97,9 @@ func (s *Saver) sendToFTS(msg messages.Message, sessionID uint64) { TimeToInteractive: m.TimeToInteractive, }) case *messages.GraphQL: - event, err = json.Marshal(GraphQLEventFTS{ - OperationKind: m.OperationKind, - OperationName: m.OperationName, - Variables: m.Variables, - Response: m.Response, - }) - case *messages.GraphQLEvent: - event, err = json.Marshal(GraphQLEventFTS{ + event, err = json.Marshal(GraphQLFTS{ + SessionID: msg.SessionID(), + ProjectID: projID, OperationKind: m.OperationKind, OperationName: m.OperationName, Variables: m.Variables, @@ -115,7 +110,7 @@ func (s *Saver) sendToFTS(msg messages.Message, sessionID uint64) { log.Printf("can't marshal json for quickwit: %s", err) } else { if len(event) > 0 { - if err := s.producer.Produce("quickwit", sessionID, event); err != nil { + if err := s.producer.Produce(s.topic, msg.SessionID(), event); err != nil { log.Printf("can't send event to quickwit: %s", err) } } diff --git a/ee/backend/internal/db/datasaver/messages.go b/ee/backend/internal/db/datasaver/messages.go index f28bd3b8f..0a729ee63 100644 --- a/ee/backend/internal/db/datasaver/messages.go +++ b/ee/backend/internal/db/datasaver/messages.go @@ -16,7 +16,7 @@ func (mi *Saver) InsertMessage(msg Message) error { } return nil case *IssueEvent: - session, err := mi.pg.GetSession(sessionID) + session, err := mi.pg.Cache.GetSession(sessionID) if err != nil { log.Printf("can't get session info for CH: %s", err) } else { @@ -37,7 +37,7 @@ func (mi *Saver) InsertMessage(msg Message) error { case *UserAnonymousID: return mi.pg.InsertWebUserAnonymousID(sessionID, m) case *CustomEvent: - session, err := mi.pg.GetSession(sessionID) + session, err := mi.pg.Cache.GetSession(sessionID) if err != nil { log.Printf("can't get session info for CH: %s", err) } else { @@ -58,8 +58,8 @@ func (mi *Saver) InsertMessage(msg Message) error { return mi.pg.InsertWebJSException(m) case *IntegrationEvent: return mi.pg.InsertWebIntegrationEvent(m) - case *FetchEvent: - session, err := mi.pg.GetSession(sessionID) + case *NetworkRequest: + session, err := mi.pg.Cache.GetSession(sessionID) if err != nil { log.Printf("can't get session info for CH: %s", err) } else { @@ -72,9 +72,9 @@ func (mi *Saver) InsertMessage(msg Message) error { } } } - return mi.pg.InsertWebFetchEvent(sessionID, m) - case *GraphQLEvent: - session, err := mi.pg.GetSession(sessionID) + return mi.pg.InsertWebNetworkRequest(sessionID, m) + case *GraphQL: + session, err := mi.pg.Cache.GetSession(sessionID) if err != nil { log.Printf("can't get session info for CH: %s", err) } else { @@ -82,7 +82,7 @@ func (mi *Saver) InsertMessage(msg Message) error { log.Printf("can't insert graphQL event into clickhouse: %s", err) } } - return mi.pg.InsertWebGraphQLEvent(sessionID, m) + return mi.pg.InsertWebGraphQL(sessionID, m) case *SetPageLocation: return mi.pg.InsertSessionReferrer(sessionID, m.Referrer) diff --git a/ee/backend/internal/db/datasaver/saver.go b/ee/backend/internal/db/datasaver/saver.go index 7b48fbaa0..e05e502f1 100644 --- a/ee/backend/internal/db/datasaver/saver.go +++ b/ee/backend/internal/db/datasaver/saver.go @@ -1,8 +1,10 @@ package datasaver import ( + "openreplay/backend/internal/config/db" "openreplay/backend/pkg/db/cache" "openreplay/backend/pkg/db/clickhouse" + "openreplay/backend/pkg/queue" "openreplay/backend/pkg/queue/types" ) @@ -10,8 +12,13 @@ type Saver struct { pg *cache.PGCache ch clickhouse.Connector producer types.Producer + topic string } -func New(pg *cache.PGCache, producer types.Producer) *Saver { - return &Saver{pg: pg, producer: producer} +func New(pg *cache.PGCache, cfg *db.Config) *Saver { + var producer types.Producer = nil + if cfg.UseQuickwit { + producer = queue.NewProducer(cfg.MessageSizeLimit, true) + } + return &Saver{pg: pg, producer: producer, topic: cfg.QuickwitTopic} } diff --git a/ee/backend/internal/db/datasaver/stats.go b/ee/backend/internal/db/datasaver/stats.go index c18918c63..049c319bd 100644 --- a/ee/backend/internal/db/datasaver/stats.go +++ b/ee/backend/internal/db/datasaver/stats.go @@ -17,6 +17,13 @@ func (si *Saver) InitStats() { } func (si *Saver) InsertStats(session *types.Session, msg messages.Message) error { + // Send data to quickwit + if sess, err := si.pg.Cache.GetSession(msg.SessionID()); err != nil { + si.SendToFTS(msg, 0) + } else { + si.SendToFTS(msg, sess.ProjectID) + } + switch m := msg.(type) { // Web case *messages.SessionEnd: @@ -43,3 +50,7 @@ func (si *Saver) InsertStats(session *types.Session, msg messages.Message) error func (si *Saver) CommitStats() error { return si.ch.Commit() } + +func (si *Saver) Close() error { + return si.ch.Stop() +} diff --git a/ee/backend/pkg/db/clickhouse/bulk.go b/ee/backend/pkg/db/clickhouse/bulk.go index 706b66f68..6eb8d98fd 100644 --- a/ee/backend/pkg/db/clickhouse/bulk.go +++ b/ee/backend/pkg/db/clickhouse/bulk.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "log" + "openreplay/backend/pkg/metrics/database" + "time" "github.com/ClickHouse/clickhouse-go/v2/lib/driver" ) @@ -16,19 +18,23 @@ type Bulk interface { type bulkImpl struct { conn driver.Conn + table string query string values [][]interface{} } -func NewBulk(conn driver.Conn, query string) (Bulk, error) { +func NewBulk(conn driver.Conn, table, query string) (Bulk, error) { switch { case conn == nil: return nil, errors.New("clickhouse connection is empty") + case table == "": + return nil, errors.New("table is empty") case query == "": return nil, errors.New("query is empty") } return &bulkImpl{ conn: conn, + table: table, query: query, values: make([][]interface{}, 0), }, nil @@ -40,6 +46,7 @@ func (b *bulkImpl) Append(args ...interface{}) error { } func (b *bulkImpl) Send() error { + start := time.Now() batch, err := b.conn.PrepareBatch(context.Background(), b.query) if err != nil { return fmt.Errorf("can't create new batch: %s", err) @@ -50,6 +57,11 @@ func (b *bulkImpl) Send() error { log.Printf("failed query: %s", b.query) } } + err = batch.Send() + // Save bulk metrics + database.RecordBulkElements(float64(len(b.values)), "ch", b.table) + database.RecordBulkInsertDuration(float64(time.Now().Sub(start).Milliseconds()), "ch", b.table) + // Prepare values slice for a new data b.values = make([][]interface{}, 0) - return batch.Send() + return err } diff --git a/ee/backend/pkg/db/clickhouse/connector.go b/ee/backend/pkg/db/clickhouse/connector.go index d882f793f..b872adcc2 100644 --- a/ee/backend/pkg/db/clickhouse/connector.go +++ b/ee/backend/pkg/db/clickhouse/connector.go @@ -19,6 +19,7 @@ import ( type Connector interface { Prepare() error Commit() error + Stop() error InsertWebSession(session *types.Session) error InsertWebResourceEvent(session *types.Session, msg *messages.ResourceEvent) error InsertWebPageEvent(session *types.Session, msg *messages.PageEvent) error @@ -27,15 +28,26 @@ type Connector interface { InsertWebErrorEvent(session *types.Session, msg *types.ErrorEvent) error InsertWebPerformanceTrackAggr(session *types.Session, msg *messages.PerformanceTrackAggr) error InsertAutocomplete(session *types.Session, msgType, msgValue string) error - InsertRequest(session *types.Session, msg *messages.FetchEvent, savePayload bool) error + InsertRequest(session *types.Session, msg *messages.NetworkRequest, savePayload bool) error InsertCustom(session *types.Session, msg *messages.CustomEvent) error - InsertGraphQL(session *types.Session, msg *messages.GraphQLEvent) error + InsertGraphQL(session *types.Session, msg *messages.GraphQL) error InsertIssue(session *types.Session, msg *messages.IssueEvent) error } +type task struct { + bulks []Bulk +} + +func NewTask() *task { + return &task{bulks: make([]Bulk, 0, 14)} +} + type connectorImpl struct { - conn driver.Conn - batches map[string]Bulk //driver.Batch + conn driver.Conn + batches map[string]Bulk //driver.Batch + workerTask chan *task + done chan struct{} + finished chan struct{} } func NewConnector(url string) Connector { @@ -60,14 +72,18 @@ func NewConnector(url string) Connector { } c := &connectorImpl{ - conn: conn, - batches: make(map[string]Bulk, 9), + conn: conn, + batches: make(map[string]Bulk, 13), + workerTask: make(chan *task, 1), + done: make(chan struct{}), + finished: make(chan struct{}), } + go c.worker() return c } func (c *connectorImpl) newBatch(name, query string) error { - batch, err := NewBulk(c.conn, query) + batch, err := NewBulk(c.conn, name, query) if err != nil { return fmt.Errorf("can't create new batch: %s", err) } @@ -76,18 +92,18 @@ func (c *connectorImpl) newBatch(name, query string) error { } var batches = map[string]string{ - "sessions": "INSERT INTO experimental.sessions (session_id, project_id, user_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, duration, pages_count, events_count, errors_count, issue_score, referrer, issue_types, tracker_version, user_browser, user_browser_version, metadata_1, metadata_2, metadata_3, metadata_4, metadata_5, metadata_6, metadata_7, metadata_8, metadata_9, metadata_10) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - "resources": "INSERT INTO experimental.resources (session_id, project_id, message_id, datetime, url, type, duration, ttfb, header_size, encoded_body_size, decoded_body_size, success) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "sessions": "INSERT INTO experimental.sessions (session_id, project_id, user_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, datetime, duration, pages_count, events_count, errors_count, issue_score, referrer, issue_types, tracker_version, user_browser, user_browser_version, metadata_1, metadata_2, metadata_3, metadata_4, metadata_5, metadata_6, metadata_7, metadata_8, metadata_9, metadata_10) VALUES (?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000))", + "resources": "INSERT INTO experimental.resources (session_id, project_id, message_id, datetime, url, type, duration, ttfb, header_size, encoded_body_size, decoded_body_size, success) VALUES (?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?)", "autocompletes": "INSERT INTO experimental.autocomplete (project_id, type, value) VALUES (?, ?, ?)", "pages": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, request_start, response_start, response_end, dom_content_loaded_event_start, dom_content_loaded_event_end, load_event_start, load_event_end, first_paint, first_contentful_paint_time, speed_index, visually_complete, time_to_interactive, event_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", "clicks": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, label, hesitation_time, event_type) VALUES (?, ?, ?, ?, ?, ?, ?)", "inputs": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, label, event_type) VALUES (?, ?, ?, ?, ?, ?)", "errors": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, source, name, message, error_id, event_type, error_tags_keys, error_tags_values) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - "performance": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, min_fps, avg_fps, max_fps, min_cpu, avg_cpu, max_cpu, min_total_js_heap_size, avg_total_js_heap_size, max_total_js_heap_size, min_used_js_heap_size, avg_used_js_heap_size, max_used_js_heap_size, event_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - "requests": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, request_body, response_body, status, method, duration, success, event_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "performance": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, min_fps, avg_fps, max_fps, min_cpu, avg_cpu, max_cpu, min_total_js_heap_size, avg_total_js_heap_size, max_total_js_heap_size, min_used_js_heap_size, avg_used_js_heap_size, max_used_js_heap_size, event_type) VALUES (?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "requests": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, request_body, response_body, status, method, duration, success, event_type) VALUES (?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?)", "custom": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, name, payload, event_type) VALUES (?, ?, ?, ?, ?, ?, ?)", "graphql": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, name, request_body, response_body, event_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", - "issuesEvents": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, issue_id, issue_type, event_type) VALUES (?, ?, ?, ?, ?, ?, ?)", + "issuesEvents": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, issue_id, issue_type, event_type, url) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", "issues": "INSERT INTO experimental.issues (project_id, issue_id, type, context_string) VALUES (?, ?, ?, ?)", } @@ -101,12 +117,47 @@ func (c *connectorImpl) Prepare() error { } func (c *connectorImpl) Commit() error { + newTask := NewTask() for _, b := range c.batches { + newTask.bulks = append(newTask.bulks, b) + } + c.batches = make(map[string]Bulk, 13) + if err := c.Prepare(); err != nil { + log.Printf("can't prepare new CH batch set: %s", err) + } + c.workerTask <- newTask + return nil +} + +func (c *connectorImpl) Stop() error { + c.done <- struct{}{} + <-c.finished + return c.conn.Close() +} + +func (c *connectorImpl) sendBulks(t *task) { + for _, b := range t.bulks { if err := b.Send(); err != nil { - return fmt.Errorf("can't send batch: %s", err) + log.Printf("can't send batch: %s", err) + } + } +} + +func (c *connectorImpl) worker() { + for { + select { + case t := <-c.workerTask: + start := time.Now() + c.sendBulks(t) + log.Printf("ch bulks dur: %d", time.Now().Sub(start).Milliseconds()) + case <-c.done: + for t := range c.workerTask { + c.sendBulks(t) + } + c.finished <- struct{}{} + return } } - return nil } func (c *connectorImpl) checkError(name string, err error) { @@ -132,6 +183,7 @@ func (c *connectorImpl) InsertIssue(session *types.Session, msg *messages.IssueE issueID, msg.Type, "ISSUE", + msg.URL, ); err != nil { c.checkError("issuesEvents", err) return fmt.Errorf("can't append to issuesEvents batch: %s", err) @@ -289,7 +341,13 @@ func (c *connectorImpl) InsertWebErrorEvent(session *types.Session, msg *types.E keys = append(keys, k) values = append(values, v) } - + // Check error source before insert to avoid panic from clickhouse lib + switch msg.Source { + case "js_exception", "bugsnag", "cloudwatch", "datadog", "elasticsearch", "newrelic", "rollbar", "sentry", "stackdriver", "sumologic": + default: + return fmt.Errorf("unknown error source: %s", msg.Source) + } + // Insert event to batch if err := c.batches["errors"].Append( session.SessionID, uint16(session.ProjectID), @@ -352,7 +410,7 @@ func (c *connectorImpl) InsertAutocomplete(session *types.Session, msgType, msgV return nil } -func (c *connectorImpl) InsertRequest(session *types.Session, msg *messages.FetchEvent, savePayload bool) error { +func (c *connectorImpl) InsertRequest(session *types.Session, msg *messages.NetworkRequest, savePayload bool) error { urlMethod := url.EnsureMethod(msg.Method) if urlMethod == "" { return fmt.Errorf("can't parse http method. sess: %d, method: %s", session.SessionID, msg.Method) @@ -365,8 +423,8 @@ func (c *connectorImpl) InsertRequest(session *types.Session, msg *messages.Fetc if err := c.batches["requests"].Append( session.SessionID, uint16(session.ProjectID), - msg.MessageID, - datetime(msg.Timestamp), + msg.Meta().Index, + datetime(uint64(msg.Meta().Timestamp)), msg.URL, request, response, @@ -386,8 +444,8 @@ func (c *connectorImpl) InsertCustom(session *types.Session, msg *messages.Custo if err := c.batches["custom"].Append( session.SessionID, uint16(session.ProjectID), - msg.MessageID, - datetime(msg.Timestamp), + msg.Meta().Index, + datetime(uint64(msg.Meta().Timestamp)), msg.Name, msg.Payload, "CUSTOM", @@ -398,12 +456,12 @@ func (c *connectorImpl) InsertCustom(session *types.Session, msg *messages.Custo return nil } -func (c *connectorImpl) InsertGraphQL(session *types.Session, msg *messages.GraphQLEvent) error { +func (c *connectorImpl) InsertGraphQL(session *types.Session, msg *messages.GraphQL) error { if err := c.batches["graphql"].Append( session.SessionID, uint16(session.ProjectID), - msg.MessageID, - datetime(msg.Timestamp), + msg.Meta().Index, + datetime(uint64(msg.Meta().Timestamp)), msg.OperationName, nullableString(msg.Variables), nullableString(msg.Response), diff --git a/ee/backend/pkg/failover/failover.go b/ee/backend/pkg/failover/failover.go index 1b9321afc..11ff7e4be 100644 --- a/ee/backend/pkg/failover/failover.go +++ b/ee/backend/pkg/failover/failover.go @@ -91,7 +91,7 @@ func (s *sessionFinderImpl) worker() { func (s *sessionFinderImpl) findSession(sessionID, timestamp, partition uint64) { sessEnd := &messages.SessionEnd{Timestamp: timestamp} sessEnd.SetSessionID(sessionID) - err := s.storage.UploadSessionFiles(sessEnd) + err := s.storage.Upload(sessEnd) if err == nil { log.Printf("found session: %d in partition: %d, original: %d", sessionID, partition, sessionID%numberOfPartitions) diff --git a/ee/backend/pkg/kafka/consumer.go b/ee/backend/pkg/kafka/consumer.go index bea1f0604..fc8c98eaa 100644 --- a/ee/backend/pkg/kafka/consumer.go +++ b/ee/backend/pkg/kafka/consumer.go @@ -120,7 +120,6 @@ func (consumer *Consumer) commitAtTimestamps( if err != nil { return err } - logPartitions("Actually assigned:", assigned) var timestamps []kafka.TopicPartition for _, p := range assigned { // p is a copy here since it is not a pointer @@ -142,7 +141,6 @@ func (consumer *Consumer) commitAtTimestamps( if err != nil { return errors.Wrap(err, "Kafka Consumer retrieving committed error") } - logPartitions("Actually committed:", committed) for _, comm := range committed { if comm.Offset == kafka.OffsetStored || comm.Offset == kafka.OffsetInvalid || diff --git a/ee/backend/pkg/kafka/log.go b/ee/backend/pkg/kafka/log.go deleted file mode 100644 index c71c6d2bd..000000000 --- a/ee/backend/pkg/kafka/log.go +++ /dev/null @@ -1,15 +0,0 @@ -package kafka - -import ( - "fmt" - "log" - - "github.com/confluentinc/confluent-kafka-go/kafka" -) - -func logPartitions(s string, prts []kafka.TopicPartition) { - for _, p := range prts { - s = fmt.Sprintf("%v | %v", s, p.Partition) - } - log.Println(s) -} diff --git a/ee/backend/pkg/kafka/producer.go b/ee/backend/pkg/kafka/producer.go index 1ec241b8a..d3bc2cf51 100644 --- a/ee/backend/pkg/kafka/producer.go +++ b/ee/backend/pkg/kafka/producer.go @@ -15,13 +15,20 @@ type Producer struct { func NewProducer(messageSizeLimit int, useBatch bool) *Producer { kafkaConfig := &kafka.ConfigMap{ - "enable.idempotence": true, - "bootstrap.servers": env.String("KAFKA_SERVERS"), - "go.delivery.reports": true, - "security.protocol": "plaintext", - "go.batch.producer": useBatch, - "queue.buffering.max.ms": 100, - "message.max.bytes": messageSizeLimit, + "enable.idempotence": true, + "bootstrap.servers": env.String("KAFKA_SERVERS"), + "go.delivery.reports": true, + "security.protocol": "plaintext", + "go.batch.producer": useBatch, + "message.max.bytes": messageSizeLimit, // should be synced with broker config + "linger.ms": 1000, + "queue.buffering.max.ms": 1000, + "batch.num.messages": 1000, + "queue.buffering.max.messages": 1000, + "retries": 3, + "retry.backoff.ms": 100, + "max.in.flight.requests.per.connection": 1, + "compression.type": env.String("COMPRESSION_TYPE"), } // Apply ssl configuration if env.Bool("KAFKA_USE_SSL") { diff --git a/ee/connectors/consumer.py b/ee/connectors/consumer.py index c8d61ff7b..1c3488642 100644 --- a/ee/connectors/consumer.py +++ b/ee/connectors/consumer.py @@ -1,5 +1,5 @@ import os -from kafka import KafkaConsumer +from confluent_kafka import Consumer from datetime import datetime from collections import defaultdict @@ -29,17 +29,21 @@ def main(): sessions_batch = [] codec = MessageCodec() - consumer = KafkaConsumer(security_protocol="SSL", - bootstrap_servers=[os.environ['KAFKA_SERVER_2'], - os.environ['KAFKA_SERVER_1']], - group_id=f"my_test3_connector_{DATABASE}", - auto_offset_reset="earliest", - enable_auto_commit=False - ) + consumer = Consumer({ + "security.protocol": "SSL", + "bootstrap.servers": ",".join([os.environ['KAFKA_SERVER_1'], + os.environ['KAFKA_SERVER_2']]), + "group.id": f"connector_{DATABASE}", + "auto.offset.reset": "earliest", + "enable.auto.commit": False + }) - consumer.subscribe(topics=["raw", "raw_ios"]) + consumer.subscribe(["raw", "raw_ios"]) print("Kafka consumer subscribed") - for msg in consumer: + while True: + msg.consumer.poll(1.0) + if msg is None: + continue messages = codec.decode_detailed(msg.value) session_id = codec.decode_key(msg.key) if messages is None: diff --git a/ee/connectors/deploy/Dockerfile_bigquery b/ee/connectors/deploy/Dockerfile_bigquery index 770ccf8fa..515914e15 100644 --- a/ee/connectors/deploy/Dockerfile_bigquery +++ b/ee/connectors/deploy/Dockerfile_bigquery @@ -1,8 +1,13 @@ -FROM python:3.8-slim +FROM python:3.11 WORKDIR /usr/src/app COPY . . +RUN apt update +RUN apt-get install -y libc-dev libffi-dev gcc +RUN apt update && apt -y install software-properties-common gcc +RUN git clone https://github.com/edenhill/librdkafka +RUN cd librdkafka && ./configure && make && make install && ldconfig RUN pip install -r ./deploy/requirements_bigquery.txt diff --git a/ee/connectors/deploy/Dockerfile_clickhouse b/ee/connectors/deploy/Dockerfile_clickhouse index 0b19edeb6..f2dad8f65 100644 --- a/ee/connectors/deploy/Dockerfile_clickhouse +++ b/ee/connectors/deploy/Dockerfile_clickhouse @@ -1,8 +1,13 @@ -FROM python:3.8-slim +FROM python:3.11 WORKDIR /usr/src/app COPY . . +RUN apt update +RUN apt-get install -y libc-dev libffi-dev gcc +RUN apt update && apt -y install software-properties-common gcc +RUN git clone https://github.com/edenhill/librdkafka +RUN cd librdkafka && ./configure && make && make install && ldconfig RUN pip install -r ./deploy/requirements_clickhouse.txt diff --git a/ee/connectors/deploy/Dockerfile_pg b/ee/connectors/deploy/Dockerfile_pg index 0598016cc..a8b1c0f01 100644 --- a/ee/connectors/deploy/Dockerfile_pg +++ b/ee/connectors/deploy/Dockerfile_pg @@ -1,8 +1,13 @@ -FROM python:3.8-slim +FROM python:3.11 WORKDIR /usr/src/app COPY . . +RUN apt update +RUN apt-get install -y libc-dev libffi-dev gcc +RUN apt update && apt -y install software-properties-common gcc +RUN git clone https://github.com/edenhill/librdkafka +RUN cd librdkafka && ./configure && make && make install && ldconfig RUN pip install -r ./deploy/requirements_pg.txt diff --git a/ee/connectors/deploy/Dockerfile_redshift b/ee/connectors/deploy/Dockerfile_redshift index 87e99acc7..c69739623 100644 --- a/ee/connectors/deploy/Dockerfile_redshift +++ b/ee/connectors/deploy/Dockerfile_redshift @@ -1,8 +1,13 @@ -FROM python:3.8-slim +FROM python:3.11 WORKDIR /usr/src/app COPY . . +RUN apt update +RUN apt-get install -y libc-dev libffi-dev gcc +RUN apt update && apt -y install software-properties-common gcc +RUN git clone https://github.com/edenhill/librdkafka +RUN cd librdkafka && ./configure && make && make install && ldconfig RUN pip install -r ./deploy/requirements_redshift.txt diff --git a/ee/connectors/deploy/Dockerfile_snowflake b/ee/connectors/deploy/Dockerfile_snowflake index 4eae51685..1d4b926a8 100644 --- a/ee/connectors/deploy/Dockerfile_snowflake +++ b/ee/connectors/deploy/Dockerfile_snowflake @@ -1,8 +1,13 @@ -FROM python:3.8-slim +FROM python:3.11 WORKDIR /usr/src/app COPY . . +RUN apt update +RUN apt-get install -y libc-dev libffi-dev gcc +RUN apt update && apt -y install software-properties-common gcc +RUN git clone https://github.com/edenhill/librdkafka +RUN cd librdkafka && ./configure && make && make install && ldconfig RUN pip install -r ./deploy/requirements_snowflake.txt diff --git a/ee/connectors/deploy/requirements_bigquery.txt b/ee/connectors/deploy/requirements_bigquery.txt index d4d63c953..7ce437323 100644 --- a/ee/connectors/deploy/requirements_bigquery.txt +++ b/ee/connectors/deploy/requirements_bigquery.txt @@ -1,7 +1,7 @@ -kafka-python==2.0.2 +confluent-kafka psycopg2-binary==2.9.3 SQLAlchemy==1.4.43 -google-cloud-bigquery +google-cloud-bigquery==3.4.2 pandas==1.5.1 PyYAML pandas-gbq diff --git a/ee/connectors/deploy/requirements_clickhouse.txt b/ee/connectors/deploy/requirements_clickhouse.txt index b21ea36c5..8853a865f 100644 --- a/ee/connectors/deploy/requirements_clickhouse.txt +++ b/ee/connectors/deploy/requirements_clickhouse.txt @@ -3,7 +3,7 @@ chardet==5.0.0 clickhouse-driver==0.2.4 clickhouse-sqlalchemy==0.2.2 idna==3.4 -kafka-python==2.0.2 +confluent-kafka pandas==1.5.1 pytz==2022.6 requests==2.28.1 diff --git a/ee/connectors/deploy/requirements_pg.txt b/ee/connectors/deploy/requirements_pg.txt index 09bf8b34d..8354e61a9 100644 --- a/ee/connectors/deploy/requirements_pg.txt +++ b/ee/connectors/deploy/requirements_pg.txt @@ -1,7 +1,7 @@ certifi==2022.09.24 chardet==5.0.0 idna==3.4 -kafka-python==2.0.2 +confluent-kafka pandas==1.5.1 psycopg2-binary==2.9.3 pytz==2022.6 diff --git a/ee/connectors/deploy/requirements_redshift.txt b/ee/connectors/deploy/requirements_redshift.txt index e4182cb92..7bd5e3f08 100644 --- a/ee/connectors/deploy/requirements_redshift.txt +++ b/ee/connectors/deploy/requirements_redshift.txt @@ -3,7 +3,7 @@ chardet==5.0.0 clickhouse-driver==0.2.4 clickhouse-sqlalchemy==0.2.2 idna==3.4 -kafka-python==2.0.2 +confluent-kafka psycopg2-binary==2.9.3 pytz==2022.6 requests==2.28.1 diff --git a/ee/connectors/deploy/requirements_snowflake.txt b/ee/connectors/deploy/requirements_snowflake.txt index 895326b32..5a3aaca99 100644 --- a/ee/connectors/deploy/requirements_snowflake.txt +++ b/ee/connectors/deploy/requirements_snowflake.txt @@ -1,5 +1,5 @@ pandas==1.5.1 -kafka-python==2.0.2 +confluent-kafka SQLAlchemy==1.4.43 snowflake-connector-python==2.8.2 snowflake-sqlalchemy==1.4.4 diff --git a/ee/connectors/msgcodec/messages.py b/ee/connectors/msgcodec/messages.py index 41181d924..54f8df955 100644 --- a/ee/connectors/msgcodec/messages.py +++ b/ee/connectors/msgcodec/messages.py @@ -6,34 +6,6 @@ class Message(ABC): pass -class BatchMeta(Message): - __id__ = 80 - - def __init__(self, page_no, first_index, timestamp): - self.page_no = page_no - self.first_index = first_index - self.timestamp = timestamp - - -class BatchMetadata(Message): - __id__ = 81 - - def __init__(self, version, page_no, first_index, timestamp, location): - self.version = version - self.page_no = page_no - self.first_index = first_index - self.timestamp = timestamp - self.location = location - - -class PartitionedMessage(Message): - __id__ = 82 - - def __init__(self, part_no, part_total): - self.part_no = part_no - self.part_total = part_total - - class Timestamp(Message): __id__ = 0 @@ -213,6 +185,20 @@ class MouseMove(Message): self.y = y +class NetworkRequest(Message): + __id__ = 21 + + def __init__(self, type, method, url, request, response, status, timestamp, duration): + self.type = type + self.method = method + self.url = url + self.request = request + self.response = response + self.status = status + self.timestamp = timestamp + self.duration = duration + + class ConsoleLog(Message): __id__ = 22 @@ -265,7 +251,7 @@ class IntegrationEvent(Message): self.payload = payload -class RawCustomEvent(Message): +class CustomEvent(Message): __id__ = 27 def __init__(self, name, payload): @@ -358,16 +344,6 @@ class ResourceEvent(Message): self.status = status -class CustomEvent(Message): - __id__ = 36 - - def __init__(self, message_id, timestamp, name, payload): - self.message_id = message_id - self.timestamp = timestamp - self.name = name - self.payload = payload - - class CSSInsertRule(Message): __id__ = 37 @@ -423,15 +399,6 @@ class StateAction(Message): self.type = type -class StateActionEvent(Message): - __id__ = 43 - - def __init__(self, message_id, timestamp, type): - self.message_id = message_id - self.timestamp = timestamp - self.type = type - - class Redux(Message): __id__ = 44 @@ -486,30 +453,21 @@ class PerformanceTrack(Message): self.used_js_heap_size = used_js_heap_size -class GraphQLEvent(Message): +class StringDict(Message): __id__ = 50 - def __init__(self, message_id, timestamp, operation_kind, operation_name, variables, response): - self.message_id = message_id - self.timestamp = timestamp - self.operation_kind = operation_kind - self.operation_name = operation_name - self.variables = variables - self.response = response + def __init__(self, key, value): + self.key = key + self.value = value -class FetchEvent(Message): +class SetNodeAttributeDict(Message): __id__ = 51 - def __init__(self, message_id, timestamp, method, url, request, response, status, duration): - self.message_id = message_id - self.timestamp = timestamp - self.method = method - self.url = url - self.request = request - self.response = response - self.status = status - self.duration = duration + def __init__(self, id, name_key, value_key): + self.id = id + self.name_key = name_key + self.value_key = value_key class DOMDrop(Message): @@ -617,7 +575,7 @@ class SetCSSDataURLBased(Message): self.base_url = base_url -class IssueEvent(Message): +class IssueEventDeprecated(Message): __id__ = 62 def __init__(self, message_id, timestamp, type, context_string, context, payload): @@ -740,14 +698,6 @@ class AdoptedSSRemoveOwner(Message): self.id = id -class Zustand(Message): - __id__ = 79 - - def __init__(self, mutation, state): - self.mutation = mutation - self.state = state - - class JSException(Message): __id__ = 78 @@ -758,6 +708,55 @@ class JSException(Message): self.metadata = metadata +class Zustand(Message): + __id__ = 79 + + def __init__(self, mutation, state): + self.mutation = mutation + self.state = state + + +class BatchMeta(Message): + __id__ = 80 + + def __init__(self, page_no, first_index, timestamp): + self.page_no = page_no + self.first_index = first_index + self.timestamp = timestamp + + +class BatchMetadata(Message): + __id__ = 81 + + def __init__(self, version, page_no, first_index, timestamp, location): + self.version = version + self.page_no = page_no + self.first_index = first_index + self.timestamp = timestamp + self.location = location + + +class PartitionedMessage(Message): + __id__ = 82 + + def __init__(self, part_no, part_total): + self.part_no = part_no + self.part_total = part_total + + +class IssueEvent(Message): + __id__ = 125 + + def __init__(self, message_id, timestamp, type, context_string, context, payload, url): + self.message_id = message_id + self.timestamp = timestamp + self.type = type + self.context_string = context_string + self.context = context + self.payload = payload + self.url = url + + class SessionEnd(Message): __id__ = 126 diff --git a/ee/connectors/msgcodec/msgcodec.py b/ee/connectors/msgcodec/msgcodec.py index 38bb6d3c2..0ba21ea12 100644 --- a/ee/connectors/msgcodec/msgcodec.py +++ b/ee/connectors/msgcodec/msgcodec.py @@ -76,28 +76,6 @@ class MessageCodec(Codec): def read_head_message(self, reader: io.BytesIO, message_id) -> Message: - if message_id == 80: - return BatchMeta( - page_no=self.read_uint(reader), - first_index=self.read_uint(reader), - timestamp=self.read_int(reader) - ) - - if message_id == 81: - return BatchMetadata( - version=self.read_uint(reader), - page_no=self.read_uint(reader), - first_index=self.read_uint(reader), - timestamp=self.read_int(reader), - location=self.read_string(reader) - ) - - if message_id == 82: - return PartitionedMessage( - part_no=self.read_uint(reader), - part_total=self.read_uint(reader) - ) - if message_id == 0: return Timestamp( timestamp=self.read_uint(reader) @@ -237,6 +215,18 @@ class MessageCodec(Codec): y=self.read_uint(reader) ) + if message_id == 21: + return NetworkRequest( + type=self.read_string(reader), + method=self.read_string(reader), + url=self.read_string(reader), + request=self.read_string(reader), + response=self.read_string(reader), + status=self.read_uint(reader), + timestamp=self.read_uint(reader), + duration=self.read_uint(reader) + ) + if message_id == 22: return ConsoleLog( level=self.read_string(reader), @@ -280,7 +270,7 @@ class MessageCodec(Codec): ) if message_id == 27: - return RawCustomEvent( + return CustomEvent( name=self.read_string(reader), payload=self.read_string(reader) ) @@ -356,14 +346,6 @@ class MessageCodec(Codec): status=self.read_uint(reader) ) - if message_id == 36: - return CustomEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - name=self.read_string(reader), - payload=self.read_string(reader) - ) - if message_id == 37: return CSSInsertRule( id=self.read_uint(reader), @@ -407,13 +389,6 @@ class MessageCodec(Codec): type=self.read_string(reader) ) - if message_id == 43: - return StateActionEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - type=self.read_string(reader) - ) - if message_id == 44: return Redux( action=self.read_string(reader), @@ -457,25 +432,16 @@ class MessageCodec(Codec): ) if message_id == 50: - return GraphQLEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - operation_kind=self.read_string(reader), - operation_name=self.read_string(reader), - variables=self.read_string(reader), - response=self.read_string(reader) + return StringDict( + key=self.read_uint(reader), + value=self.read_string(reader) ) if message_id == 51: - return FetchEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - method=self.read_string(reader), - url=self.read_string(reader), - request=self.read_string(reader), - response=self.read_string(reader), - status=self.read_uint(reader), - duration=self.read_uint(reader) + return SetNodeAttributeDict( + id=self.read_uint(reader), + name_key=self.read_uint(reader), + value_key=self.read_uint(reader) ) if message_id == 52: @@ -564,7 +530,7 @@ class MessageCodec(Codec): ) if message_id == 62: - return IssueEvent( + return IssueEventDeprecated( message_id=self.read_uint(reader), timestamp=self.read_uint(reader), type=self.read_string(reader), @@ -658,12 +624,6 @@ class MessageCodec(Codec): id=self.read_uint(reader) ) - if message_id == 79: - return Zustand( - mutation=self.read_string(reader), - state=self.read_string(reader) - ) - if message_id == 78: return JSException( name=self.read_string(reader), @@ -672,6 +632,45 @@ class MessageCodec(Codec): metadata=self.read_string(reader) ) + if message_id == 79: + return Zustand( + mutation=self.read_string(reader), + state=self.read_string(reader) + ) + + if message_id == 80: + return BatchMeta( + page_no=self.read_uint(reader), + first_index=self.read_uint(reader), + timestamp=self.read_int(reader) + ) + + if message_id == 81: + return BatchMetadata( + version=self.read_uint(reader), + page_no=self.read_uint(reader), + first_index=self.read_uint(reader), + timestamp=self.read_int(reader), + location=self.read_string(reader) + ) + + if message_id == 82: + return PartitionedMessage( + part_no=self.read_uint(reader), + part_total=self.read_uint(reader) + ) + + if message_id == 125: + return IssueEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + type=self.read_string(reader), + context_string=self.read_string(reader), + context=self.read_string(reader), + payload=self.read_string(reader), + url=self.read_string(reader) + ) + if message_id == 126: return SessionEnd( timestamp=self.read_uint(reader), diff --git a/ee/quickwit/Dockerfile b/ee/quickwit/Dockerfile new file mode 100644 index 000000000..00180501b --- /dev/null +++ b/ee/quickwit/Dockerfile @@ -0,0 +1,23 @@ +FROM quickwit/quickwit + +COPY *.yaml /quickwit/ +COPY entrypoint.sh /quickwit/ +COPY consumer.py /quickwit/ +COPY requirements.txt /quickwit/ +COPY msgcodec /quickwit/msgcodec +WORKDIR /quickwit + +RUN apt-get update +RUN apt-get install python3 python3-pip -y +RUN apt-get clean +RUN pip install -r requirements.txt + +COPY env.default .env +RUN source .env + +ENV filter="true" \ + encrypted="false" + +EXPOSE 7280 + +ENTRYPOINT ./entrypoint.sh diff --git a/ee/quickwit/consumer.py b/ee/quickwit/consumer.py new file mode 100644 index 000000000..af14f0a9b --- /dev/null +++ b/ee/quickwit/consumer.py @@ -0,0 +1,190 @@ +from decouple import config +from confluent_kafka import Consumer +from datetime import datetime +import os as _os +import queue +import requests +import json + + +from time import time, sleep + + +#decryption = config('encrypted', cast=bool) +decryption = False +MessageCodec = None +max_retry=3 +Fetch, FetchEvent, PageEvent, GraphQ = None, None, None, None +if decryption: + from msgcodec.msgcodec import MessageCodec + from msgcodec.messages import Fetch, FetchEvent, PageEvent, GraphQL + print("Enabled decryption mode") + +def _quickwit_ingest(index, data_list, retry=0): + try: + res = requests.post(f'http://localhost:7280/api/v1/{index}/ingest', data=__jsonify_data(data_list, index)) + except requests.exceptions.ConnectionError as e: + retry += 1 + assert retry <= max_retry, f'[ENDPOINT CONNECTION FAIL] Failed to connect to endpoint http://localhost:7280/api/v1/{index}/ingest\n{e}\n' + sleep(5*retry) + print(f"[ENDPOINT ERROR] Failed to connect to endpoint http://localhost:7280/api/v1/{index}/ingest, retrying in {5*retry} seconds..\n") + return _quickwit_ingest(index, data_list, retry=retry) + return res + +def __jsonify_data(data_list, msg_type): + res = list() + i = 0 + for data in data_list: + if msg_type == 'fetchevent': + try: + _tmp = data['request'] + if _tmp != '': + data['request'] = json.loads(_tmp) + else: + data['request'] = {} + _tmp = data['response'] + if _tmp != '': + data['response'] = json.loads(_tmp) + if data['response']['body'][:1] == '{' or data['response']['body'][:2] == '[{': + data['response']['body'] = json.loads(data['response']['body']) + else: + data['response'] = {} + except Exception as e: + print(f'Error {e}\tWhile decoding fetchevent\nEvent: {data}\n') + elif msg_type == 'graphql': + try: + _tmp = data['variables'] + if _tmp != '': + data['variables'] = json.loads(_tmp) + else: + data['variables'] = {} + _tmp = data['response'] + if _tmp != '': + data['response'] = json.loads(_tmp) + else: + data['response'] = {} + except Exception as e: + print(f'Error {e}\tWhile decoding graphql\nEvent: {data}\n') + i += 1 + res.append(json.dumps(data)) + return '\n'.join(res) + +def message_type(message): + if decryption: + if isinstance(message, FetchEvent) or isinstance(Fetch): + return 'fetchevent' + elif isinstance(message, PageEvent): + return 'pageevent' + elif isinstance(message, GraphQL): + return 'graphql' + else: + return 'default' + else: + if 'loaded' in message.keys(): + return 'pageevent' + elif 'variables' in message.keys(): + return 'graphql' + elif 'status' in message.keys(): + return 'fetchevent' + else: + return 'default' + + +class KafkaFilter(): + + def __init__(self): + kafka_sources = config('KAFKA_SERVER') + topic = config('QUICKWIT_TOPIC') + + fetchevent_maxsize = config('fetch_maxsize', default=100, cast=int) + graphql_maxsize = config('graphql_maxsize', default=100, cast=int) + pageevent_maxsize = config('pageevent_maxsize', default=100, cast=int) + + if decryption: + self.codec = MessageCodec() + self.consumer = Consumer({ + "security.protocol": "SSL", + "bootstrap.servers": kafka_sources, + "group.id": config("group_id"), + "auto.offset.reset": "earliest", + "enable.auto.commit":False + }) + else: + self.consumer = Consumer({ + "security.protocol": "SSL", + "bootstrap.servers": kafka_sources, + "group.id": config("group_id"), + "auto.offset.reset": "earliest", + #value_deserializer=lambda m: json.loads(m.decode('utf-8')), + "enable.auto.commit": False + }) + self.consumer.subscribe([topic]) + self.queues = {'fetchevent': queue.Queue(fetchevent_maxsize), + 'graphql': queue.Queue(graphql_maxsize), + 'pageevent': queue.Queue(pageevent_maxsize) + } + + def add_to_queue(self, message): + associated_queue = message_type(message) + if associated_queue == 'default': + return + if self.queues[associated_queue].full(): + self.flush_to_quickwit() + self.queues[associated_queue].put(message) + + def flush_to_quickwit(self): + for queue_name, _queue in self.queues.items(): + _list = list() + unix_timestamp = int(datetime.now().timestamp()) + while not _queue.empty(): + msg = _queue.get() + if decryption: + value = msg.__dict__ + else: + value = dict(msg) + value['insertion_timestamp'] = unix_timestamp + if queue_name == 'fetchevent' and 'message_id' not in value.keys(): + value['message_id'] = 0 + _list.append(value) + if len(_list) > 0: + _quickwit_ingest(queue_name, _list) + self.consumer.commit() + + def run(self): + _tmp_previous = None + repeated = False + while True: + msg = self.consumer.poll(1.0) + if msg is None: + continue + if msg.error(): + print(f'[Consumer error] {msg.error()}') + continue + value = json.loads(msg.value().decode('utf-8')) + if decryption: + messages = self.codec.decode_detailed(value) + else: + messages = [value] + + if _tmp_previous is None: + _tmp_previous = messages + if type(messages)==list: + for message in messages: + self.add_to_queue(message) + else: + self.add_to_queue(messages) + elif _tmp_previous != messages: + if type(messages)==list: + for message in messages: + self.add_to_queue(message) + else: + self.add_to_queue(messages) + _tmp_previous = messages + repeated = False + elif not repeated: + repeated = True + + +if __name__ == '__main__': + layer = KafkaFilter() + layer.run() diff --git a/ee/quickwit/entrypoint.sh b/ee/quickwit/entrypoint.sh new file mode 100755 index 000000000..549cdbd12 --- /dev/null +++ b/ee/quickwit/entrypoint.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +# This script will rplace the env variable values to the config files + +ls config/ +find /quickwit/ -type f -name "*.yaml" -exec sed -i "s#{{KAFKA_SERVER}}#${KAFKA_SERVER}#g" {} \; +find /quickwit/ -type f -name "*.yaml" -exec sed -i "s#{{AWS_BUCKET}}#${AWS_BUCKET}#g" {} \; +find /quickwit/ -type f -name "*.yaml" -exec sed -i "s/{{QUICKWIT_TOPIC}}/${QUICKWIT_TOPIC}/g" {} \; +find /quickwit/ -type f -name "*.yaml" -exec sed -i "s#{{data_dir_path}}#${data_dir_path}#g" {} \; + +quickwit index create --index-config index-config-fetch.yaml --config s3-config.yaml +quickwit index create --index-config index-config-graphql.yaml --config s3-config.yaml +quickwit index create --index-config index-config-pageevent.yaml --config s3-config.yaml + +quickwit source delete --index fetchevent --source fetch-kafka --config s3-config.yaml +quickwit source delete --index graphql --source graphql-kafka --config s3-config.yaml +quickwit source delete --index pageevent --source pageevent-kafka --config s3-config.yaml + + +if [${filter} == "false"]; then + quickwit source create --index fetchevent --source-config source-fetch.yaml --config s3-config.yaml + quickwit source create --index graphql --source-config source-graphql.yaml --config s3-config.yaml + quickwit source create --index pageevent --source-config source-pageevent.yaml --config s3-config.yaml + quickwit run --config s3-config-listen.yaml +else + quickwit run --config s3-config-listen.yaml & python3 consumer.py && fg +fi diff --git a/ee/quickwit/env.default b/ee/quickwit/env.default new file mode 100644 index 000000000..24cf79186 --- /dev/null +++ b/ee/quickwit/env.default @@ -0,0 +1,6 @@ +KAFKA_SERVER= +QUICKWIT_TOPIC=ee-quickwit +fetch_maxsize=800 +graphql_maxsize=800 +pageevent_maxsize=800 +group_id=ee-quickwit diff --git a/ee/quickwit/index-config-fetch.yaml b/ee/quickwit/index-config-fetch.yaml index 1d89f72c9..55cced160 100644 --- a/ee/quickwit/index-config-fetch.yaml +++ b/ee/quickwit/index-config-fetch.yaml @@ -2,13 +2,28 @@ # Index config file for gh-archive dataset. # -version: 0 +version: 0.4 index_id: fetchevent doc_mapping: - mode: strict + mode: dynamic field_mappings: + - name: insertion_timestamp + type: datetime + input_formats: + - unix_timestamp + precision: seconds + fast: true + - name: project_id + type: i64 + fast: true + - name: session_id + type: i64 + fast: true + - name: message_id + type: i64 + fast: true - name: method type: text tokenizer: default @@ -18,11 +33,15 @@ doc_mapping: tokenizer: default record: position - name: request - type: text + type: json + stored: true + indexed: true tokenizer: default record: position - name: response - type: text + type: json + stored: true + indexed: true tokenizer: default record: position - name: status @@ -35,6 +54,11 @@ doc_mapping: - name: duration type: i64 fast: true + timestamp_field: insertion_timestamp search_settings: - default_search_fields: [url, request, response] + default_search_fields: [project_id, session_id, url, request] + +retention: + period: 30 days + schedule: hourly diff --git a/ee/quickwit/index-config-graphql.yaml b/ee/quickwit/index-config-graphql.yaml index bac1d8406..b94c5d4a6 100644 --- a/ee/quickwit/index-config-graphql.yaml +++ b/ee/quickwit/index-config-graphql.yaml @@ -2,13 +2,25 @@ # Index config file for gh-archive dataset. # -version: 0 +version: 0.4 index_id: graphql doc_mapping: - mode: strict + mode: dynamic field_mappings: + - name: insertion_timestamp + type: datetime + input_formats: + - unix_timestamp + precision: seconds + fast: true + - name: project_id + type: i64 + fast: true + - name: session_id + type: i64 + fast: true - name: operation_kind type: text tokenizer: default @@ -18,13 +30,22 @@ doc_mapping: tokenizer: default record: position - name: variables - type: text + type: json + stored: true + indexed: true tokenizer: default record: position - name: response - type: text + type: json + stored: true + indexed: true tokenizer: default record: position + timestamp_field: insertion_timestamp search_settings: - default_search_fields: [operation_kind, operation_name, variables] + default_search_fields: [project_id, session_id, operation_kind, operation_name, variables] + +retention: + period: 30 days + schedule: hourly diff --git a/ee/quickwit/index-config-pageevent.yaml b/ee/quickwit/index-config-pageevent.yaml index e47dd6a1d..1ffdae9f0 100644 --- a/ee/quickwit/index-config-pageevent.yaml +++ b/ee/quickwit/index-config-pageevent.yaml @@ -2,13 +2,25 @@ # Index config file for gh-archive dataset. # -version: 0 +version: 0.4 index_id: pageevent doc_mapping: mode: strict field_mappings: + - name: insertion_timestamp + type: datetime + input_formats: + - unix_timestamp + precision: seconds + fast: true + - name: project_id + type: i64 + fast: true + - name: session_id + type: i64 + fast: true - name: message_id type: i64 indexed: true @@ -63,6 +75,11 @@ doc_mapping: - name: time_to_interactive type: i64 fast: true + timestamp_field: insertion_timestamp search_settings: - default_search_fields: [url, referrer, visually_complete] + default_search_fields: [project_id, session_id, url, referrer, visually_complete] + +retention: + period: 30 days + schedule: hourly diff --git a/ee/quickwit/requirements.txt b/ee/quickwit/requirements.txt index d328a9142..78fb272f5 100644 --- a/ee/quickwit/requirements.txt +++ b/ee/quickwit/requirements.txt @@ -1 +1,4 @@ -kafka-python +confluent-kafka +python-decouple +requests +zstd diff --git a/ee/recommendation/Dockerfile b/ee/recommendation/Dockerfile new file mode 100644 index 000000000..992bcf89a --- /dev/null +++ b/ee/recommendation/Dockerfile @@ -0,0 +1,14 @@ +FROM apache/airflow:2.4.3 +COPY requirements.txt . + +USER root +RUN apt-get update \ + && apt-get install -y \ + vim \ + && apt-get install gcc libc-dev g++ -y \ + && apt-get install -y pkg-config libxml2-dev libxmlsec1-dev libxmlsec1-openssl + + +USER airflow +RUN pip install --upgrade pip +RUN pip install -r requirements.txt diff --git a/ee/recommendation/clean.sh b/ee/recommendation/clean.sh new file mode 100644 index 000000000..857c8d63d --- /dev/null +++ b/ee/recommendation/clean.sh @@ -0,0 +1 @@ +docker-compose down --volumes --rmi all diff --git a/ee/recommendation/dags/training_dag.py b/ee/recommendation/dags/training_dag.py new file mode 100644 index 000000000..ff340f772 --- /dev/null +++ b/ee/recommendation/dags/training_dag.py @@ -0,0 +1,46 @@ +from datetime import datetime, timedelta +from textwrap import dedent + +import pendulum + +from airflow import DAG +from airflow.operators.bash import BashOperator +from airflow.operators.python import PythonOperator +import os +_work_dir = os.getcwd() + +def my_function(): + l = os.listdir('scripts') + print(l) + return l + +dag = DAG( + "first_test", + default_args={ + "depends_on_past": True, + "retries": 1, + "retry_delay": timedelta(minutes=3), + }, + start_date=pendulum.datetime(2015, 12, 1, tz="UTC"), + description="My first test", + schedule="@daily", + catchup=False, +) + + +#assigning the task for our dag to do +with dag: + first_world = PythonOperator( + task_id='FirstTest', + python_callable=my_function, + ) + hello_world = BashOperator( + task_id='OneTest', + bash_command=f'python {_work_dir}/scripts/processing.py --batch_size 500', + # provide_context=True + ) + this_world = BashOperator( + task_id='ThisTest', + bash_command=f'python {_work_dir}/scripts/task.py --mode train --kernel linear', + ) + first_world >> hello_world >> this_world diff --git a/ee/recommendation/docker-compose.yaml b/ee/recommendation/docker-compose.yaml new file mode 100644 index 000000000..d7d068551 --- /dev/null +++ b/ee/recommendation/docker-compose.yaml @@ -0,0 +1,285 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Basic Airflow cluster configuration for CeleryExecutor with Redis and PostgreSQL. +# +# WARNING: This configuration is for local development. Do not use it in a production deployment. +# +# This configuration supports basic configuration using environment variables or an .env file +# The following variables are supported: +# +# AIRFLOW_IMAGE_NAME - Docker image name used to run Airflow. +# Default: apache/airflow:2.4.3 +# AIRFLOW_UID - User ID in Airflow containers +# Default: 50000 +# Those configurations are useful mostly in case of standalone testing/running Airflow in test/try-out mode +# +# _AIRFLOW_WWW_USER_USERNAME - Username for the administrator account (if requested). +# Default: airflow +# _AIRFLOW_WWW_USER_PASSWORD - Password for the administrator account (if requested). +# Default: airflow +# _PIP_ADDITIONAL_REQUIREMENTS - Additional PIP requirements to add when starting all containers. +# Default: '' +# +# Feel free to modify this file to suit your needs. +--- +version: '3' +x-airflow-common: + &airflow-common + # In order to add custom dependencies or upgrade provider packages you can use your extended image. + # Comment the image line, place your Dockerfile in the directory where you placed the docker-compose.yaml + # and uncomment the "build" line below, Then run `docker-compose build` to build the images. + # image: ${AIRFLOW_IMAGE_NAME:-apache/airflow:2.4.3} + build: . + environment: + &airflow-common-env + AIRFLOW__CORE__EXECUTOR: CeleryExecutor + AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow + # For backward compatibility, with Airflow <2.3 + AIRFLOW__CORE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow + AIRFLOW__CELERY__RESULT_BACKEND: db+postgresql://airflow:airflow@postgres/airflow + AIRFLOW__CELERY__BROKER_URL: redis://:@redis:6379/0 + AIRFLOW__CORE__FERNET_KEY: '' + AIRFLOW__CORE__DAGS_ARE_PAUSED_AT_CREATION: 'true' + AIRFLOW__CORE__LOAD_EXAMPLES: 'false' + AIRFLOW__API__AUTH_BACKENDS: 'airflow.api.auth.backend.basic_auth' + _PIP_ADDITIONAL_REQUIREMENTS: 'argcomplete' + AIRFLOW__CODE_EDITOR__ENABLED: 'true' + AIRFLOW__CODE_EDITOR__GIT_ENABLED: 'false' + AIRFLOW__CODE_EDITOR__STRING_NORMALIZATION: 'true' + AIRFLOW__CODE_EDITOR__MOUNT: '/opt/airflow/dags' + pg_user: "${pg_user}" + pg_password: "${pg_password}" + pg_dbname: "${pg_dbname}" + pg_host: "${pg_host}" + pg_port: "${pg_port}" + PG_TIMEOUT: "${PG_TIMEOUT}" + PG_POOL: "${PG_POOL}" + volumes: + - ./dags:/opt/airflow/dags + - ./logs:/opt/airflow/logs + - ./plugins:/opt/airflow/plugins + - ./scripts:/opt/airflow/scripts + - ./cache:/opt/airflow/cache + user: "${AIRFLOW_UID:-50000}:0" + depends_on: + &airflow-common-depends-on + redis: + condition: service_healthy + postgres: + condition: service_healthy + +services: + postgres: + image: postgres:13 + environment: + POSTGRES_USER: airflow + POSTGRES_PASSWORD: airflow + POSTGRES_DB: airflow + volumes: + - postgres-db-volume:/var/lib/postgresql/data + healthcheck: + test: ["CMD", "pg_isready", "-U", "airflow"] + interval: 5s + retries: 5 + restart: always + + redis: + image: redis:latest + expose: + - 6379 + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 30s + retries: 50 + restart: always + + airflow-webserver: + <<: *airflow-common + command: webserver + ports: + - 8080:8080 + healthcheck: + test: ["CMD", "curl", "--fail", "http://localhost:8080/health"] + interval: 10s + timeout: 10s + retries: 5 + restart: always + depends_on: + <<: *airflow-common-depends-on + airflow-init: + condition: service_completed_successfully + + airflow-scheduler: + <<: *airflow-common + command: scheduler + healthcheck: + test: ["CMD-SHELL", 'airflow jobs check --job-type SchedulerJob --hostname "$${HOSTNAME}"'] + interval: 10s + timeout: 10s + retries: 5 + restart: always + depends_on: + <<: *airflow-common-depends-on + airflow-init: + condition: service_completed_successfully + + airflow-worker: + <<: *airflow-common + command: celery worker + healthcheck: + test: + - "CMD-SHELL" + - 'celery --app airflow.executors.celery_executor.app inspect ping -d "celery@$${HOSTNAME}"' + interval: 10s + timeout: 10s + retries: 5 + environment: + <<: *airflow-common-env + # Required to handle warm shutdown of the celery workers properly + # See https://airflow.apache.org/docs/docker-stack/entrypoint.html#signal-propagation + DUMB_INIT_SETSID: "0" + restart: always + depends_on: + <<: *airflow-common-depends-on + airflow-init: + condition: service_completed_successfully + + airflow-triggerer: + <<: *airflow-common + command: triggerer + healthcheck: + test: ["CMD-SHELL", 'airflow jobs check --job-type TriggererJob --hostname "$${HOSTNAME}"'] + interval: 10s + timeout: 10s + retries: 5 + restart: always + depends_on: + <<: *airflow-common-depends-on + airflow-init: + condition: service_completed_successfully + + airflow-init: + <<: *airflow-common + entrypoint: /bin/bash + # yamllint disable rule:line-length + command: + - -c + - | + function ver() { + printf "%04d%04d%04d%04d" $${1//./ } + } + register-python-argcomplete airflow >> ~/.bashrc + airflow_version=$$(AIRFLOW__LOGGING__LOGGING_LEVEL=INFO && gosu airflow airflow version) + airflow_version_comparable=$$(ver $${airflow_version}) + min_airflow_version=2.2.0 + min_airflow_version_comparable=$$(ver $${min_airflow_version}) + if [[ -z "${AIRFLOW_UID}" ]]; then + echo + echo -e "\033[1;33mWARNING!!!: AIRFLOW_UID not set!\e[0m" + echo "If you are on Linux, you SHOULD follow the instructions below to set " + echo "AIRFLOW_UID environment variable, otherwise files will be owned by root." + echo "For other operating systems you can get rid of the warning with manually created .env file:" + echo " See: https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html#setting-the-right-airflow-user" + echo + fi + one_meg=1048576 + mem_available=$$(($$(getconf _PHYS_PAGES) * $$(getconf PAGE_SIZE) / one_meg)) + cpus_available=$$(grep -cE 'cpu[0-9]+' /proc/stat) + disk_available=$$(df / | tail -1 | awk '{print $$4}') + warning_resources="false" + if (( mem_available < 4000 )) ; then + echo + echo -e "\033[1;33mWARNING!!!: Not enough memory available for Docker.\e[0m" + echo "At least 4GB of memory required. You have $$(numfmt --to iec $$((mem_available * one_meg)))" + echo + warning_resources="true" + fi + if (( cpus_available < 2 )); then + echo + echo -e "\033[1;33mWARNING!!!: Not enough CPUS available for Docker.\e[0m" + echo "At least 2 CPUs recommended. You have $${cpus_available}" + echo + warning_resources="true" + fi + if (( disk_available < one_meg * 10 )); then + echo + echo -e "\033[1;33mWARNING!!!: Not enough Disk space available for Docker.\e[0m" + echo "At least 10 GBs recommended. You have $$(numfmt --to iec $$((disk_available * 1024 )))" + echo + warning_resources="true" + fi + if [[ $${warning_resources} == "true" ]]; then + echo + echo -e "\033[1;33mWARNING!!!: You have not enough resources to run Airflow (see above)!\e[0m" + echo "Please follow the instructions to increase amount of resources available:" + echo " https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html#before-you-begin" + echo + fi + mkdir -p /sources/logs /sources/dags /sources/plugins + chown -R "${AIRFLOW_UID}:0" /sources/{logs,dags,plugins} + exec /entrypoint airflow version + # yamllint enable rule:line-length + environment: + <<: *airflow-common-env + _AIRFLOW_DB_UPGRADE: 'true' + _AIRFLOW_WWW_USER_CREATE: 'true' + _AIRFLOW_WWW_USER_USERNAME: ${_AIRFLOW_WWW_USER_USERNAME:-airflow} + _AIRFLOW_WWW_USER_PASSWORD: ${_AIRFLOW_WWW_USER_PASSWORD:-airflow} + _PIP_ADDITIONAL_REQUIREMENTS: '' + user: "0:0" + volumes: + - .:/sources + + airflow-cli: + <<: *airflow-common + profiles: + - debug + environment: + <<: *airflow-common-env + CONNECTION_CHECK_MAX_COUNT: "0" + # Workaround for entrypoint issue. See: https://github.com/apache/airflow/issues/16252 + command: + - bash + - -c + - airflow + + # You can enable flower by adding "--profile flower" option e.g. docker-compose --profile flower up + # or by explicitly targeted on the command line e.g. docker-compose up flower. + # See: https://docs.docker.com/compose/profiles/ + flower: + <<: *airflow-common + command: celery flower + profiles: + - flower + ports: + - 5555:5555 + healthcheck: + test: ["CMD", "curl", "--fail", "http://localhost:5555/"] + interval: 10s + timeout: 10s + retries: 5 + restart: always + depends_on: + <<: *airflow-common-depends-on + airflow-init: + condition: service_completed_successfully + +volumes: + postgres-db-volume: diff --git a/ee/recommendation/requirements.txt b/ee/recommendation/requirements.txt new file mode 100644 index 000000000..7f0d26c2e --- /dev/null +++ b/ee/recommendation/requirements.txt @@ -0,0 +1,22 @@ +requests==2.28.1 +urllib3==1.26.12 +pyjwt==2.5.0 +psycopg2-binary==2.9.3 + +numpy +threadpoolctl==3.1.0 +joblib==1.2.0 +scipy +scikit-learn +mlflow + +airflow-code-editor + +pydantic[email]==1.10.2 + +clickhouse-driver==0.2.4 +python3-saml==1.14.0 +python-multipart==0.0.5 +python-decouple + +argcomplete diff --git a/ee/recommendation/run.sh b/ee/recommendation/run.sh new file mode 100644 index 000000000..0a703bca4 --- /dev/null +++ b/ee/recommendation/run.sh @@ -0,0 +1,11 @@ +echo 'Setting up required modules..' +mkdir scripts +mkdir plugins +mkdir logs +mkdir scripts/utils +cp ../../api/chalicelib/utils/pg_client.py scripts/utils +cp ../api/chalicelib/utils/ch_client.py scripts/utils +echo 'Building containers...' +docker-compose up airflow-init +echo 'Running containers...' +docker-compose up diff --git a/ee/recommendation/scripts/core/features.py b/ee/recommendation/scripts/core/features.py new file mode 100644 index 000000000..c2e21535e --- /dev/null +++ b/ee/recommendation/scripts/core/features.py @@ -0,0 +1,161 @@ +from utils.ch_client import ClickHouseClient +from utils.pg_client import PostgresClient + +def get_features_clickhouse(**kwargs): + """Gets features from ClickHouse database""" + if 'limit' in kwargs: + limit = kwargs['limit'] + else: + limit = 500 + query = f"""SELECT session_id, project_id, user_id, events_count, errors_count, duration, country, issue_score, device_type, rage, jsexception, badrequest FROM ( + SELECT session_id, project_id, user_id, events_count, errors_count, duration, toInt8(user_country) as country, issue_score, toInt8(user_device_type) as device_type FROM experimental.sessions WHERE user_id IS NOT NULL) as T1 +INNER JOIN (SELECT session_id, project_id, sum(issue_type = 'click_rage') as rage, sum(issue_type = 'js_exception') as jsexception, sum(issue_type = 'bad_request') as badrequest FROM experimental.events WHERE event_type = 'ISSUE' AND session_id > 0 GROUP BY session_id, project_id LIMIT {limit}) as T2 +ON T1.session_id = T2.session_id AND T1.project_id = T2.project_id;""" + with ClickHouseClient() as conn: + res = conn.execute(query) + return res + + +def get_features_postgres(**kwargs): + with PostgresClient() as conn: + funnels = query_funnels(conn, **kwargs) + metrics = query_metrics(conn, **kwargs) + filters = query_with_filters(conn, **kwargs) + #clean_filters(funnels) + #clean_filters(filters) + return clean_filters_split(funnels, isfunnel=True), metrics, clean_filters_split(filters) + + + +def query_funnels(conn, **kwargs): + """Gets Funnels (PG database)""" + # If public.funnel is empty + funnels_query = f"""SELECT project_id, user_id, filter FROM (SELECT project_id, user_id, metric_id FROM public.metrics WHERE metric_type='funnel' + ) as T1 LEFT JOIN (SELECT filter, metric_id FROM public.metric_series) as T2 ON T1.metric_id = T2.metric_id""" + # Else + # funnels_query = "SELECT project_id, user_id, filter FROM public.funnels" + + conn.execute(funnels_query) + res = conn.fetchall() + return res + + +def query_metrics(conn, **kwargs): + """Gets Metrics (PG_database)""" + metrics_query = """SELECT metric_type, metric_of, metric_value, metric_format FROM public.metrics""" + conn.execute(metrics_query) + res = conn.fetchall() + return res + + +def query_with_filters(conn, **kwargs): + """Gets Metrics with filters (PG database)""" + filters_query = """SELECT T1.metric_id as metric_id, project_id, name, metric_type, metric_of, filter FROM ( + SELECT metric_id, project_id, name, metric_type, metric_of FROM metrics) as T1 INNER JOIN + (SELECT metric_id, filter FROM metric_series WHERE filter != '{}') as T2 ON T1.metric_id = T2.metric_id""" + conn.execute(filters_query) + res = conn.fetchall() + return res + + +def transform_funnel(project_id, user_id, data): + res = list() + for k in range(len(data)): + _tmp = data[k] + if _tmp['project_id'] != project_id or _tmp['user_id'] != user_id: + continue + else: + _tmp = _tmp['filter']['events'] + res.append(_tmp) + return res + + +def transform_with_filter(data, *kwargs): + res = list() + for k in range(len(data)): + _tmp = data[k] + jump = False + for _key in kwargs.keys(): + if data[_key] != kwargs[_key]: + jump = True + break + if jump: + continue + _type = data['metric_type'] + if _type == 'funnel': + res.append(['funnel', _tmp['filter']['events']]) + elif _type == 'timeseries': + res.append(['timeseries', _tmp['filter']['filters'], _tmp['filter']['events']]) + elif _type == 'table': + res.append(['table', _tmp['metric_of'], _tmp['filter']['events']]) + return res + + +def transform(element): + key_ = element.pop('user_id') + secondary_key_ = element.pop('session_id') + context_ = element.pop('project_id') + features_ = element + del element + return {(key_, context_): {secondary_key_: list(features_.values())}} + + +def get_by_project(data, project_id): + head_ = [list(d.keys())[0][1] for d in data] + index_ = [k for k in range(len(head_)) if head_[k] == project_id] + return [data[k] for k in index_] + + +def get_by_user(data, user_id): + head_ = [list(d.keys())[0][0] for d in data] + index_ = [k for k in range(len(head_)) if head_[k] == user_id] + return [data[k] for k in index_] + + +def clean_filters(data): + for j in range(len(data)): + _filter = data[j]['filter'] + _tmp = list() + for i in range(len(_filter['filters'])): + if 'value' in _filter['filters'][i].keys(): + _tmp.append({'type': _filter['filters'][i]['type'], + 'value': _filter['filters'][i]['value'], + 'operator': _filter['filters'][i]['operator']}) + data[j]['filter'] = _tmp + + +def clean_filters_split(data, isfunnel=False): + _data = list() + for j in range(len(data)): + _filter = data[j]['filter'] + _tmp = list() + for i in range(len(_filter['filters'])): + if 'value' in _filter['filters'][i].keys(): + _type = _filter['filters'][i]['type'] + _value = _filter['filters'][i]['value'] + if isinstance(_value, str): + _value = [_value] + _operator = _filter['filters'][i]['operator'] + if isfunnel: + _data.append({'project_id': data[j]['project_id'], 'user_id': data[j]['user_id'], + 'type': _type, + 'value': _value, + 'operator': _operator + }) + else: + _data.append({'metric_id': data[j]['metric_id'], 'project_id': data[j]['project_id'], + 'name': data[j]['name'], 'metric_type': data[j]['metric_type'], + 'metric_of': data[j]['metric_of'], + 'type': _type, + 'value': _value, + 'operator': _operator + }) + return _data + +def test(): + print('One test') + +if __name__ == '__main__': + print('Just a test') + #data = get_features_clickhouse() + #print('Data length:', len(data)) diff --git a/ee/recommendation/scripts/core/recommendation_model.py b/ee/recommendation/scripts/core/recommendation_model.py new file mode 100644 index 000000000..9dae948a7 --- /dev/null +++ b/ee/recommendation/scripts/core/recommendation_model.py @@ -0,0 +1,15 @@ +from sklearn.svm import SVC + +class SVM_recommendation(): + def __init__(**params): + f"""{SVC.__doc__}""" + self.svm = SVC(params) + + def fit(self, X1=None, X2=None): + assert X1 is not None or X2 is not None, 'X1 or X2 must be given' + self.svm.fit(X1) + self.svm.fit(X2) + + + def predict(self, X): + return self.svm.predict(X) diff --git a/ee/recommendation/scripts/model_registry.py b/ee/recommendation/scripts/model_registry.py new file mode 100644 index 000000000..80d6dbde6 --- /dev/null +++ b/ee/recommendation/scripts/model_registry.py @@ -0,0 +1,60 @@ +import mlflow +## +import numpy as np +import pickle + +from sklearn import datasets, linear_model +from sklearn.metrics import mean_squared_error, r2_score + +# source: https://scikit-learn.org/stable/auto_examples/linear_model/plot_ols.html + +# Load the diabetes dataset +diabetes_X, diabetes_y = datasets.load_diabetes(return_X_y=True) + +# Use only one feature +diabetes_X = diabetes_X[:, np.newaxis, 2] + +# Split the data into training/testing sets +diabetes_X_train = diabetes_X[:-20] +diabetes_X_test = diabetes_X[-20:] + +# Split the targets into training/testing sets +diabetes_y_train = diabetes_y[:-20] +diabetes_y_test = diabetes_y[-20:] + + +def print_predictions(m, y_pred): + + # The coefficients + print('Coefficients: \n', m.coef_) + # The mean squared error + print('Mean squared error: %.2f' + % mean_squared_error(diabetes_y_test, y_pred)) + # The coefficient of determination: 1 is perfect prediction + print('Coefficient of determination: %.2f' + % r2_score(diabetes_y_test, y_pred)) + +# Create linear regression object +lr_model = linear_model.LinearRegression() + +# Train the model using the training sets +lr_model.fit(diabetes_X_train, diabetes_y_train) + +# Make predictions using the testing set +diabetes_y_pred = lr_model.predict(diabetes_X_test) +print_predictions(lr_model, diabetes_y_pred) + +# save the model in the native sklearn format +filename = 'lr_model.pkl' +pickle.dump(lr_model, open(filename, 'wb')) +## +# load the model into memory +loaded_model = pickle.load(open(filename, 'rb')) + +# log and register the model using MLflow scikit-learn API +mlflow.set_tracking_uri("postgresql+psycopg2://airflow:airflow@postgres/mlruns") +reg_model_name = "SklearnLinearRegression" +print("--") +mlflow.sklearn.log_model(loaded_model, "sk_learn", + serialization_format="cloudpickle", + registered_model_name=reg_model_name) diff --git a/ee/recommendation/scripts/processing.py b/ee/recommendation/scripts/processing.py new file mode 100644 index 000000000..8f3631655 --- /dev/null +++ b/ee/recommendation/scripts/processing.py @@ -0,0 +1,42 @@ +import time +import argparse +from core import features +from utils import pg_client +import multiprocessing as mp +from decouple import config +import asyncio +import pandas + + +def features_ch(q): + q.put(features.get_features_clickhouse()) + +def features_pg(q): + q.put(features.get_features_postgres()) + +def get_features(): + #mp.set_start_method('spawn') + #q = mp.Queue() + #p1 = mp.Process(target=features_ch, args=(q,)) + #p1.start() + pg_features = features.get_features_postgres() + ch_features = []#p1.join() + return [pg_features, ch_features] + + +parser = argparse.ArgumentParser(description='Gets and process data from Postgres and ClickHouse.') +parser.add_argument('--batch_size', type=int, required=True, help='--batch_size max size of columns per file to be saved in opt/airflow/cache') + +args = parser.parse_args() + +if __name__ == '__main__': + asyncio.run(pg_client.init()) + print(args) + t1 = time.time() + data = get_features() + #print(data) + cache_dir = config("data_dir", default=f"/opt/airflow/cache") + for d in data[0]: + pandas.DataFrame(d).to_csv(f'{cache_dir}/tmp-{hash(time.time())}', sep=',') + t2 = time.time() + print(f'DONE! information retrieved in {t2-t1: .2f} seconds') diff --git a/ee/recommendation/scripts/task.py b/ee/recommendation/scripts/task.py new file mode 100644 index 000000000..b427fa1c5 --- /dev/null +++ b/ee/recommendation/scripts/task.py @@ -0,0 +1,41 @@ +import time +import argparse +from decouple import config +from core import recommendation_model + +import pandas +import json +import os + + +def transform_dict_string(s_dicts): + data = list() + for s_dict in s_dicts: + data.append(json.loads(s_dict.replace("'", '"').replace('None','null').replace('False','false'))) + return data + +def process_file(file_name): + return pandas.read_csv(file_name, sep=",") + + +def read_batches(): + base_dir = config('dir_path', default='/opt/airflow/cache') + files = os.listdir(base_dir) + for file in files: + yield process_file(f'{base_dir}/{file}') + + +parser = argparse.ArgumentParser(description='Handle machine learning inputs.') +parser.add_argument('--mode', choices=['train', 'test'], required=True, help='--mode sets the model in train or test mode') +parser.add_argument('--kernel', default='linear', help='--kernel set the kernel to be used for SVM') + +args = parser.parse_args() + +if __name__ == '__main__': + print(args) + t1 = time.time() + buff = read_batches() + for b in buff: + print(b.head()) + t2 = time.time() + print(f'DONE! information retrieved in {t2-t1: .2f} seconds') diff --git a/ee/recommendation/scripts/utils/ch_client.py b/ee/recommendation/scripts/utils/ch_client.py new file mode 100644 index 000000000..514820212 --- /dev/null +++ b/ee/recommendation/scripts/utils/ch_client.py @@ -0,0 +1,54 @@ +import logging + +import clickhouse_driver +from decouple import config + +logging.basicConfig(level=config("LOGLEVEL", default=logging.INFO)) + +settings = {} +if config('ch_timeout', cast=int, default=-1) > 0: + logging.info(f"CH-max_execution_time set to {config('ch_timeout')}s") + settings = {**settings, "max_execution_time": config('ch_timeout', cast=int)} + +if config('ch_receive_timeout', cast=int, default=-1) > 0: + logging.info(f"CH-receive_timeout set to {config('ch_receive_timeout')}s") + settings = {**settings, "receive_timeout": config('ch_receive_timeout', cast=int)} + + +class ClickHouseClient: + __client = None + + def __init__(self): + self.__client = clickhouse_driver.Client(host=config("ch_host"), + database="default", + port=config("ch_port", cast=int), + settings=settings) \ + if self.__client is None else self.__client + + def __enter__(self): + return self + + def execute(self, query, params=None, **args): + try: + results = self.__client.execute(query=query, params=params, with_column_types=True, **args) + keys = tuple(x for x, y in results[1]) + return [dict(zip(keys, i)) for i in results[0]] + except Exception as err: + logging.error("--------- CH QUERY EXCEPTION -----------") + logging.error(self.format(query=query, params=params)) + logging.error("--------------------") + raise err + + def insert(self, query, params=None, **args): + return self.__client.execute(query=query, params=params, **args) + + def client(self): + return self.__client + + def format(self, query, params): + if params is None: + return query + return self.__client.substitute_params(query, params, self.__client.connection.context) + + def __exit__(self, *args): + pass diff --git a/ee/recommendation/scripts/utils/pg_client.py b/ee/recommendation/scripts/utils/pg_client.py new file mode 100644 index 000000000..69a5b5a8b --- /dev/null +++ b/ee/recommendation/scripts/utils/pg_client.py @@ -0,0 +1,166 @@ +import logging +import time +from threading import Semaphore + +import psycopg2 +import psycopg2.extras +from decouple import config +from psycopg2 import pool + +logging.basicConfig(level=config("LOGLEVEL", default=logging.INFO)) +logging.getLogger('apscheduler').setLevel(config("LOGLEVEL", default=logging.INFO)) + +_PG_CONFIG = {"host": config("pg_host"), + "database": config("pg_dbname"), + "user": config("pg_user"), + "password": config("pg_password"), + "port": config("pg_port", cast=int), + "application_name": config("APP_NAME", default="PY")} +PG_CONFIG = dict(_PG_CONFIG) +if config("PG_TIMEOUT", cast=int, default=0) > 0: + PG_CONFIG["options"] = f"-c statement_timeout={config('PG_TIMEOUT', cast=int) * 1000}" + + +class ORThreadedConnectionPool(psycopg2.pool.ThreadedConnectionPool): + def __init__(self, minconn, maxconn, *args, **kwargs): + self._semaphore = Semaphore(maxconn) + super().__init__(minconn, maxconn, *args, **kwargs) + + def getconn(self, *args, **kwargs): + self._semaphore.acquire() + try: + return super().getconn(*args, **kwargs) + except psycopg2.pool.PoolError as e: + if str(e) == "connection pool is closed": + make_pool() + raise e + + def putconn(self, *args, **kwargs): + try: + super().putconn(*args, **kwargs) + self._semaphore.release() + except psycopg2.pool.PoolError as e: + if str(e) == "trying to put unkeyed connection": + print("!!! trying to put unkeyed connection") + print(f"env-PG_POOL:{config('PG_POOL', default=None)}") + return + raise e + + +postgreSQL_pool: ORThreadedConnectionPool = None + +RETRY_MAX = config("PG_RETRY_MAX", cast=int, default=50) +RETRY_INTERVAL = config("PG_RETRY_INTERVAL", cast=int, default=2) +RETRY = 0 + + +def make_pool(): + if not config('PG_POOL', cast=bool, default=True): + return + global postgreSQL_pool + global RETRY + if postgreSQL_pool is not None: + try: + postgreSQL_pool.closeall() + except (Exception, psycopg2.DatabaseError) as error: + logging.error("Error while closing all connexions to PostgreSQL", error) + try: + postgreSQL_pool = ORThreadedConnectionPool(config("PG_MINCONN", cast=int, default=20), + config("PG_MAXCONN", cast=int, default=80), + **PG_CONFIG) + if (postgreSQL_pool): + logging.info("Connection pool created successfully") + except (Exception, psycopg2.DatabaseError) as error: + logging.error("Error while connecting to PostgreSQL", error) + if RETRY < RETRY_MAX: + RETRY += 1 + logging.info(f"waiting for {RETRY_INTERVAL}s before retry n°{RETRY}") + time.sleep(RETRY_INTERVAL) + make_pool() + else: + raise error + + +class PostgresClient: + connection = None + cursor = None + long_query = False + unlimited_query = False + + def __init__(self, long_query=False, unlimited_query=False): + self.long_query = long_query + self.unlimited_query = unlimited_query + if unlimited_query: + long_config = dict(_PG_CONFIG) + long_config["application_name"] += "-UNLIMITED" + self.connection = psycopg2.connect(**long_config) + elif long_query: + long_config = dict(_PG_CONFIG) + long_config["application_name"] += "-LONG" + long_config["options"] = f"-c statement_timeout=" \ + f"{config('pg_long_timeout', cast=int, default=5 * 60) * 1000}" + self.connection = psycopg2.connect(**long_config) + elif not config('PG_POOL', cast=bool, default=True): + single_config = dict(_PG_CONFIG) + single_config["application_name"] += "-NOPOOL" + single_config["options"] = f"-c statement_timeout={config('PG_TIMEOUT', cast=int, default=30) * 1000}" + self.connection = psycopg2.connect(**single_config) + else: + self.connection = postgreSQL_pool.getconn() + + def __enter__(self): + if self.cursor is None: + self.cursor = self.connection.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + self.cursor.recreate = self.recreate_cursor + return self.cursor + + def __exit__(self, *args): + try: + self.connection.commit() + self.cursor.close() + if self.long_query or self.unlimited_query: + self.connection.close() + except Exception as error: + logging.error("Error while committing/closing PG-connection", error) + if str(error) == "connection already closed" \ + and not self.long_query \ + and not self.unlimited_query \ + and config('PG_POOL', cast=bool, default=True): + logging.info("Recreating the connexion pool") + make_pool() + else: + raise error + finally: + if config('PG_POOL', cast=bool, default=True) \ + and not self.long_query \ + and not self.unlimited_query: + postgreSQL_pool.putconn(self.connection) + + def recreate_cursor(self, rollback=False): + if rollback: + try: + self.connection.rollback() + except Exception as error: + logging.error("Error while rollbacking connection for recreation", error) + try: + self.cursor.close() + except Exception as error: + logging.error("Error while closing cursor for recreation", error) + self.cursor = None + return self.__enter__() + + +async def init(): + logging.info(f">PG_POOL:{config('PG_POOL', default=None)}") + if config('PG_POOL', cast=bool, default=True): + make_pool() + + +async def terminate(): + global postgreSQL_pool + if postgreSQL_pool is not None: + try: + postgreSQL_pool.closeall() + logging.info("Closed all connexions to PostgreSQL") + except (Exception, psycopg2.DatabaseError) as error: + logging.error("Error while closing all connexions to PostgreSQL", error) diff --git a/ee/recommendation/signals.sql b/ee/recommendation/signals.sql new file mode 100644 index 000000000..5500969ed --- /dev/null +++ b/ee/recommendation/signals.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS frontend_signals +( + project_id bigint NOT NULL, + user_id text NOT NULL, + timestamp bigint NOT NULL, + action text NOT NULL, + source text NOT NULL, + category text NOT NULL, + data json +); +CREATE INDEX IF NOT EXISTS frontend_signals_user_id_idx ON frontend_signals (user_id); diff --git a/ee/scripts/schema/db/init_dbs/clickhouse/1.10.0/1.10.0.sql b/ee/scripts/schema/db/init_dbs/clickhouse/1.10.0/1.10.0.sql new file mode 100644 index 000000000..cc4816020 --- /dev/null +++ b/ee/scripts/schema/db/init_dbs/clickhouse/1.10.0/1.10.0.sql @@ -0,0 +1,2 @@ +ALTER TABLE experimental.sessions + MODIFY COLUMN user_country Enum8('UN'=-128, 'RW'=-127, 'SO'=-126, 'YE'=-125, 'IQ'=-124, 'SA'=-123, 'IR'=-122, 'CY'=-121, 'TZ'=-120, 'SY'=-119, 'AM'=-118, 'KE'=-117, 'CD'=-116, 'DJ'=-115, 'UG'=-114, 'CF'=-113, 'SC'=-112, 'JO'=-111, 'LB'=-110, 'KW'=-109, 'OM'=-108, 'QA'=-107, 'BH'=-106, 'AE'=-105, 'IL'=-104, 'TR'=-103, 'ET'=-102, 'ER'=-101, 'EG'=-100, 'SD'=-99, 'GR'=-98, 'BI'=-97, 'EE'=-96, 'LV'=-95, 'AZ'=-94, 'LT'=-93, 'SJ'=-92, 'GE'=-91, 'MD'=-90, 'BY'=-89, 'FI'=-88, 'AX'=-87, 'UA'=-86, 'MK'=-85, 'HU'=-84, 'BG'=-83, 'AL'=-82, 'PL'=-81, 'RO'=-80, 'XK'=-79, 'ZW'=-78, 'ZM'=-77, 'KM'=-76, 'MW'=-75, 'LS'=-74, 'BW'=-73, 'MU'=-72, 'SZ'=-71, 'RE'=-70, 'ZA'=-69, 'YT'=-68, 'MZ'=-67, 'MG'=-66, 'AF'=-65, 'PK'=-64, 'BD'=-63, 'TM'=-62, 'TJ'=-61, 'LK'=-60, 'BT'=-59, 'IN'=-58, 'MV'=-57, 'IO'=-56, 'NP'=-55, 'MM'=-54, 'UZ'=-53, 'KZ'=-52, 'KG'=-51, 'TF'=-50, 'HM'=-49, 'CC'=-48, 'PW'=-47, 'VN'=-46, 'TH'=-45, 'ID'=-44, 'LA'=-43, 'TW'=-42, 'PH'=-41, 'MY'=-40, 'CN'=-39, 'HK'=-38, 'BN'=-37, 'MO'=-36, 'KH'=-35, 'KR'=-34, 'JP'=-33, 'KP'=-32, 'SG'=-31, 'CK'=-30, 'TL'=-29, 'RU'=-28, 'MN'=-27, 'AU'=-26, 'CX'=-25, 'MH'=-24, 'FM'=-23, 'PG'=-22, 'SB'=-21, 'TV'=-20, 'NR'=-19, 'VU'=-18, 'NC'=-17, 'NF'=-16, 'NZ'=-15, 'FJ'=-14, 'LY'=-13, 'CM'=-12, 'SN'=-11, 'CG'=-10, 'PT'=-9, 'LR'=-8, 'CI'=-7, 'GH'=-6, 'GQ'=-5, 'NG'=-4, 'BF'=-3, 'TG'=-2, 'GW'=-1, 'MR'=0, 'BJ'=1, 'GA'=2, 'SL'=3, 'ST'=4, 'GI'=5, 'GM'=6, 'GN'=7, 'TD'=8, 'NE'=9, 'ML'=10, 'EH'=11, 'TN'=12, 'ES'=13, 'MA'=14, 'MT'=15, 'DZ'=16, 'FO'=17, 'DK'=18, 'IS'=19, 'GB'=20, 'CH'=21, 'SE'=22, 'NL'=23, 'AT'=24, 'BE'=25, 'DE'=26, 'LU'=27, 'IE'=28, 'MC'=29, 'FR'=30, 'AD'=31, 'LI'=32, 'JE'=33, 'IM'=34, 'GG'=35, 'SK'=36, 'CZ'=37, 'NO'=38, 'VA'=39, 'SM'=40, 'IT'=41, 'SI'=42, 'ME'=43, 'HR'=44, 'BA'=45, 'AO'=46, 'NA'=47, 'SH'=48, 'BV'=49, 'BB'=50, 'CV'=51, 'GY'=52, 'GF'=53, 'SR'=54, 'PM'=55, 'GL'=56, 'PY'=57, 'UY'=58, 'BR'=59, 'FK'=60, 'GS'=61, 'JM'=62, 'DO'=63, 'CU'=64, 'MQ'=65, 'BS'=66, 'BM'=67, 'AI'=68, 'TT'=69, 'KN'=70, 'DM'=71, 'AG'=72, 'LC'=73, 'TC'=74, 'AW'=75, 'VG'=76, 'VC'=77, 'MS'=78, 'MF'=79, 'BL'=80, 'GP'=81, 'GD'=82, 'KY'=83, 'BZ'=84, 'SV'=85, 'GT'=86, 'HN'=87, 'NI'=88, 'CR'=89, 'VE'=90, 'EC'=91, 'CO'=92, 'PA'=93, 'HT'=94, 'AR'=95, 'CL'=96, 'BO'=97, 'PE'=98, 'MX'=99, 'PF'=100, 'PN'=101, 'KI'=102, 'TK'=103, 'TO'=104, 'WF'=105, 'WS'=106, 'NU'=107, 'MP'=108, 'GU'=109, 'PR'=110, 'VI'=111, 'UM'=112, 'AS'=113, 'CA'=114, 'US'=115, 'PS'=116, 'RS'=117, 'AQ'=118, 'SX'=119, 'CW'=120, 'BQ'=121, 'SS'=122,'BU'=123, 'VD'=124, 'YD'=125, 'DD'=126); diff --git a/ee/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql b/ee/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql index b172c0080..9b2cfbbd1 100644 --- a/ee/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql +++ b/ee/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql @@ -129,7 +129,7 @@ CREATE TABLE IF NOT EXISTS experimental.sessions user_browser_version LowCardinality(Nullable(String)), user_device Nullable(String), user_device_type Enum8('other'=0, 'desktop'=1, 'mobile'=2), - user_country Enum8('UN'=-128, 'RW'=-127, 'SO'=-126, 'YE'=-125, 'IQ'=-124, 'SA'=-123, 'IR'=-122, 'CY'=-121, 'TZ'=-120, 'SY'=-119, 'AM'=-118, 'KE'=-117, 'CD'=-116, 'DJ'=-115, 'UG'=-114, 'CF'=-113, 'SC'=-112, 'JO'=-111, 'LB'=-110, 'KW'=-109, 'OM'=-108, 'QA'=-107, 'BH'=-106, 'AE'=-105, 'IL'=-104, 'TR'=-103, 'ET'=-102, 'ER'=-101, 'EG'=-100, 'SD'=-99, 'GR'=-98, 'BI'=-97, 'EE'=-96, 'LV'=-95, 'AZ'=-94, 'LT'=-93, 'SJ'=-92, 'GE'=-91, 'MD'=-90, 'BY'=-89, 'FI'=-88, 'AX'=-87, 'UA'=-86, 'MK'=-85, 'HU'=-84, 'BG'=-83, 'AL'=-82, 'PL'=-81, 'RO'=-80, 'XK'=-79, 'ZW'=-78, 'ZM'=-77, 'KM'=-76, 'MW'=-75, 'LS'=-74, 'BW'=-73, 'MU'=-72, 'SZ'=-71, 'RE'=-70, 'ZA'=-69, 'YT'=-68, 'MZ'=-67, 'MG'=-66, 'AF'=-65, 'PK'=-64, 'BD'=-63, 'TM'=-62, 'TJ'=-61, 'LK'=-60, 'BT'=-59, 'IN'=-58, 'MV'=-57, 'IO'=-56, 'NP'=-55, 'MM'=-54, 'UZ'=-53, 'KZ'=-52, 'KG'=-51, 'TF'=-50, 'HM'=-49, 'CC'=-48, 'PW'=-47, 'VN'=-46, 'TH'=-45, 'ID'=-44, 'LA'=-43, 'TW'=-42, 'PH'=-41, 'MY'=-40, 'CN'=-39, 'HK'=-38, 'BN'=-37, 'MO'=-36, 'KH'=-35, 'KR'=-34, 'JP'=-33, 'KP'=-32, 'SG'=-31, 'CK'=-30, 'TL'=-29, 'RU'=-28, 'MN'=-27, 'AU'=-26, 'CX'=-25, 'MH'=-24, 'FM'=-23, 'PG'=-22, 'SB'=-21, 'TV'=-20, 'NR'=-19, 'VU'=-18, 'NC'=-17, 'NF'=-16, 'NZ'=-15, 'FJ'=-14, 'LY'=-13, 'CM'=-12, 'SN'=-11, 'CG'=-10, 'PT'=-9, 'LR'=-8, 'CI'=-7, 'GH'=-6, 'GQ'=-5, 'NG'=-4, 'BF'=-3, 'TG'=-2, 'GW'=-1, 'MR'=0, 'BJ'=1, 'GA'=2, 'SL'=3, 'ST'=4, 'GI'=5, 'GM'=6, 'GN'=7, 'TD'=8, 'NE'=9, 'ML'=10, 'EH'=11, 'TN'=12, 'ES'=13, 'MA'=14, 'MT'=15, 'DZ'=16, 'FO'=17, 'DK'=18, 'IS'=19, 'GB'=20, 'CH'=21, 'SE'=22, 'NL'=23, 'AT'=24, 'BE'=25, 'DE'=26, 'LU'=27, 'IE'=28, 'MC'=29, 'FR'=30, 'AD'=31, 'LI'=32, 'JE'=33, 'IM'=34, 'GG'=35, 'SK'=36, 'CZ'=37, 'NO'=38, 'VA'=39, 'SM'=40, 'IT'=41, 'SI'=42, 'ME'=43, 'HR'=44, 'BA'=45, 'AO'=46, 'NA'=47, 'SH'=48, 'BV'=49, 'BB'=50, 'CV'=51, 'GY'=52, 'GF'=53, 'SR'=54, 'PM'=55, 'GL'=56, 'PY'=57, 'UY'=58, 'BR'=59, 'FK'=60, 'GS'=61, 'JM'=62, 'DO'=63, 'CU'=64, 'MQ'=65, 'BS'=66, 'BM'=67, 'AI'=68, 'TT'=69, 'KN'=70, 'DM'=71, 'AG'=72, 'LC'=73, 'TC'=74, 'AW'=75, 'VG'=76, 'VC'=77, 'MS'=78, 'MF'=79, 'BL'=80, 'GP'=81, 'GD'=82, 'KY'=83, 'BZ'=84, 'SV'=85, 'GT'=86, 'HN'=87, 'NI'=88, 'CR'=89, 'VE'=90, 'EC'=91, 'CO'=92, 'PA'=93, 'HT'=94, 'AR'=95, 'CL'=96, 'BO'=97, 'PE'=98, 'MX'=99, 'PF'=100, 'PN'=101, 'KI'=102, 'TK'=103, 'TO'=104, 'WF'=105, 'WS'=106, 'NU'=107, 'MP'=108, 'GU'=109, 'PR'=110, 'VI'=111, 'UM'=112, 'AS'=113, 'CA'=114, 'US'=115, 'PS'=116, 'RS'=117, 'AQ'=118, 'SX'=119, 'CW'=120, 'BQ'=121, 'SS'=122), + user_country Enum8('UN'=-128, 'RW'=-127, 'SO'=-126, 'YE'=-125, 'IQ'=-124, 'SA'=-123, 'IR'=-122, 'CY'=-121, 'TZ'=-120, 'SY'=-119, 'AM'=-118, 'KE'=-117, 'CD'=-116, 'DJ'=-115, 'UG'=-114, 'CF'=-113, 'SC'=-112, 'JO'=-111, 'LB'=-110, 'KW'=-109, 'OM'=-108, 'QA'=-107, 'BH'=-106, 'AE'=-105, 'IL'=-104, 'TR'=-103, 'ET'=-102, 'ER'=-101, 'EG'=-100, 'SD'=-99, 'GR'=-98, 'BI'=-97, 'EE'=-96, 'LV'=-95, 'AZ'=-94, 'LT'=-93, 'SJ'=-92, 'GE'=-91, 'MD'=-90, 'BY'=-89, 'FI'=-88, 'AX'=-87, 'UA'=-86, 'MK'=-85, 'HU'=-84, 'BG'=-83, 'AL'=-82, 'PL'=-81, 'RO'=-80, 'XK'=-79, 'ZW'=-78, 'ZM'=-77, 'KM'=-76, 'MW'=-75, 'LS'=-74, 'BW'=-73, 'MU'=-72, 'SZ'=-71, 'RE'=-70, 'ZA'=-69, 'YT'=-68, 'MZ'=-67, 'MG'=-66, 'AF'=-65, 'PK'=-64, 'BD'=-63, 'TM'=-62, 'TJ'=-61, 'LK'=-60, 'BT'=-59, 'IN'=-58, 'MV'=-57, 'IO'=-56, 'NP'=-55, 'MM'=-54, 'UZ'=-53, 'KZ'=-52, 'KG'=-51, 'TF'=-50, 'HM'=-49, 'CC'=-48, 'PW'=-47, 'VN'=-46, 'TH'=-45, 'ID'=-44, 'LA'=-43, 'TW'=-42, 'PH'=-41, 'MY'=-40, 'CN'=-39, 'HK'=-38, 'BN'=-37, 'MO'=-36, 'KH'=-35, 'KR'=-34, 'JP'=-33, 'KP'=-32, 'SG'=-31, 'CK'=-30, 'TL'=-29, 'RU'=-28, 'MN'=-27, 'AU'=-26, 'CX'=-25, 'MH'=-24, 'FM'=-23, 'PG'=-22, 'SB'=-21, 'TV'=-20, 'NR'=-19, 'VU'=-18, 'NC'=-17, 'NF'=-16, 'NZ'=-15, 'FJ'=-14, 'LY'=-13, 'CM'=-12, 'SN'=-11, 'CG'=-10, 'PT'=-9, 'LR'=-8, 'CI'=-7, 'GH'=-6, 'GQ'=-5, 'NG'=-4, 'BF'=-3, 'TG'=-2, 'GW'=-1, 'MR'=0, 'BJ'=1, 'GA'=2, 'SL'=3, 'ST'=4, 'GI'=5, 'GM'=6, 'GN'=7, 'TD'=8, 'NE'=9, 'ML'=10, 'EH'=11, 'TN'=12, 'ES'=13, 'MA'=14, 'MT'=15, 'DZ'=16, 'FO'=17, 'DK'=18, 'IS'=19, 'GB'=20, 'CH'=21, 'SE'=22, 'NL'=23, 'AT'=24, 'BE'=25, 'DE'=26, 'LU'=27, 'IE'=28, 'MC'=29, 'FR'=30, 'AD'=31, 'LI'=32, 'JE'=33, 'IM'=34, 'GG'=35, 'SK'=36, 'CZ'=37, 'NO'=38, 'VA'=39, 'SM'=40, 'IT'=41, 'SI'=42, 'ME'=43, 'HR'=44, 'BA'=45, 'AO'=46, 'NA'=47, 'SH'=48, 'BV'=49, 'BB'=50, 'CV'=51, 'GY'=52, 'GF'=53, 'SR'=54, 'PM'=55, 'GL'=56, 'PY'=57, 'UY'=58, 'BR'=59, 'FK'=60, 'GS'=61, 'JM'=62, 'DO'=63, 'CU'=64, 'MQ'=65, 'BS'=66, 'BM'=67, 'AI'=68, 'TT'=69, 'KN'=70, 'DM'=71, 'AG'=72, 'LC'=73, 'TC'=74, 'AW'=75, 'VG'=76, 'VC'=77, 'MS'=78, 'MF'=79, 'BL'=80, 'GP'=81, 'GD'=82, 'KY'=83, 'BZ'=84, 'SV'=85, 'GT'=86, 'HN'=87, 'NI'=88, 'CR'=89, 'VE'=90, 'EC'=91, 'CO'=92, 'PA'=93, 'HT'=94, 'AR'=95, 'CL'=96, 'BO'=97, 'PE'=98, 'MX'=99, 'PF'=100, 'PN'=101, 'KI'=102, 'TK'=103, 'TO'=104, 'WF'=105, 'WS'=106, 'NU'=107, 'MP'=108, 'GU'=109, 'PR'=110, 'VI'=111, 'UM'=112, 'AS'=113, 'CA'=114, 'US'=115, 'PS'=116, 'RS'=117, 'AQ'=118, 'SX'=119, 'CW'=120, 'BQ'=121, 'SS'=122,'BU'=123, 'VD'=124, 'YD'=125, 'DD'=126), platform Enum8('web'=1,'ios'=2,'android'=3) DEFAULT 'web', datetime DateTime, duration UInt32, @@ -212,7 +212,7 @@ CREATE TABLE IF NOT EXISTS experimental.issues CREATE MATERIALIZED VIEW IF NOT EXISTS experimental.events_l7d_mv ENGINE = ReplacingMergeTree(_timestamp) - PARTITION BY toYYYYMM(datetime) + PARTITION BY toYYYYMMDD(datetime) ORDER BY (project_id, datetime, event_type, session_id, message_id) TTL datetime + INTERVAL 7 DAY POPULATE @@ -285,7 +285,7 @@ WHERE datetime >= now() - INTERVAL 7 DAY; CREATE MATERIALIZED VIEW IF NOT EXISTS experimental.resources_l7d_mv ENGINE = ReplacingMergeTree(_timestamp) - PARTITION BY toYYYYMM(datetime) + PARTITION BY toYYYYMMDD(datetime) ORDER BY (project_id, datetime, type, session_id, message_id) TTL datetime + INTERVAL 7 DAY POPULATE @@ -362,34 +362,34 @@ WHERE datetime >= now() - INTERVAL 7 DAY AND isNotNull(duration) AND duration > 0; -CREATE MATERIALIZED VIEW IF NOT EXISTS experimental.js_errors_sessions_mv - ENGINE = ReplacingMergeTree(_timestamp) - PARTITION BY toYYYYMM(datetime) - ORDER BY (project_id, datetime, event_type, error_id, session_id) - TTL _timestamp + INTERVAL 35 DAY - POPULATE -AS -SELECT session_id, - project_id, - events.datetime AS datetime, - event_type, - assumeNotNull(error_id) AS error_id, - source, - name, - message, - error_tags_keys, - error_tags_values, - message_id, - user_id, - user_browser, - user_browser_version, - user_os, - user_os_version, - user_device_type, - user_device, - user_country, - _timestamp -FROM experimental.events - INNER JOIN experimental.sessions USING (session_id) -WHERE event_type = 'ERROR' - AND source = 'js_exception'; \ No newline at end of file +-- CREATE MATERIALIZED VIEW IF NOT EXISTS experimental.js_errors_sessions_mv +-- ENGINE = ReplacingMergeTree(_timestamp) +-- PARTITION BY toYYYYMM(datetime) +-- ORDER BY (project_id, datetime, event_type, error_id, session_id) +-- TTL _timestamp + INTERVAL 35 DAY +-- POPULATE +-- AS +-- SELECT session_id, +-- project_id, +-- events.datetime AS datetime, +-- event_type, +-- assumeNotNull(error_id) AS error_id, +-- source, +-- name, +-- message, +-- error_tags_keys, +-- error_tags_values, +-- message_id, +-- user_id, +-- user_browser, +-- user_browser_version, +-- user_os, +-- user_os_version, +-- user_device_type, +-- user_device, +-- user_country, +-- _timestamp +-- FROM experimental.events +-- INNER JOIN experimental.sessions USING (session_id) +-- WHERE event_type = 'ERROR' +-- AND source = 'js_exception'; diff --git a/ee/scripts/schema/db/init_dbs/postgresql/1.10.0/1.10.0.sql b/ee/scripts/schema/db/init_dbs/postgresql/1.10.0/1.10.0.sql new file mode 100644 index 000000000..044b24176 --- /dev/null +++ b/ee/scripts/schema/db/init_dbs/postgresql/1.10.0/1.10.0.sql @@ -0,0 +1,655 @@ +BEGIN; +CREATE OR REPLACE FUNCTION openreplay_version() + RETURNS text AS +$$ +SELECT 'v1.10.0-ee' +$$ LANGUAGE sql IMMUTABLE; + +-- Backup dashboard & search data: +DO +$$ + BEGIN + IF NOT (SELECT EXISTS(SELECT schema_name + FROM information_schema.schemata + WHERE schema_name = 'backup_v1_10_0')) THEN + CREATE SCHEMA backup_v1_10_0; + CREATE TABLE backup_v1_10_0.dashboards + ( + dashboard_id integer, + project_id integer, + user_id integer, + name text NOT NULL, + description text NOT NULL DEFAULT '', + is_public boolean NOT NULL DEFAULT TRUE, + is_pinned boolean NOT NULL DEFAULT FALSE, + created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), + deleted_at timestamp NULL DEFAULT NULL + ); + CREATE TABLE backup_v1_10_0.dashboard_widgets + ( + widget_id integer, + dashboard_id integer, + metric_id integer, + user_id integer, + created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), + config jsonb NOT NULL DEFAULT '{}'::jsonb + ); + CREATE TABLE backup_v1_10_0.searches + ( + search_id integer, + project_id integer, + user_id integer, + name text not null, + filter jsonb not null, + created_at timestamp default timezone('utc'::text, now()) not null, + deleted_at timestamp, + is_public boolean NOT NULL DEFAULT False + ); + CREATE TABLE backup_v1_10_0.metrics + ( + metric_id integer, + project_id integer, + user_id integer, + name text NOT NULL, + is_public boolean NOT NULL DEFAULT FALSE, + active boolean NOT NULL DEFAULT TRUE, + created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), + deleted_at timestamp, + edited_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), + metric_type text NOT NULL, + view_type text NOT NULL, + metric_of text NOT NULL DEFAULT 'sessionCount', + metric_value text[] NOT NULL DEFAULT '{}'::text[], + metric_format text, + category text NULL DEFAULT 'custom', + is_pinned boolean NOT NULL DEFAULT FALSE, + is_predefined boolean NOT NULL DEFAULT FALSE, + is_template boolean NOT NULL DEFAULT FALSE, + predefined_key text NULL DEFAULT NULL, + default_config jsonb NOT NULL + ); + CREATE TABLE backup_v1_10_0.metric_series + ( + series_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY, + metric_id integer REFERENCES metrics (metric_id) ON DELETE CASCADE, + index integer NOT NULL, + name text NULL, + filter jsonb NOT NULL, + created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL, + deleted_at timestamp + ); + + INSERT INTO backup_v1_10_0.dashboards(dashboard_id, project_id, user_id, name, description, is_public, + is_pinned, + created_at, deleted_at) + SELECT dashboard_id, + project_id, + user_id, + name, + description, + is_public, + is_pinned, + created_at, + deleted_at + FROM public.dashboards + ORDER BY dashboard_id; + + INSERT INTO backup_v1_10_0.metrics(metric_id, project_id, user_id, name, is_public, active, created_at, + deleted_at, edited_at, metric_type, view_type, metric_of, metric_value, + metric_format, category, is_pinned, is_predefined, is_template, + predefined_key, default_config) + SELECT metric_id, + project_id, + user_id, + name, + is_public, + active, + created_at, + deleted_at, + edited_at, + metric_type, + view_type, + metric_of, + metric_value, + metric_format, + category, + is_pinned, + is_predefined, + is_template, + predefined_key, + default_config + FROM public.metrics + ORDER BY metric_id; + + INSERT INTO backup_v1_10_0.metric_series(series_id, metric_id, index, name, filter, created_at, deleted_at) + SELECT series_id, metric_id, index, name, filter, created_at, deleted_at + FROM public.metric_series + ORDER BY series_id; + + INSERT INTO backup_v1_10_0.dashboard_widgets(widget_id, dashboard_id, metric_id, user_id, created_at, config) + SELECT widget_id, dashboard_id, metric_id, user_id, created_at, config + FROM public.dashboard_widgets + ORDER BY widget_id; + + INSERT INTO backup_v1_10_0.searches(search_id, project_id, user_id, name, filter, created_at, deleted_at, + is_public) + SELECT search_id, + project_id, + user_id, + name, + filter, + created_at, + deleted_at, + is_public + FROM public.searches + ORDER BY search_id; + END IF; + END +$$ LANGUAGE plpgsql; + +CREATE TABLE IF NOT EXISTS frontend_signals +( + project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE, + user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, + timestamp bigint NOT NULL, + action text NOT NULL, + source text NOT NULL, + category text NOT NULL, + data jsonb, + created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL +); +CREATE INDEX IF NOT EXISTS frontend_signals_user_id_idx ON frontend_signals (user_id); + +CREATE TABLE IF NOT EXISTS assist_records +( + record_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY, + project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE, + user_id integer NOT NULL REFERENCES users (user_id) ON DELETE SET NULL, + session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE SET NULL, + created_at bigint NOT NULL DEFAULT (EXTRACT(EPOCH FROM now() at time zone 'utc') * 1000)::bigint, + deleted_at timestamp without time zone NULL DEFAULT NULL, + name text NOT NULL, + file_key text NOT NULL, + duration integer NOT NULL +); + +ALTER TYPE webhook_type ADD VALUE IF NOT EXISTS 'msteams'; + +UPDATE metrics +SET is_public= TRUE; + +CREATE OR REPLACE FUNCTION get_global_key(key text) + RETURNS text AS +$$ +DECLARE + events_map CONSTANT JSONB := '{ + "SESSIONS": "sessions", + "sessionCount": "sessionCount", + "CLICK": "click", + "INPUT": "input", + "LOCATION": "location", + "CUSTOM": "custom", + "REQUEST": "request", + "FETCH": "fetch", + "GRAPHQL": "graphql", + "STATEACTION": "stateAction", + "ERROR": "error", + "CLICK_IOS": "clickIos", + "INPUT_IOS": "inputIos", + "VIEW_IOS": "viewIos", + "CUSTOM_IOS": "customIos", + "REQUEST_IOS": "requestIos", + "ERROR_IOS": "errorIos", + "DOM_COMPLETE": "domComplete", + "LARGEST_CONTENTFUL_PAINT_TIME": "largestContentfulPaintTime", + "TIME_BETWEEN_EVENTS": "timeBetweenEvents", + "TTFB": "ttfb", + "AVG_CPU_LOAD": "avgCpuLoad", + "AVG_MEMORY_USAGE": "avgMemoryUsage", + "FETCH_FAILED": "fetchFailed", + "FETCH_URL": "fetchUrl", + "FETCH_STATUS_CODE": "fetchStatusCode", + "FETCH_METHOD": "fetchMethod", + "FETCH_DURATION": "fetchDuration", + "FETCH_REQUEST_BODY": "fetchRequestBody", + "FETCH_RESPONSE_BODY": "fetchResponseBody", + "GRAPHQL_NAME": "graphqlName", + "GRAPHQL_METHOD": "graphqlMethod", + "GRAPHQL_REQUEST_BODY": "graphqlRequestBody", + "GRAPHQL_RESPONSE_BODY": "graphqlResponseBody", + "USEROS": "userOs", + "USERBROWSER": "userBrowser", + "USERDEVICE": "userDevice", + "USERCOUNTRY": "userCountry", + "USERID": "userId", + "USERANONYMOUSID": "userAnonymousId", + "REFERRER": "referrer", + "REVID": "revId", + "USEROS_IOS": "userOsIos", + "USERDEVICE_IOS": "userDeviceIos", + "USERCOUNTRY_IOS": "userCountryIos", + "USERID_IOS": "userIdIos", + "USERANONYMOUSID_IOS": "userAnonymousIdIos", + "REVID_IOS": "revIdIos", + "DURATION": "duration", + "PLATFORM": "platform", + "METADATA": "metadata", + "ISSUE": "issue", + "EVENTS_COUNT": "eventsCount", + "UTM_SOURCE": "utmSource", + "UTM_MEDIUM": "utmMedium", + "UTM_CAMPAIGN": "utmCampaign" + }'; +BEGIN + RETURN jsonb_extract_path(events_map, key); +END; +$$ LANGUAGE plpgsql IMMUTABLE; + +ALTER TABLE IF EXISTS metrics + ALTER COLUMN metric_type TYPE text, + ALTER COLUMN metric_type SET DEFAULT 'timeseries', + ALTER COLUMN view_type TYPE text, + ALTER COLUMN view_type SET DEFAULT 'lineChart', + ADD COLUMN IF NOT EXISTS thumbnail text; + +DO +$$ + BEGIN + IF EXISTS(SELECT column_name + FROM information_schema.columns + WHERE table_name = 'metrics' + AND column_name = 'is_predefined' + AND table_schema = 'public') THEN + -- 0. change metric_of + UPDATE metrics + SET metric_of=coalesce(replace(get_global_key(metric_of), '"', ''), + left(metric_of, 1) || right(replace(initcap(metric_of), '_', ''), -1)) + WHERE not is_predefined; + + -- 1. pre transform structure + ALTER TABLE IF EXISTS metrics + ADD COLUMN IF NOT EXISTS o_metric_id INTEGER, + ADD COLUMN IF NOT EXISTS o_widget_id INTEGER; + + -- 2. insert predefined metrics related to dashboards as custom metrics + INSERT INTO metrics(project_id, user_id, name, metric_type, view_type, metric_of, metric_value, + metric_format, default_config, is_public, o_metric_id, o_widget_id) + SELECT dashboards.project_id, + dashboard_widgets.user_id, + metrics.name, + left(category, 1) || right(replace(initcap(category), ' ', ''), -1) AS metric_type, + 'chart' AS view_type, + left(predefined_key, 1) || right(replace(initcap(predefined_key), '_', ''), -1) AS metric_of, + metric_value, + metric_format, + default_config, + TRUE AS is_public, + metrics.metric_id, + dashboard_widgets.widget_id + FROM metrics + INNER JOIN dashboard_widgets USING (metric_id) + INNER JOIN dashboards USING (dashboard_id) + WHERE is_predefined; + + -- 3. update widgets + UPDATE dashboard_widgets + SET metric_id=metrics.metric_id + FROM metrics + WHERE metrics.o_widget_id IS NOT NULL + AND dashboard_widgets.widget_id = metrics.o_widget_id; + + -- 4. delete predefined metrics + DELETE + FROM metrics + WHERE is_predefined; + + ALTER TABLE IF EXISTS metrics + DROP COLUMN IF EXISTS active, + DROP COLUMN IF EXISTS is_predefined, + DROP COLUMN IF EXISTS predefined_key, + DROP COLUMN IF EXISTS is_template, + DROP COLUMN IF EXISTS category, + DROP COLUMN IF EXISTS o_metric_id, + DROP COLUMN IF EXISTS o_widget_id, + DROP CONSTRAINT IF EXISTS null_project_id_for_template_only, + DROP CONSTRAINT IF EXISTS metrics_unique_key, + DROP CONSTRAINT IF EXISTS unique_key; + + END IF; + END; +$$ +LANGUAGE plpgsql; + +DROP TYPE IF EXISTS metric_type; +DROP TYPE IF EXISTS metric_view_type; + +ALTER TABLE IF EXISTS events.clicks + ADD COLUMN IF NOT EXISTS path text; + +DROP INDEX IF EXISTS events.clicks_url_gin_idx; +DROP INDEX IF EXISTS events.inputs_label_value_idx; +DROP INDEX IF EXISTS events.inputs_label_idx; +DROP INDEX IF EXISTS events.pages_base_path_idx; +DROP INDEX IF EXISTS events.pages_base_path_idx1; +DROP INDEX IF EXISTS events.pages_base_path_idx2; +DROP INDEX IF EXISTS events.pages_base_referrer_gin_idx1; +DROP INDEX IF EXISTS events.pages_base_referrer_gin_idx2; +DROP INDEX IF EXISTS events.resources_url_gin_idx; +DROP INDEX IF EXISTS events.resources_url_idx; +DROP INDEX IF EXISTS events.resources_url_hostpath_idx; +DROP INDEX IF EXISTS events.resources_session_id_timestamp_idx; +DROP INDEX IF EXISTS events.resources_duration_durationgt0_idx; +DROP INDEX IF EXISTS events.state_actions_name_idx; +DROP INDEX IF EXISTS events_common.requests_query_nn_idx; +DROP INDEX IF EXISTS events_common.requests_host_nn_idx; +DROP INDEX IF EXISTS events_common.issues_context_string_gin_idx; +DROP INDEX IF EXISTS public.sessions_user_country_gin_idx; +DROP INDEX IF EXISTS public.sessions_user_browser_gin_idx; +DROP INDEX IF EXISTS public.sessions_user_os_gin_idx; +DROP INDEX IF EXISTS public.issues_context_string_gin_idx; + + +ALTER TABLE IF EXISTS projects + ADD COLUMN IF NOT EXISTS beacon_size integer NOT NULL DEFAULT 0; + +-- To migrate saved search data + +SET client_min_messages TO NOTICE; +CREATE OR REPLACE FUNCTION get_new_event_key(key text) + RETURNS text AS +$$ +DECLARE + events_map CONSTANT JSONB := '{ + "CLICK": "click", + "INPUT": "input", + "LOCATION": "location", + "CUSTOM": "custom", + "REQUEST": "request", + "FETCH": "fetch", + "GRAPHQL": "graphql", + "STATEACTION": "stateAction", + "ERROR": "error", + "CLICK_IOS": "clickIos", + "INPUT_IOS": "inputIos", + "VIEW_IOS": "viewIos", + "CUSTOM_IOS": "customIos", + "REQUEST_IOS": "requestIos", + "ERROR_IOS": "errorIos", + "DOM_COMPLETE": "domComplete", + "LARGEST_CONTENTFUL_PAINT_TIME": "largestContentfulPaintTime", + "TIME_BETWEEN_EVENTS": "timeBetweenEvents", + "TTFB": "ttfb", + "AVG_CPU_LOAD": "avgCpuLoad", + "AVG_MEMORY_USAGE": "avgMemoryUsage", + "FETCH_FAILED": "fetchFailed" + }'; +BEGIN + RETURN jsonb_extract_path(events_map, key); +END; +$$ LANGUAGE plpgsql IMMUTABLE; + + +CREATE OR REPLACE FUNCTION get_new_event_filter_key(key text) + RETURNS text AS +$$ +DECLARE + event_filters_map CONSTANT JSONB := '{ + "FETCH_URL": "fetchUrl", + "FETCH_STATUS_CODE": "fetchStatusCode", + "FETCH_METHOD": "fetchMethod", + "FETCH_DURATION": "fetchDuration", + "FETCH_REQUEST_BODY": "fetchRequestBody", + "FETCH_RESPONSE_BODY": "fetchResponseBody", + "GRAPHQL_NAME": "graphqlName", + "GRAPHQL_METHOD": "graphqlMethod", + "GRAPHQL_REQUEST_BODY": "graphqlRequestBody", + "GRAPHQL_RESPONSE_BODY": "graphqlResponseBody" + }'; +BEGIN + RETURN jsonb_extract_path(event_filters_map, key); +END; +$$ LANGUAGE plpgsql IMMUTABLE; + +CREATE OR REPLACE FUNCTION get_new_filter_key(key text) + RETURNS text AS +$$ +DECLARE + filters_map CONSTANT JSONB := '{ + "USEROS": "userOs", + "USERBROWSER": "userBrowser", + "USERDEVICE": "userDevice", + "USERCOUNTRY": "userCountry", + "USERID": "userId", + "USERANONYMOUSID": "userAnonymousId", + "REFERRER": "referrer", + "REVID": "revId", + "USEROS_IOS": "userOsIos", + "USERDEVICE_IOS": "userDeviceIos", + "USERCOUNTRY_IOS": "userCountryIos", + "USERID_IOS": "userIdIos", + "USERANONYMOUSID_IOS": "userAnonymousIdIos", + "REVID_IOS": "revIdIos", + "DURATION": "duration", + "PLATFORM": "platform", + "METADATA": "metadata", + "ISSUE": "issue", + "EVENTS_COUNT": "eventsCount", + "UTM_SOURCE": "utmSource", + "UTM_MEDIUM": "utmMedium", + "UTM_CAMPAIGN": "utmCampaign" + }'; +BEGIN + RETURN jsonb_extract_path(filters_map, key); +END; +$$ LANGUAGE plpgsql IMMUTABLE; + +DO +$$ + DECLARE + row RECORD; + events_att JSONB; + event_filters_att JSONB; + filters_att JSONB; + element JSONB; + s_element JSONB; + new_value TEXT; + new_events JSONB[]; + new_filters JSONB[]; + new_event_filters JSONB[]; + changed BOOLEAN; + planned_update JSONB[]; + BEGIN + planned_update := '{}'::jsonb[]; + FOR row IN SELECT * FROM searches + LOOP + -- Transform events attributes + events_att := row.filter -> 'events'; + IF events_att IS NOT NULL THEN + new_events := '{}'::jsonb[]; + FOR element IN SELECT jsonb_array_elements(events_att) + LOOP + changed := FALSE; + new_value := get_new_event_key(element ->> 'type'); + if new_value IS NOT NULL THEN + changed := TRUE; + new_value := replace(new_value, '"', ''); + element := element || jsonb_build_object('type', new_value); + END IF; + -- Transform event's sub-filters attributes + event_filters_att := element -> 'filters'; + new_event_filters := '{}'::jsonb[]; + IF event_filters_att IS NOT NULL AND jsonb_array_length(event_filters_att) > 0 THEN + FOR s_element IN SELECT jsonb_array_elements(event_filters_att) + LOOP + new_value := get_new_event_filter_key(s_element ->> 'type'); + if new_value IS NOT NULL THEN + changed := TRUE; + new_value := replace(new_value, '"', ''); + s_element := s_element || jsonb_build_object('type', new_value); + new_event_filters := array_append(new_event_filters, s_element); + END IF; + END LOOP; + element := element || jsonb_build_object('filters', new_event_filters); + END IF; + IF changed THEN + new_events := array_append(new_events, element); + END IF; + END LOOP; + IF array_length(new_events, 1) > 0 THEN + row.filter := row.filter || jsonb_build_object('events', new_events); + END IF; + END IF; + + -- Transform filters attributes + filters_att := row.filter -> 'filters'; + IF filters_att IS NOT NULL THEN + new_filters := '{}'::jsonb; + FOR element IN SELECT jsonb_array_elements(filters_att) + LOOP + new_value := get_new_filter_key(element ->> 'type'); + if new_value IS NOT NULL THEN + new_value := replace(new_value, '"', ''); + element := element || jsonb_build_object('type', new_value); + new_filters := array_append(new_filters, element); + END IF; + END LOOP; + IF array_length(new_filters, 1) > 0 THEN + row.filter := row.filter || jsonb_build_object('filters', new_filters); + END IF; + END IF; + + IF array_length(new_events, 1) > 0 OR array_length(new_filters, 1) > 0 THEN + planned_update := array_append(planned_update, + jsonb_build_object('id', row.search_id, 'change', row.filter)); + END IF; + END LOOP; + + -- Update saved search + IF array_length(planned_update, 1) > 0 THEN + raise notice 'must update % elements',array_length(planned_update, 1); + + UPDATE searches + SET filter=changes.change -> 'change' + FROM (SELECT unnest(planned_update)) AS changes(change) + WHERE search_id = (changes.change -> 'id')::integer; + raise notice 'update done'; + ELSE + raise notice 'nothing to update'; + END IF; + END ; +$$ +LANGUAGE plpgsql; + + +-- To migrate metric_series data +DO +$$ + DECLARE + row RECORD; + events_att JSONB; + event_filters_att JSONB; + filters_att JSONB; + element JSONB; + s_element JSONB; + new_value TEXT; + new_events JSONB[]; + new_filters JSONB[]; + new_event_filters JSONB[]; + changed BOOLEAN; + planned_update JSONB[]; + BEGIN + planned_update := '{}'::jsonb[]; + FOR row IN SELECT * FROM metric_series + LOOP + -- Transform events attributes + events_att := row.filter -> 'events'; + IF events_att IS NOT NULL THEN + new_events := '{}'::jsonb[]; + FOR element IN SELECT jsonb_array_elements(events_att) + LOOP + changed := FALSE; + new_value := get_new_event_key(element ->> 'type'); + if new_value IS NOT NULL THEN + changed := TRUE; + new_value := replace(new_value, '"', ''); + element := element || jsonb_build_object('type', new_value); + END IF; + -- Transform event's sub-filters attributes + event_filters_att := element -> 'filters'; + new_event_filters := '{}'::jsonb[]; + IF event_filters_att IS NOT NULL AND jsonb_array_length(event_filters_att) > 0 THEN + FOR s_element IN SELECT jsonb_array_elements(event_filters_att) + LOOP + new_value := get_new_event_filter_key(s_element ->> 'type'); + if new_value IS NOT NULL THEN + changed := TRUE; + new_value := replace(new_value, '"', ''); + s_element := s_element || jsonb_build_object('type', new_value); + new_event_filters := array_append(new_event_filters, s_element); + END IF; + END LOOP; + element := element || jsonb_build_object('filters', new_event_filters); + END IF; + IF changed THEN + new_events := array_append(new_events, element); + END IF; + END LOOP; + IF array_length(new_events, 1) > 0 THEN + row.filter := row.filter || jsonb_build_object('events', new_events); + END IF; + END IF; + + -- Transform filters attributes + filters_att := row.filter -> 'filters'; + IF filters_att IS NOT NULL THEN + new_filters := '{}'::jsonb; + FOR element IN SELECT jsonb_array_elements(filters_att) + LOOP + new_value := get_new_filter_key(element ->> 'type'); + if new_value IS NOT NULL THEN + new_value := replace(new_value, '"', ''); + element := element || jsonb_build_object('type', new_value); + new_filters := array_append(new_filters, element); + END IF; + END LOOP; + IF array_length(new_filters, 1) > 0 THEN + row.filter := row.filter || jsonb_build_object('filters', new_filters); + END IF; + END IF; + + IF array_length(new_events, 1) > 0 OR array_length(new_filters, 1) > 0 THEN + planned_update := array_append(planned_update, + jsonb_build_object('id', row.series_id, 'change', row.filter)); + END IF; + END LOOP; + + -- Update metric_series + IF array_length(planned_update, 1) > 0 THEN + raise notice 'must update % elements',array_length(planned_update, 1); + + UPDATE metric_series + SET filter=changes.change -> 'change' + FROM (SELECT unnest(planned_update)) AS changes(change) + WHERE series_id = (changes.change -> 'id')::integer; + raise notice 'update done'; + ELSE + raise notice 'nothing to update'; + END IF; + END ; +$$ +LANGUAGE plpgsql; + +DROP FUNCTION get_new_filter_key; +DROP FUNCTION get_new_event_filter_key; +DROP FUNCTION get_new_event_key; +DROP FUNCTION get_global_key; + +DROP TABLE IF EXISTS public.funnels; +ALTER TABLE IF EXISTS public.metrics + ADD COLUMN IF NOT EXISTS data jsonb NULL; +COMMIT; + +CREATE INDEX CONCURRENTLY IF NOT EXISTS clicks_selector_idx ON events.clicks (selector); +CREATE INDEX CONCURRENTLY IF NOT EXISTS clicks_path_idx ON events.clicks (path); +CREATE INDEX CONCURRENTLY IF NOT EXISTS clicks_path_gin_idx ON events.clicks USING GIN (path gin_trgm_ops); +CREATE INDEX CONCURRENTLY IF NOT EXISTS issues_project_id_issue_id_idx ON public.issues (project_id, issue_id); \ No newline at end of file diff --git a/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql b/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql index e864f3664..0b2945b39 100644 --- a/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql +++ b/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql @@ -7,7 +7,7 @@ CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE OR REPLACE FUNCTION openreplay_version() RETURNS text AS $$ -SELECT 'v1.9.0-ee' +SELECT 'v1.10.0-ee' $$ LANGUAGE sql IMMUTABLE; @@ -109,7 +109,6 @@ $$ ('dashboards'), ('dashboard_widgets'), ('errors'), - ('funnels'), ('integrations'), ('issues'), ('jira_cloud'), @@ -131,7 +130,8 @@ $$ ('user_viewed_sessions'), ('users'), ('webhooks'), - ('sessions_notes')) + ('sessions_notes'), + ('assist_records')) select bool_and(exists(select * from information_schema.tables t where table_schema = 'public' @@ -146,7 +146,7 @@ $$ tenant_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY, tenant_key text NOT NULL DEFAULT generate_api_key(20), name text NOT NULL, - api_key text UNIQUE default generate_api_key(20) not null, + api_key text UNIQUE DEFAULT generate_api_key(20) NOT NULL, created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'), deleted_at timestamp without time zone NULL DEFAULT NULL, license text NULL, @@ -186,9 +186,9 @@ $$ email text NOT NULL UNIQUE, role user_role NOT NULL DEFAULT 'member', name text NOT NULL, - created_at timestamp without time zone NOT NULL default (now() at time zone 'utc'), + created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'), deleted_at timestamp without time zone NULL DEFAULT NULL, - api_key text UNIQUE default generate_api_key(20) not null, + api_key text UNIQUE DEFAULT generate_api_key(20) NOT NULL, jwt_iat timestamp without time zone NULL DEFAULT NULL, data jsonb NOT NULL DEFAULT'{}'::jsonb, weekly_report boolean NOT NULL DEFAULT TRUE, @@ -256,7 +256,8 @@ $$ "defaultInputMode": "plain" }'::jsonb, first_recorded_session_at timestamp without time zone NULL DEFAULT NULL, - sessions_last_check_at timestamp without time zone NULL DEFAULT NULL + sessions_last_check_at timestamp without time zone NULL DEFAULT NULL, + beacon_size integer NOT NULL DEFAULT 0 ); @@ -283,25 +284,25 @@ $$ IF NOT EXISTS(SELECT * FROM pg_type typ WHERE typ.typname = 'webhook_type') THEN - create type webhook_type as enum ('webhook','slack','email'); + CREATE TYPE webhook_type AS ENUM ('webhook','slack','email','msteams'); END IF; - create table IF NOT EXISTS webhooks + CREATE TABLE IF NOT EXISTS webhooks ( - webhook_id integer generated by default as identity + webhook_id integer generated by DEFAULT as identity constraint webhooks_pkey primary key, - tenant_id integer not null + tenant_id integer NOT NULL constraint webhooks_tenant_id_fkey references tenants on delete cascade, - endpoint text not null, - created_at timestamp default timezone('utc'::text, now()) not null, + endpoint text NOT NULL, + created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL, deleted_at timestamp, auth_header text, - type webhook_type not null, - index integer default 0 not null, + type webhook_type NOT NULL DEFAULT 'webhook', + index integer DEFAULT 0 NOT NULL, name varchar(100) ); @@ -334,39 +335,24 @@ $$ ); - CREATE TABLE IF NOT EXISTS funnels - ( - funnel_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY, - project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE, - user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, - name text not null, - filter jsonb not null, - created_at timestamp default timezone('utc'::text, now()) not null, - deleted_at timestamp, - is_public boolean NOT NULL DEFAULT False - ); - - CREATE INDEX IF NOT EXISTS funnels_user_id_is_public_idx ON public.funnels (user_id, is_public); - CREATE INDEX IF NOT EXISTS funnels_project_id_idx ON public.funnels (project_id); - IF NOT EXISTS(SELECT * FROM pg_type typ WHERE typ.typname = 'announcement_type') THEN - create type announcement_type as enum ('notification','alert'); + CREATE TYPE announcement_type AS ENUM ('notification','alert'); END IF; - create table IF NOT EXISTS announcements + CREATE TABLE IF NOT EXISTS announcements ( - announcement_id serial not null + announcement_id serial NOT NULL constraint announcements_pk primary key, - title text not null, - description text not null, + title text NOT NULL, + description text NOT NULL, button_text varchar(30), button_url text, image_url text, - created_at timestamp default timezone('utc'::text, now()) not null, - type announcement_type default 'notification'::announcement_type not null + created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL, + type announcement_type DEFAULT 'notification'::announcement_type NOT NULL ); IF NOT EXISTS(SELECT * @@ -395,14 +381,14 @@ $$ CREATE TABLE IF NOT EXISTS jira_cloud ( - user_id integer not null + user_id integer NOT NULL constraint jira_cloud_pk primary key constraint jira_cloud_users_fkey references users on delete cascade, - username text not null, - token text not null, + username text NOT NULL, + token text NOT NULL, url text ); @@ -441,7 +427,6 @@ $$ context jsonb DEFAULT NULL ); CREATE INDEX IF NOT EXISTS issues_issue_id_type_idx ON issues (issue_id, type); - CREATE INDEX IF NOT EXISTS issues_context_string_gin_idx ON public.issues USING GIN (context_string gin_trgm_ops); CREATE INDEX IF NOT EXISTS issues_project_id_issue_id_idx ON public.issues (project_id, issue_id); CREATE INDEX IF NOT EXISTS issues_project_id_idx ON issues (project_id); @@ -591,12 +576,9 @@ $$ CREATE INDEX IF NOT EXISTS sessions_metadata8_gin_idx ON public.sessions USING GIN (metadata_8 gin_trgm_ops); CREATE INDEX IF NOT EXISTS sessions_metadata9_gin_idx ON public.sessions USING GIN (metadata_9 gin_trgm_ops); CREATE INDEX IF NOT EXISTS sessions_metadata10_gin_idx ON public.sessions USING GIN (metadata_10 gin_trgm_ops); - CREATE INDEX IF NOT EXISTS sessions_user_os_gin_idx ON public.sessions USING GIN (user_os gin_trgm_ops); - CREATE INDEX IF NOT EXISTS sessions_user_browser_gin_idx ON public.sessions USING GIN (user_browser gin_trgm_ops); CREATE INDEX IF NOT EXISTS sessions_user_device_gin_idx ON public.sessions USING GIN (user_device gin_trgm_ops); CREATE INDEX IF NOT EXISTS sessions_user_id_gin_idx ON public.sessions USING GIN (user_id gin_trgm_ops); CREATE INDEX IF NOT EXISTS sessions_user_anonymous_id_gin_idx ON public.sessions USING GIN (user_anonymous_id gin_trgm_ops); - CREATE INDEX IF NOT EXISTS sessions_user_country_gin_idx ON public.sessions (project_id, user_country); CREATE INDEX IF NOT EXISTS sessions_start_ts_idx ON public.sessions (start_ts) WHERE duration > 0; CREATE INDEX IF NOT EXISTS sessions_project_id_idx ON public.sessions (project_id) WHERE duration > 0; CREATE INDEX IF NOT EXISTS sessions_session_id_project_id_start_ts_idx ON sessions (session_id, project_id, start_ts) WHERE duration > 0; @@ -646,14 +628,28 @@ $$ CREATE INDEX IF NOT EXISTS user_favorite_sessions_user_id_session_id_idx ON user_favorite_sessions (user_id, session_id); + CREATE TABLE IF NOT EXISTS frontend_signals + ( + project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE, + user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, + timestamp bigint NOT NULL, + action text NOT NULL, + source text NOT NULL, + category text NOT NULL, + data jsonb, + created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL + ); + CREATE INDEX IF NOT EXISTS frontend_signals_user_id_idx ON frontend_signals (user_id); + + CREATE TABLE IF NOT EXISTS assigned_sessions ( session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE, issue_id text NOT NULL, provider oauth_provider NOT NULL, created_by integer NOT NULL, - created_at timestamp default timezone('utc'::text, now()) NOT NULL, - provider_data jsonb default'{}'::jsonb NOT NULL + created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL, + provider_data jsonb DEFAULT'{}'::jsonb NOT NULL ); CREATE INDEX IF NOT EXISTS assigned_sessions_session_id_idx ON assigned_sessions (session_id); @@ -706,8 +702,8 @@ $$ project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE, action job_action NOT NULL, reference_id text NOT NULL, - created_at timestamp default timezone('utc'::text, now()) NOT NULL, - updated_at timestamp default timezone('utc'::text, now()) NULL, + created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL, + updated_at timestamp DEFAULT timezone('utc'::text, now()) NULL, start_at timestamp NOT NULL, errors text NULL ); @@ -735,37 +731,29 @@ $$ CREATE INDEX IF NOT EXISTS traces_created_at_idx ON traces (created_at); CREATE INDEX IF NOT EXISTS traces_action_idx ON traces (action); - CREATE TYPE metric_type AS ENUM ('timeseries','table', 'predefined','funnel'); - CREATE TYPE metric_view_type AS ENUM ('lineChart','progress','table','pieChart','areaChart','barChart','stackedBarChart','stackedBarLineChart','overview','map'); CREATE TABLE IF NOT EXISTS metrics ( metric_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY, - project_id integer NULL REFERENCES projects (project_id) ON DELETE CASCADE, - user_id integer REFERENCES users (user_id) ON DELETE SET NULL, - name text NOT NULL, - is_public boolean NOT NULL DEFAULT FALSE, - active boolean NOT NULL DEFAULT TRUE, - created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), + project_id integer NULL REFERENCES projects (project_id) ON DELETE CASCADE, + user_id integer REFERENCES users (user_id) ON DELETE SET NULL, + name text NOT NULL, + is_public boolean NOT NULL DEFAULT TRUE, + created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), deleted_at timestamp, - edited_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), - metric_type metric_type NOT NULL DEFAULT 'timeseries', - view_type metric_view_type NOT NULL DEFAULT 'lineChart', - metric_of text NOT NULL DEFAULT 'sessionCount', - metric_value text[] NOT NULL DEFAULT '{}'::text[], + edited_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), + metric_type text NOT NULL DEFAULT 'timeseries', + view_type text NOT NULL DEFAULT 'lineChart', + metric_of text NOT NULL DEFAULT 'sessionCount', + metric_value text[] NOT NULL DEFAULT '{}'::text[], metric_format text, - category text NULL DEFAULT 'custom', - is_pinned boolean NOT NULL DEFAULT FALSE, - is_predefined boolean NOT NULL DEFAULT FALSE, - is_template boolean NOT NULL DEFAULT FALSE, - predefined_key text NULL DEFAULT NULL, - default_config jsonb NOT NULL DEFAULT '{ + thumbnail text, + is_pinned boolean NOT NULL DEFAULT FALSE, + default_config jsonb NOT NULL DEFAULT '{ "col": 2, "row": 2, "position": 0 }'::jsonb, - CONSTRAINT null_project_id_for_template_only - CHECK ( (metrics.category != 'custom') != (metrics.project_id IS NOT NULL) ), - CONSTRAINT unique_key UNIQUE (predefined_key) + data jsonb NULL ); CREATE INDEX IF NOT EXISTS metrics_user_id_is_public_idx ON public.metrics (user_id, is_public); CREATE TABLE IF NOT EXISTS metric_series @@ -809,9 +797,9 @@ $$ search_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY, project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE, user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, - name text not null, - filter jsonb not null, - created_at timestamp default timezone('utc'::text, now()) not null, + name text NOT NULL, + filter jsonb NOT NULL, + created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL, deleted_at timestamp, is_public boolean NOT NULL DEFAULT False ); @@ -863,7 +851,7 @@ $$ ( note_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY, message text NOT NULL, - created_at timestamp without time zone NOT NULL default (now() at time zone 'utc'), + created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'), user_id integer NULL REFERENCES users (user_id) ON DELETE SET NULL, deleted_at timestamp without time zone NULL DEFAULT NULL, tag text NULL, @@ -927,15 +915,6 @@ $$ CREATE INDEX IF NOT EXISTS pages_timestamp_idx ON events.pages (timestamp); CREATE INDEX IF NOT EXISTS pages_session_id_timestamp_idx ON events.pages (session_id, timestamp); CREATE INDEX IF NOT EXISTS pages_base_referrer_idx ON events.pages (base_referrer); - CREATE INDEX IF NOT EXISTS pages_base_referrer_gin_idx2 ON events.pages USING GIN (RIGHT(base_referrer, - length(base_referrer) - - (CASE - WHEN base_referrer LIKE 'http://%' - THEN 7 - WHEN base_referrer LIKE 'https://%' - THEN 8 - ELSE 0 END)) - gin_trgm_ops); CREATE INDEX IF NOT EXISTS pages_response_time_idx ON events.pages (response_time); CREATE INDEX IF NOT EXISTS pages_response_end_idx ON events.pages (response_end); CREATE INDEX IF NOT EXISTS pages_path_gin_idx ON events.pages USING GIN (path gin_trgm_ops); @@ -973,6 +952,7 @@ $$ timestamp bigint NOT NULL, label text DEFAULT NULL, url text DEFAULT '' NOT NULL, + path text, selector text DEFAULT '' NOT NULL, PRIMARY KEY (session_id, message_id) ); @@ -982,9 +962,11 @@ $$ CREATE INDEX IF NOT EXISTS clicks_timestamp_idx ON events.clicks (timestamp); CREATE INDEX IF NOT EXISTS clicks_label_session_id_timestamp_idx ON events.clicks (label, session_id, timestamp); CREATE INDEX IF NOT EXISTS clicks_url_idx ON events.clicks (url); - CREATE INDEX IF NOT EXISTS clicks_url_gin_idx ON events.clicks USING GIN (url gin_trgm_ops); CREATE INDEX IF NOT EXISTS clicks_url_session_id_timestamp_selector_idx ON events.clicks (url, session_id, timestamp, selector); CREATE INDEX IF NOT EXISTS clicks_session_id_timestamp_idx ON events.clicks (session_id, timestamp); + CREATE INDEX IF NOT EXISTS clicks_selector_idx ON events.clicks (selector); + CREATE INDEX IF NOT EXISTS clicks_path_idx ON events.clicks (path); + CREATE INDEX IF NOT EXISTS clicks_path_gin_idx ON events.clicks USING GIN (path gin_trgm_ops); CREATE TABLE IF NOT EXISTS events.inputs @@ -997,9 +979,7 @@ $$ PRIMARY KEY (session_id, message_id) ); CREATE INDEX IF NOT EXISTS inputs_session_id_idx ON events.inputs (session_id); - CREATE INDEX IF NOT EXISTS inputs_label_value_idx ON events.inputs (label, value); CREATE INDEX IF NOT EXISTS inputs_label_gin_idx ON events.inputs USING GIN (label gin_trgm_ops); - CREATE INDEX IF NOT EXISTS inputs_label_idx ON events.inputs (label); CREATE INDEX IF NOT EXISTS inputs_timestamp_idx ON events.inputs (timestamp); CREATE INDEX IF NOT EXISTS inputs_label_session_id_timestamp_idx ON events.inputs (label, session_id, timestamp); @@ -1066,7 +1046,6 @@ $$ name text NOT NULL, PRIMARY KEY (session_id, message_id) ); - CREATE INDEX IF NOT EXISTS state_actions_name_idx ON events.state_actions (name); CREATE INDEX IF NOT EXISTS state_actions_name_gin_idx ON events.state_actions USING GIN (name gin_trgm_ops); CREATE INDEX IF NOT EXISTS state_actions_timestamp_idx ON events.state_actions (timestamp); @@ -1102,17 +1081,12 @@ $$ CREATE INDEX IF NOT EXISTS resources_session_id_idx ON events.resources (session_id); CREATE INDEX IF NOT EXISTS resources_status_idx ON events.resources (status); CREATE INDEX IF NOT EXISTS resources_type_idx ON events.resources (type); - CREATE INDEX IF NOT EXISTS resources_duration_durationgt0_idx ON events.resources (duration) WHERE duration > 0; CREATE INDEX IF NOT EXISTS resources_url_host_idx ON events.resources (url_host); CREATE INDEX IF NOT EXISTS resources_timestamp_idx ON events.resources (timestamp); CREATE INDEX IF NOT EXISTS resources_success_idx ON events.resources (success); - CREATE INDEX IF NOT EXISTS resources_url_gin_idx ON events.resources USING GIN (url gin_trgm_ops); - CREATE INDEX IF NOT EXISTS resources_url_idx ON events.resources (url); CREATE INDEX IF NOT EXISTS resources_url_hostpath_gin_idx ON events.resources USING GIN (url_hostpath gin_trgm_ops); - CREATE INDEX IF NOT EXISTS resources_url_hostpath_idx ON events.resources (url_hostpath); CREATE INDEX IF NOT EXISTS resources_timestamp_type_durationgt0NN_idx ON events.resources (timestamp, type) WHERE duration > 0 AND duration IS NOT NULL; - CREATE INDEX IF NOT EXISTS resources_session_id_timestamp_idx ON events.resources (session_id, timestamp); CREATE INDEX IF NOT EXISTS resources_session_id_timestamp_type_idx ON events.resources (session_id, timestamp, type); CREATE INDEX IF NOT EXISTS resources_timestamp_type_durationgt0NN_noFetch_idx ON events.resources (timestamp, type) WHERE duration > 0 AND duration IS NOT NULL AND type != 'fetch'; CREATE INDEX IF NOT EXISTS resources_session_id_timestamp_url_host_fail_idx ON events.resources (session_id, timestamp, url_host) WHERE success = FALSE; @@ -1229,257 +1203,26 @@ $$ CREATE INDEX IF NOT EXISTS requests_response_body_nn_gin_idx ON events_common.requests USING GIN (response_body gin_trgm_ops) WHERE response_body IS NOT NULL; CREATE INDEX IF NOT EXISTS requests_status_code_nn_idx ON events_common.requests (status_code) WHERE status_code IS NOT NULL; CREATE INDEX IF NOT EXISTS requests_session_id_status_code_nn_idx ON events_common.requests (session_id, status_code) WHERE status_code IS NOT NULL; - CREATE INDEX IF NOT EXISTS requests_host_nn_idx ON events_common.requests (host) WHERE host IS NOT NULL; CREATE INDEX IF NOT EXISTS requests_host_nn_gin_idx ON events_common.requests USING GIN (host gin_trgm_ops) WHERE host IS NOT NULL; CREATE INDEX IF NOT EXISTS requests_path_nn_idx ON events_common.requests (path) WHERE path IS NOT NULL; CREATE INDEX IF NOT EXISTS requests_path_nn_gin_idx ON events_common.requests USING GIN (path gin_trgm_ops) WHERE path IS NOT NULL; - CREATE INDEX IF NOT EXISTS requests_query_nn_idx ON events_common.requests (query) WHERE query IS NOT NULL; CREATE INDEX IF NOT EXISTS requests_query_nn_gin_idx ON events_common.requests USING GIN (query gin_trgm_ops) WHERE query IS NOT NULL; - + CREATE TABLE IF NOT EXISTS assist_records + ( + record_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY, + project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE, + user_id integer NOT NULL REFERENCES users (user_id) ON DELETE SET NULL, + session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE SET NULL, + created_at bigint NOT NULL DEFAULT (EXTRACT(EPOCH FROM now() at time zone 'utc') * 1000)::bigint, + deleted_at timestamp without time zone NULL DEFAULT NULL, + name text NOT NULL, + file_key text NOT NULL, + duration integer NOT NULL + ); END IF; END; $$ LANGUAGE plpgsql; -INSERT INTO metrics (name, category, default_config, is_predefined, is_template, is_public, predefined_key, metric_type, - view_type) -VALUES ('Captured sessions', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 -}', true, true, true, 'count_sessions', 'predefined', 'overview'), - ('Request Load Time', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_request_load_time', 'predefined', 'overview'), - ('Page Load Time', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_page_load_time', 'predefined', 'overview'), - ('Image Load Time', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_image_load_time', 'predefined', 'overview'), - ('DOM Content Load Start', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_dom_content_load_start', 'predefined', 'overview'), - ('First Meaningful paint', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_first_contentful_pixel', 'predefined', 'overview'), - ('No. of Visited Pages', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_visited_pages', 'predefined', 'overview'), - ('Session Duration', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_session_duration', 'predefined', 'overview'), - ('DOM Build Time', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_pages_dom_buildtime', 'predefined', 'overview'), - ('Pages Response Time', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_pages_response_time', 'predefined', 'overview'), - ('Response Time', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_response_time', 'predefined', 'overview'), - ('First Paint', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_first_paint', 'predefined', 'overview'), - ('DOM Content Loaded', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_dom_content_loaded', 'predefined', 'overview'), - ('Time Till First byte', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_till_first_byte', 'predefined', 'overview'), - ('Time To Interactive', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_time_to_interactive', 'predefined', 'overview'), - ('Captured requests', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'count_requests', 'predefined', 'overview'), - ('Time To Render', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_time_to_render', 'predefined', 'overview'), - ('Memory Consumption', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_used_js_heap_size', 'predefined', 'overview'), - ('CPU Load', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_cpu', 'predefined', 'overview'), - ('Frame rate', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_fps', 'predefined', 'overview'), - - ('Sessions Affected by JS Errors', 'errors', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'impacted_sessions_by_js_errors', 'predefined', 'barChart'), - ('Top Domains with 4xx Fetch Errors', 'errors', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'domains_errors_4xx', 'predefined', 'lineChart'), - ('Top Domains with 5xx Fetch Errors', 'errors', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'domains_errors_5xx', 'predefined', 'lineChart'), - ('Errors per Domain', 'errors', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'errors_per_domains', 'predefined', 'table'), - ('Fetch Calls with Errors', 'errors', '{ - "col": 4, - "row": 2, - "position": 0 - }', true, true, true, 'calls_errors', 'predefined', 'table'), - ('Errors by Type', 'errors', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'errors_per_type', 'predefined', 'barChart'), - ('Errors by Origin', 'errors', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'resources_by_party', 'predefined', 'stackedBarChart'), - - ('Speed Index by Location', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'speed_location', 'predefined', 'map'), - ('Slowest Domains', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'slowest_domains', 'predefined', 'table'), - ('Sessions per Browser', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'sessions_per_browser', 'predefined', 'table'), - ('Time To Render', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'time_to_render', 'predefined', 'areaChart'), - ('Sessions Impacted by Slow Pages', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'impacted_sessions_by_slow_pages', 'predefined', 'areaChart'), - ('Memory Consumption', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'memory_consumption', 'predefined', 'areaChart'), - ('CPU Load', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'cpu', 'predefined', 'areaChart'), - ('Frame Rate', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'fps', 'predefined', 'areaChart'), - ('Crashes', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'crashes', 'predefined', 'areaChart'), - ('Resources Loaded vs Visually Complete', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'resources_vs_visually_complete', 'predefined', 'areaChart'), - ('DOM Build Time', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'pages_dom_buildtime', 'predefined', 'areaChart'), - ('Pages Response Time', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'pages_response_time', 'predefined', 'areaChart'), - ('Pages Response Time Distribution', 'performance', '{ - "col": 4, - "row": 2, - "position": 0 - }', true, true, true, 'pages_response_time_distribution', 'predefined', 'barChart'), - - ('Missing Resources', 'resources', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'missing_resources', 'predefined', 'table'), - ('Slowest Resources', 'resources', '{ - "col": 4, - "row": 2, - "position": 0 - }', true, true, true, 'slowest_resources', 'predefined', 'table'), - ('Resources Fetch Time', 'resources', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'resources_loading_time', 'predefined', 'table'), - ('Resource Loaded vs Response End', 'resources', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'resource_type_vs_response_end', 'predefined', 'stackedBarLineChart'), - ('Breakdown of Loaded Resources', 'resources', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'resources_count_by_type', 'predefined', 'stackedBarChart') -ON CONFLICT (predefined_key) DO UPDATE - SET name=excluded.name, - category=excluded.category, - default_config=excluded.default_config, - is_predefined=excluded.is_predefined, - is_template=excluded.is_template, - is_public=excluded.is_public, - metric_type=excluded.metric_type, - view_type=excluded.view_type; - COMMIT; \ No newline at end of file diff --git a/ee/utilities/.gitignore b/ee/utilities/.gitignore index 8c9dca279..cd68b1ffb 100644 --- a/ee/utilities/.gitignore +++ b/ee/utilities/.gitignore @@ -15,5 +15,4 @@ servers/sourcemaps-server.js /utils/helper.js /utils/assistHelper.js .local -run-dev.sh *.mmdb diff --git a/ee/utilities/Dockerfile b/ee/utilities/Dockerfile index 08ccba56f..3119b5eed 100644 --- a/ee/utilities/Dockerfile +++ b/ee/utilities/Dockerfile @@ -18,4 +18,4 @@ USER 1001 ADD --chown=1001 https://static.openreplay.com/geoip/GeoLite2-Country.mmdb $MAXMINDDB_FILE ENTRYPOINT ["/sbin/tini", "--"] -CMD npm start +CMD npm start \ No newline at end of file diff --git a/ee/utilities/clean.sh b/ee/utilities/clean-dev.sh similarity index 100% rename from ee/utilities/clean.sh rename to ee/utilities/clean-dev.sh diff --git a/ee/utilities/package-lock.json b/ee/utilities/package-lock.json index ce7002fee..1d74677cf 100644 --- a/ee/utilities/package-lock.json +++ b/ee/utilities/package-lock.json @@ -1,22 +1,22 @@ { - "name": "utilities-server", + "name": "assist-server", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "utilities-server", + "name": "assist-server", "version": "1.0.0", "license": "Elastic License 2.0 (ELv2)", "dependencies": { - "@maxmind/geoip2-node": "^3.4.0", - "@socket.io/redis-adapter": "^7.2.0", - "express": "^4.18.1", - "jsonwebtoken": "^8.5.1", - "redis": "^4.2.0", - "socket.io": "^4.5.1", - "ua-parser-js": "^1.0.2", - "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.10.0" + "@maxmind/geoip2-node": "^3.5.0", + "@socket.io/redis-adapter": "^8.1.0", + "express": "^4.18.2", + "jsonwebtoken": "^9.0.0", + "redis": "^4.6.4", + "socket.io": "^4.6.0", + "ua-parser-js": "^1.0.33", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.19.0" } }, "node_modules/@maxmind/geoip2-node": { @@ -30,19 +30,19 @@ } }, "node_modules/@redis/bloom": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.1.0.tgz", - "integrity": "sha512-9QovlxmpRtvxVbN0UBcv8WfdSMudNZZTFqCsnBszcQXqaZb/TVe30ScgGEO7u1EAIacTPAo7/oCYjYAxiHLanQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", "peerDependencies": { "@redis/client": "^1.0.0" } }, "node_modules/@redis/client": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.4.2.tgz", - "integrity": "sha512-oUdEjE0I7JS5AyaAjkD3aOXn9NhO7XKyPyXEyrgFDu++VrVBHUPnV6dgEya9TcMuj5nIJRuCzCm8ZP+c9zCHPw==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.5.tgz", + "integrity": "sha512-fuMnpDYSjT5JXR9rrCW1YWA4L8N/9/uS4ImT3ZEC/hcaQRI1D/9FvwjriRj1UvepIgzZXthFVKMNRzP/LNL7BQ==", "dependencies": { - "cluster-key-slot": "1.1.1", + "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", "yallist": "4.0.0" }, @@ -67,9 +67,9 @@ } }, "node_modules/@redis/search": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.0.tgz", - "integrity": "sha512-NyFZEVnxIJEybpy+YskjgOJRNsfTYqaPbK/Buv6W2kmFNaRk85JiqjJZA5QkRmWvGbyQYwoO5QfDi2wHskKrQQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.1.tgz", + "integrity": "sha512-pqCXTc5e7wJJgUuJiC3hBgfoFRoPxYzwn0BEfKgejTM7M/9zP3IpUcqcjgfp8hF+LoV8rHZzcNTz7V+pEIY7LQ==", "peerDependencies": { "@redis/client": "^1.0.0" } @@ -88,17 +88,19 @@ "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" }, "node_modules/@socket.io/redis-adapter": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@socket.io/redis-adapter/-/redis-adapter-7.2.0.tgz", - "integrity": "sha512-/r6oF6Myz0K9uatB/pfCi0BhKg/KRMh1OokrqcjlNz6aq40WiXdFLRbHJQuwGHq/KvB+D6141K+IynbVxZGvhw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/redis-adapter/-/redis-adapter-8.1.0.tgz", + "integrity": "sha512-8nGMKcQ+DWpgefxA/Pi25aLajVilRPKwu29mZXu5cT+WGVYItcCkfMr4RsMmyYXUyJf00mN+7WinVLihmJwpXA==", "dependencies": { "debug": "~4.3.1", - "notepack.io": "~2.2.0", - "socket.io-adapter": "^2.4.0", - "uid2": "0.0.3" + "notepack.io": "~3.0.1", + "uid2": "1.0.0" }, "engines": { "node": ">=10.0.0" + }, + "peerDependencies": { + "socket.io-adapter": "^2.4.0" } }, "node_modules/@types/cookie": { @@ -107,14 +109,17 @@ "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" }, "node_modules/@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dependencies": { + "@types/node": "*" + } }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" + "version": "18.14.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.1.tgz", + "integrity": "sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ==" }, "node_modules/accepts": { "version": "1.3.8", @@ -239,9 +244,9 @@ } }, "node_modules/cluster-key-slot": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.1.tgz", - "integrity": "sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", "engines": { "node": ">=0.10.0" } @@ -258,9 +263,9 @@ } }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { "node": ">= 0.6" } @@ -350,9 +355,9 @@ } }, "node_modules/engine.io": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz", - "integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz", + "integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==", "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", @@ -363,16 +368,16 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.0.3", - "ws": "~8.2.3" + "ws": "~8.11.0" }, "engines": { "node": ">=10.0.0" } }, "node_modules/engine.io-parser": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", - "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", "engines": { "node": ">=10.0.0" } @@ -520,9 +525,9 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -608,24 +613,18 @@ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "dependencies": { "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" + "node": ">=12", + "npm": ">=6" } }, "node_modules/jsprim": { @@ -661,40 +660,21 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + "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.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + "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==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } }, "node_modules/map-obj": { "version": "4.3.0", @@ -794,9 +774,9 @@ } }, "node_modules/notepack.io": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-2.2.0.tgz", - "integrity": "sha512-9b5w3t5VSH6ZPosoYnyDONnUTF8o0UkBw7JLA6eBlYJWyGT1Q3vQa8Hmuj1/X6RYvHjjygBDgw6fJhe0JEojfw==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-3.0.1.tgz", + "integrity": "sha512-TKC/8zH5pXIAMVQio2TvVDTtPRX+DJPHDqjRbxogtFiByHyzKmy96RA0JtCQJ+WouyyL4A10xomQzgbUT+1jCg==" }, "node_modules/object-assign": { "version": "4.1.1", @@ -807,9 +787,9 @@ } }, "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "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" } @@ -898,15 +878,15 @@ } }, "node_modules/redis": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.5.1.tgz", - "integrity": "sha512-oxXSoIqMJCQVBTfxP6BNTCtDMyh9G6Vi5wjdPdV/sRKkufyZslDqCScSGcOr6XGR/reAWZefz7E4leM31RgdBA==", + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.4.tgz", + "integrity": "sha512-wi2tgDdQ+Q8q+PR5FLRx4QvDiWaA+PoJbrzsyFqlClN5R4LplHqN3scs/aGjE//mbz++W19SgxiEnQ27jnCRaA==", "dependencies": { - "@redis/bloom": "1.1.0", - "@redis/client": "1.4.2", + "@redis/bloom": "1.2.0", + "@redis/client": "1.5.5", "@redis/graph": "1.1.0", "@redis/json": "1.0.4", - "@redis/search": "1.1.0", + "@redis/search": "1.1.1", "@redis/time-series": "1.0.4" } }, @@ -935,11 +915,17 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/send": { @@ -1016,15 +1002,15 @@ } }, "node_modules/socket.io": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.4.tgz", - "integrity": "sha512-m3GC94iK9MfIEeIBfbhJs5BqFibMtkRk8ZpKwG2QwxV0m/eEhPIV4ara6XCF1LWNAus7z58RodiZlAH71U3EhQ==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", + "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "debug": "~4.3.2", - "engine.io": "~6.2.1", - "socket.io-adapter": "~2.4.0", + "engine.io": "~6.4.1", + "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.1" }, "engines": { @@ -1032,14 +1018,17 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", - "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dependencies": { + "ws": "~8.11.0" + } }, "node_modules/socket.io-parser": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz", - "integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", + "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -1096,9 +1085,9 @@ } }, "node_modules/ua-parser-js": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", - "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", "funding": [ { "type": "opencollective", @@ -1114,9 +1103,12 @@ } }, "node_modules/uid2": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", - "integrity": "sha512-5gSP1liv10Gjp8cMEnFd6shzkL/D6W1uhXSFNCxDC+YI8+L8wkCYCbJ7n77Ezb4wE/xzMogecE+DtamEe9PZjg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-1.0.0.tgz", + "integrity": "sha512-+I6aJUv63YAcY9n4mQreLUt0d4lvwkkopDNmpomkAUz0fAkEMV9pRWxN0EjhW1YfRhcuyHg2v3mwddCDW1+LFQ==", + "engines": { + "node": ">= 4.0.0" + } }, "node_modules/unpipe": { "version": "1.0.0", @@ -1135,8 +1127,8 @@ } }, "node_modules/uWebSockets.js": { - "version": "20.10.0", - "resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#806df48c9da86af7b3341f3e443388c7cd15c3de" + "version": "20.19.0", + "resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#42c9c0d5d31f46ca4115dc75672b0037ec970f28" }, "node_modules/vary": { "version": "1.1.2", @@ -1160,9 +1152,9 @@ } }, "node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", "engines": { "node": ">=10.0.0" }, diff --git a/ee/utilities/package.json b/ee/utilities/package.json index 5dfd325af..3fcedf03b 100644 --- a/ee/utilities/package.json +++ b/ee/utilities/package.json @@ -1,5 +1,5 @@ { - "name": "utilities-server", + "name": "assist-server", "version": "1.0.0", "description": "assist server to get live sessions & sourcemaps reader to get stack trace", "main": "peerjs-server.js", @@ -18,13 +18,13 @@ }, "homepage": "https://github.com/openreplay/openreplay#readme", "dependencies": { - "@maxmind/geoip2-node": "^3.4.0", - "@socket.io/redis-adapter": "^7.2.0", - "express": "^4.18.1", - "jsonwebtoken": "^8.5.1", - "redis": "^4.2.0", - "socket.io": "^4.5.1", - "ua-parser-js": "^1.0.2", - "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.10.0" + "@maxmind/geoip2-node": "^3.5.0", + "@socket.io/redis-adapter": "^8.1.0", + "express": "^4.18.2", + "jsonwebtoken": "^9.0.0", + "redis": "^4.6.4", + "socket.io": "^4.6.0", + "ua-parser-js": "^1.0.33", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.19.0" } } diff --git a/ee/utilities/run-dev.sh b/ee/utilities/run-dev.sh new file mode 100755 index 000000000..00e8d5a4b --- /dev/null +++ b/ee/utilities/run-dev.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -a +source .env +set +a + +npm start \ No newline at end of file diff --git a/ee/utilities/servers/websocket-cluster.js b/ee/utilities/servers/websocket-cluster.js index 6aa2bade5..e129bfcb6 100644 --- a/ee/utilities/servers/websocket-cluster.js +++ b/ee/utilities/servers/websocket-cluster.js @@ -24,7 +24,7 @@ const { const {createAdapter} = require("@socket.io/redis-adapter"); const {createClient} = require("redis"); const wsRouter = express.Router(); -const REDIS_URL = process.env.REDIS_URL || "redis://localhost:6379"; +const REDIS_URL = (process.env.REDIS_URL || "localhost:6379").replace(/((^\w+:|^)\/\/|^)/, 'redis://'); const pubClient = createClient({url: REDIS_URL}); const subClient = pubClient.duplicate(); console.log(`Using Redis: ${REDIS_URL}`); @@ -309,7 +309,8 @@ module.exports = { debug && console.log(`notifying new agent about no SESSIONS`); io.to(socket.id).emit(EVENTS_DEFINITION.emit.NO_SESSIONS); } - await io.of('/').adapter.remoteJoin(socket.id, socket.peerId); + // await io.of('/').adapter.join(socket.id, socket.peerId); + await socket.join(socket.peerId); let rooms = await io.of('/').adapter.allRooms(); if (rooms.has(socket.peerId)) { let connectedSockets = await io.in(socket.peerId).fetchSockets(); diff --git a/ee/utilities/servers/websocket.js b/ee/utilities/servers/websocket.js index bf65789f2..c906b5987 100644 --- a/ee/utilities/servers/websocket.js +++ b/ee/utilities/servers/websocket.js @@ -287,7 +287,7 @@ module.exports = { debug && console.log(`notifying new agent about no SESSIONS`); io.to(socket.id).emit(EVENTS_DEFINITION.emit.NO_SESSIONS); } - socket.join(socket.peerId); + await socket.join(socket.peerId); if (io.sockets.adapter.rooms.get(socket.peerId)) { debug && console.log(`${socket.id} joined room:${socket.peerId}, as:${socket.identity}, members:${io.sockets.adapter.rooms.get(socket.peerId).size}`); } diff --git a/frontend/.babelrc b/frontend/.babelrc index 8c99ee5a4..631979df1 100644 --- a/frontend/.babelrc +++ b/frontend/.babelrc @@ -5,6 +5,7 @@ "@babel/preset-typescript" ], "plugins": [ + "babel-plugin-react-require", [ "@babel/plugin-proposal-private-property-in-object", { "loose": true } ], [ "@babel/plugin-transform-runtime", { "regenerator": true } ], [ "@babel/plugin-proposal-decorators", { "legacy":true } ], diff --git a/frontend/.env.sample b/frontend/.env.sample index d5337deee..4b6cface2 100644 --- a/frontend/.env.sample +++ b/frontend/.env.sample @@ -22,5 +22,5 @@ MINIO_ACCESS_KEY = '' MINIO_SECRET_KEY = '' # APP and TRACKER VERSIONS -VERSION = '1.9.0' -TRACKER_VERSION = '4.1.9' +VERSION = '1.10.0' +TRACKER_VERSION = '5.0.0' diff --git a/frontend/.gitignore b/frontend/.gitignore index 6fce862e7..c5ac128a5 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -3,7 +3,6 @@ node_modules/ public/ .idea drafts -yarn.lock app/components/ui/SVG.js *.DS_Store .env @@ -15,3 +14,8 @@ app/components/ui/SVG.js !.yarn/releases !.yarn/sdks !.yarn/versions +*.env.json +cypress.env.json +**/__diff_output__/* +*.diff.png +cypress/videos/ diff --git a/frontend/.storybook/config.js b/frontend/.storybook/config.DEPRECATED.js similarity index 76% rename from frontend/.storybook/config.js rename to frontend/.storybook/config.DEPRECATED.js index 1ff1f28d1..ba3f6eb05 100644 --- a/frontend/.storybook/config.js +++ b/frontend/.storybook/config.DEPRECATED.js @@ -2,26 +2,21 @@ import { configure, addDecorator } from '@storybook/react'; import { Provider } from 'react-redux'; import store from '../app/store'; import { MemoryRouter } from "react-router" -import { PlayerProvider } from '../app/player/store' const withProvider = (story) => ( - { story() } - ) // const req = require.context('../app/components/ui', true, /\.stories\.js$/); // const issues = require.context('../app/components/Session/Issues', true, /\.stories\.js$/); -// const bugFinder = require.context('../app/components/BugFinder', true, /\.stories\.js$/); addDecorator(withProvider); addDecorator(story => {story()}); // function loadStories() { // req.keys().forEach(filename => req(filename)); -// bugFinder.keys().forEach(filename => bugFinder(filename)); // } // configure(loadStories, module); @@ -33,4 +28,4 @@ configure( require.context('../app', true, /\.stories\.js$/), ], module -); \ No newline at end of file +); diff --git a/frontend/.storybook/main.ts b/frontend/.storybook/main.ts new file mode 100644 index 000000000..b11d20308 --- /dev/null +++ b/frontend/.storybook/main.ts @@ -0,0 +1,36 @@ +import custom from '../webpack.config'; + +export default { + stories: ['../app/components/**/*.stories.@(js|jsx|ts|tsx)'], + addons: [ + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-interactions', + ], + framework: '@storybook/react', + core: { + builder: '@storybook/builder-webpack5', + }, + reactOptions: { + fastRefresh: true, + }, + webpackFinal: async (config: any) => { + config.module = custom.module; + config.resolve = custom.resolve; + if (custom.plugins) { + config.plugins.unshift(custom.plugins[0]); + config.plugins.unshift(custom.plugins[1]); + config.plugins.unshift(custom.plugins[4]); + } + config.module.rules.unshift({ + test: /\.(svg)$/i, + exclude: /node_modules/, + use: [ + { + loader: 'file-loader', + }, + ], + }); + return config; + }, +}; diff --git a/frontend/.storybook/openReplayDecorator.js b/frontend/.storybook/openReplayDecorator.js new file mode 100644 index 000000000..730eaf636 --- /dev/null +++ b/frontend/.storybook/openReplayDecorator.js @@ -0,0 +1,13 @@ +import { Provider } from 'react-redux'; +import store from '../app/store'; +import { StoreProvider, RootStore } from '../app/mstore'; + +const withProvider = (Story) => ( + + + + + +); + +export default withProvider; diff --git a/frontend/.storybook/preview-head.html b/frontend/.storybook/preview-head.html deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/.storybook/preview.ts b/frontend/.storybook/preview.ts new file mode 100644 index 000000000..0334feb28 --- /dev/null +++ b/frontend/.storybook/preview.ts @@ -0,0 +1,13 @@ +import openReplayProvider from './openReplayDecorator'; +import '../app/styles/index.scss'; + +export default { + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, +}; +export const decorators = [openReplayProvider] \ No newline at end of file diff --git a/frontend/.storybook/webpack.config.js b/frontend/.storybook/webpack.config.js index 1a123fa52..ff2dbcee8 100644 --- a/frontend/.storybook/webpack.config.js +++ b/frontend/.storybook/webpack.config.js @@ -1,15 +1,3 @@ -const pathAlias = require('../path-alias'); -const mainConfig = require('../webpack.config.js'); - -module.exports = async ({ config }) => { - var conf = mainConfig(); - config.resolve.alias = Object.assign(conf.resolve.alias, config.resolve.alias); // Path Alias - config.resolve.extensions = conf.resolve.extensions - config.module.rules = conf.module.rules; - config.module.rules[0].use[0] = 'style-loader'; // instead of separated css - config.module.rules[1].use[0] = 'style-loader'; - config.plugins.push(conf.plugins[0]); // global React - config.plugins.push(conf.plugins[5]); - config.entry = config.entry.concat(conf.entry.slice(2)) // CSS entries - return config; -}; +export default { + +} \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 5e6c9b3b0..ccd4655ee 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -14,9 +14,13 @@ COPY nginx.conf /etc/nginx/conf.d/default.conf # Default step in docker build FROM nginx:alpine LABEL maintainer=Rajesh +ARG GIT_SHA +LABEL GIT_SHA=$GIT_SHA COPY --from=builder /work/public /var/www/openreplay COPY nginx.conf /etc/nginx/conf.d/default.conf +ENV GIT_SHA=$GIT_SHA + EXPOSE 8080 RUN chown -R nginx:nginx /var/cache/nginx && \ chown -R nginx:nginx /var/log/nginx && \ diff --git a/frontend/app/Router.js b/frontend/app/Router.js index 6a4aea446..662a7a7a7 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -6,11 +6,8 @@ import { Notification } from 'UI'; import { Loader } from 'UI'; import { fetchUserInfo } from 'Duck/user'; import withSiteIdUpdater from 'HOCs/withSiteIdUpdater'; -import WidgetViewPure from 'Components/Dashboard/components/WidgetView'; import Header from 'Components/Header/Header'; import { fetchList as fetchSiteList } from 'Duck/site'; -import { fetchList as fetchAnnouncements } from 'Duck/announcements'; -import { fetchList as fetchAlerts } from 'Duck/alerts'; import { withStore } from 'App/mstore'; import APIClient from './api_client'; @@ -31,14 +28,15 @@ const LiveSessionPure = lazy(() => import('Components/Session/LiveSession')); const OnboardingPure = lazy(() => import('Components/Onboarding/Onboarding')); const ClientPure = lazy(() => import('Components/Client/Client')); const AssistPure = lazy(() => import('Components/Assist')); -const BugFinderPure = lazy(() => import('Components/Overview')); +const SessionsOverviewPure = lazy(() => import('Components/Overview')); const DashboardPure = lazy(() => import('Components/Dashboard/NewDashboard')); const ErrorsPure = lazy(() => import('Components/Errors/Errors')); const FunnelDetailsPure = lazy(() => import('Components/Funnels/FunnelDetails')); const FunnelIssueDetails = lazy(() => import('Components/Funnels/FunnelIssueDetails')); const FunnelPagePure = lazy(() => import('Components/Funnels/FunnelPage')); +const MultiviewPure = lazy(() => import('Components/Session_/Multiview/Multiview.tsx')); -const BugFinder = withSiteIdUpdater(BugFinderPure); +const SessionsOverview = withSiteIdUpdater(SessionsOverviewPure); const Dashboard = withSiteIdUpdater(DashboardPure); const Session = withSiteIdUpdater(SessionPure); const LiveSession = withSiteIdUpdater(LiveSessionPure); @@ -49,6 +47,7 @@ const Errors = withSiteIdUpdater(ErrorsPure); const FunnelPage = withSiteIdUpdater(FunnelPagePure); const FunnelsDetails = withSiteIdUpdater(FunnelDetailsPure); const FunnelIssue = withSiteIdUpdater(FunnelIssueDetails); +const Multiview = withSiteIdUpdater(MultiviewPure) const withSiteId = routes.withSiteId; const METRICS_PATH = routes.metrics(); @@ -67,6 +66,7 @@ const DASHBOARD_METRIC_DETAILS_PATH = routes.dashboardMetricDetails(); // const WIDGET_PATAH = routes.dashboardMetric(); const SESSIONS_PATH = routes.sessions(); const ASSIST_PATH = routes.assist(); +const RECORDINGS_PATH = routes.recordings(); const ERRORS_PATH = routes.errors(); const ERROR_PATH = routes.error(); const FUNNEL_PATH = routes.funnels(); @@ -80,13 +80,15 @@ const FORGOT_PASSWORD = routes.forgotPassword(); const CLIENT_PATH = routes.client(); const ONBOARDING_PATH = routes.onboarding(); const ONBOARDING_REDIRECT_PATH = routes.onboarding(OB_DEFAULT_TAB); +const MULTIVIEW_PATH = routes.multiview(); +const MULTIVIEW_INDEX_PATH = routes.multiviewIndex(); @withStore @withRouter @connect( (state) => { const siteId = state.getIn(['site', 'siteId']); - const jwt = state.get('jwt'); + const jwt = state.getIn(['user', 'jwt']); const changePassword = state.getIn(['user', 'account', 'changePassword']); const userInfoLoading = state.getIn(['user', 'fetchUserInfoRequest', 'loading']); return { @@ -111,8 +113,6 @@ const ONBOARDING_REDIRECT_PATH = routes.onboarding(OB_DEFAULT_TAB); fetchTenants, setSessionPath, fetchSiteList, - fetchAnnouncements, - fetchAlerts, } ) class Router extends React.Component { @@ -170,8 +170,14 @@ class Router extends React.Component { render() { const { isLoggedIn, jwt, siteId, sites, loading, changePassword, location, existingTenant, onboarding, isEnterprise } = this.props; const siteIdList = sites.map(({ id }) => id).toJS(); - const hideHeader = (location.pathname && location.pathname.includes('/session/')) || location.pathname.includes('/assist/'); - const isPlayer = isRoute(SESSION_PATH, location.pathname) || isRoute(LIVE_SESSION_PATH, location.pathname); + const hideHeader = (location.pathname && location.pathname.includes('/session/')) + || location.pathname.includes('/assist/') + || location.pathname.includes('multiview'); + const isPlayer = isRoute(SESSION_PATH, location.pathname) + || isRoute(LIVE_SESSION_PATH, location.pathname) + || isRoute(MULTIVIEW_PATH, location.pathname) + || isRoute(MULTIVIEW_INDEX_PATH, location.pathname); + const redirectToOnboarding = !onboarding && localStorage.getItem(GLOBAL_HAS_NO_RECORDINGS) === 'true' return isLoggedIn ? ( @@ -194,6 +200,12 @@ class Router extends React.Component { state: tenantId, }); break; + case '/integrations/msteams': + client.post('integrations/msteams/add', { + code: location.search.split('=')[1], + state: tenantId, + }); + break; } return ; }} @@ -212,13 +224,16 @@ class Router extends React.Component { + + + - + } /> diff --git a/frontend/app/api_client.js b/frontend/app/api_client.js index 0e4699359..c9a041cc9 100644 --- a/frontend/app/api_client.js +++ b/frontend/app/api_client.js @@ -11,6 +11,7 @@ const siteIdRequiredPaths = [ '/metadata', '/integrations/sentry/events', '/integrations/slack/notify', + '/integrations/msteams/notify', '/assignments', '/integration/sources', '/issue_types', @@ -24,7 +25,7 @@ const siteIdRequiredPaths = [ '/heatmaps', '/custom_metrics', '/dashboards', - '/metrics', + '/cards', '/unprocessed', '/notes', // '/custom_metrics/sessions', @@ -57,7 +58,7 @@ export const clean = (obj, forbidenValues = [ undefined, '' ]) => { export default class APIClient { constructor() { - const jwt = store.getState().get('jwt'); + const jwt = store.getState().getIn(['user', 'jwt']); const siteId = store.getState().getIn([ 'site', 'siteId' ]); this.init = { headers: { @@ -87,14 +88,20 @@ export default class APIClient { if ( path !== '/targets_temp' && !path.includes('/metadata/session_search') && - !path.includes('/watchdogs/rules') && !path.includes('/assist/credentials') && !!this.siteId && siteIdRequiredPaths.some(sidPath => path.startsWith(sidPath)) ) { edp = `${ edp }/${ this.siteId }` } - return fetch(edp + path, this.init); + return fetch(edp + path, this.init) + .then((response) => { + if (response.ok) { + return response + } else { + return Promise.reject({ message: `! ${this.init.method} error on ${path}; ${response.status}`, response }); + } + }) } get(path, params, options) { diff --git a/frontend/app/api_middleware.js b/frontend/app/api_middleware.js index 1846a9dbc..d00074945 100644 --- a/frontend/app/api_middleware.js +++ b/frontend/app/api_middleware.js @@ -1,8 +1,8 @@ import logger from 'App/logger'; import APIClient from './api_client'; -import { UPDATE, DELETE } from './duck/jwt'; +import { FETCH_ACCOUNT, UPDATE_JWT } from './duck/user'; -export default (store) => (next) => (action) => { +export default () => (next) => (action) => { const { types, call, ...rest } = action; if (!call) { return next(action); @@ -14,7 +14,7 @@ export default (store) => (next) => (action) => { return call(client) .then(async (response) => { if (response.status === 403) { - next({ type: DELETE }); + next({ type: FETCH_ACCOUNT.FAILURE }); } if (!response.ok) { const text = await response.text(); @@ -30,20 +30,21 @@ export default (store) => (next) => (action) => { next({ type: SUCCESS, data, ...rest }); } if (jwt) { - next({ type: UPDATE, data: jwt }); + next({ type: UPDATE_JWT, data: jwt }); } }) - .catch((e) => { + .catch(async (e) => { + const data = await e.response?.json(); logger.error('Error during API request. ', e); - return next({ type: FAILURE, errors: parseError(e) }); + return next({ type: FAILURE, errors: data ? parseError(data.errors) : [] }); }); }; function parseError(e) { try { - return JSON.parse(e).errors || []; + return [...JSON.parse(e).errors] || []; } catch { - return e; + return Array.isArray(e) ? e : [e]; } } diff --git a/frontend/app/assets/integrations/teams.svg b/frontend/app/assets/integrations/teams.svg new file mode 100644 index 000000000..e93adb2b8 --- /dev/null +++ b/frontend/app/assets/integrations/teams.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/app/components/Alerts/AlertForm.js b/frontend/app/components/Alerts/AlertForm.js index 6604574e0..fcec66918 100644 --- a/frontend/app/components/Alerts/AlertForm.js +++ b/frontend/app/components/Alerts/AlertForm.js @@ -1,330 +1,382 @@ import React, { useEffect } from 'react'; -import { Button, Form, Input, SegmentSelection, Checkbox, Message, Link, Icon } from 'UI'; -import { alertMetrics as metrics } from 'App/constants'; +import { Button, Form, Input, SegmentSelection, Checkbox, Icon } from 'UI'; import { alertConditions as conditions } from 'App/constants'; -import { client, CLIENT_TABS } from 'App/routes'; -import { connect } from 'react-redux'; import stl from './alertForm.module.css'; import DropdownChips from './DropdownChips'; import { validateEmail } from 'App/validate'; import cn from 'classnames'; -import { fetchTriggerOptions } from 'Duck/alerts'; +import { useStore } from 'App/mstore' +import { observer } from 'mobx-react-lite' import Select from 'Shared/Select'; const thresholdOptions = [ - { label: '15 minutes', value: 15 }, - { label: '30 minutes', value: 30 }, - { label: '1 hour', value: 60 }, - { label: '2 hours', value: 120 }, - { label: '4 hours', value: 240 }, - { label: '1 day', value: 1440 }, + { label: '15 minutes', value: 15 }, + { label: '30 minutes', value: 30 }, + { label: '1 hour', value: 60 }, + { label: '2 hours', value: 120 }, + { label: '4 hours', value: 240 }, + { label: '1 day', value: 1440 }, ]; const changeOptions = [ - { label: 'change', value: 'change' }, - { label: '% change', value: 'percent' }, + { label: 'change', value: 'change' }, + { label: '% change', value: 'percent' }, ]; -const Circle = ({ text }) =>
{text}
; - -const Section = ({ index, title, description, content }) => ( -
-
- -
- {title} - {description &&
{description}
} -
-
- -
{content}
-
+const Circle = ({ text }) => ( +
+ {text} +
); -const integrationsRoute = client(CLIENT_TABS.INTEGRATIONS); +const Section = ({ index, title, description, content }) => ( +
+
+ +
+ {title} + {description &&
{description}
} +
+
-const AlertForm = (props) => { - const { - instance, - slackChannels, - webhooks, - loading, - onDelete, - deleting, - triggerOptions, - metricId, - style = { width: '580px', height: '100vh' }, - } = props; - const write = ({ target: { value, name } }) => props.edit({ [name]: value }); - const writeOption = (e, { name, value }) => props.edit({ [name]: value.value }); - const onChangeCheck = ({ target: { checked, name } }) => props.edit({ [name]: checked }); - // const onChangeOption = ({ checked, name }) => props.edit({ [ name ]: checked }) - // const onChangeCheck = (e) => { console.log(e) } +
{content}
+
+); - useEffect(() => { - props.fetchTriggerOptions(); - }, []); +function AlertForm(props) { + const { + slackChannels, + msTeamsChannels, + webhooks, + onDelete, + style = { width: '580px', height: '100vh' }, + } = props; + const { alertsStore } = useStore() + const { + triggerOptions, + loading, + } = alertsStore + const instance = alertsStore.instance + const deleting = loading - const writeQueryOption = (e, { name, value }) => { - const { query } = instance; - props.edit({ query: { ...query, [name]: value } }); - }; + const write = ({ target: { value, name } }) => alertsStore.edit({ [name]: value }); + const writeOption = (e, { name, value }) => alertsStore.edit({ [name]: value.value }); + const onChangeCheck = ({ target: { checked, name } }) => alertsStore.edit({ [name]: checked }); - const writeQuery = ({ target: { value, name } }) => { - const { query } = instance; - props.edit({ query: { ...query, [name]: value } }); - }; + useEffect(() => { + void alertsStore.fetchTriggerOptions(); + }, []); - const metric = instance && instance.query.left ? triggerOptions.find((i) => i.value === instance.query.left) : null; - const unit = metric ? metric.unit : ''; - const isThreshold = instance.detectionMethod === 'threshold'; + const writeQueryOption = (e, { name, value }) => { + const { query } = instance; + alertsStore.edit({ query: { ...query, [name]: value } }); + }; - return ( -
props.onSubmit(instance)} id="alert-form"> -
- -
-
- props.edit({ [name]: value })} - value={{ value: instance.detectionMethod }} - list={[ - { name: 'Threshold', value: 'threshold' }, - { name: 'Change', value: 'change' }, - ]} - /> -
- {isThreshold && 'Eg. Alert me if memory.avg is greater than 500mb over the past 4 hours.'} - {!isThreshold && - 'Eg. Alert me if % change of memory.avg is greater than 10% over the past 4 hours compared to the previous 4 hours.'} -
-
-
- } - /> + const writeQuery = ({ target: { value, name } }) => { + const { query } = instance; + alertsStore.edit({ query: { ...query, [name]: value } }); + }; -
+ const metric = + instance && instance.query.left + ? triggerOptions.find((i) => i.value === instance.query.left) + : null; + const unit = metric ? metric.unit : ''; + const isThreshold = instance.detectionMethod === 'threshold'; -
- {!isThreshold && ( -
- - i.value === instance.query.left)} - // onChange={ writeQueryOption } - onChange={({ value }) => writeQueryOption(null, { name: 'left', value: value.value })} - /> -
- -
- -
- - {'test'} - - )} - {!unit && ( - - )} -
-
- -
- - writeOption(null, { name: 'previousPeriod', value })} - /> -
- )} -
- } - /> - -
- -
-
- - - -
- - {instance.slack && ( -
- -
- props.edit({ slackInput: selected })} - /> -
-
- )} - - {instance.email && ( -
- -
- props.edit({ emailInput: selected })} - /> -
-
- )} - - {instance.webhook && ( -
- - props.edit({ webhookInput: selected })} - /> -
- )} -
- } - /> + return ( + props.onSubmit(instance)} + id="alert-form" + > +
+ +
+
+ alertsStore.edit({ [name]: value })} + value={{ value: instance.detectionMethod }} + list={[ + { name: 'Threshold', value: 'threshold' }, + { name: 'Change', value: 'change' }, + ]} + /> +
+ {isThreshold && + 'Eg. Alert me if memory.avg is greater than 500mb over the past 4 hours.'} + {!isThreshold && + 'Eg. Alert me if % change of memory.avg is greater than 10% over the past 4 hours compared to the previous 4 hours.'} +
+
+ } + /> -
-
- -
- +
+ +
+ {!isThreshold && ( +
+ + i.value === instance.query.left)} + // onChange={ writeQueryOption } + onChange={({ value }) => + writeQueryOption(null, { name: 'left', value: value.value }) + } + /> +
+ +
+ +
+ + {'test'} + + )} + {!unit && ( + + )}
+
+ +
+ + writeOption(null, { name: 'previousPeriod', value })} + /> +
+ )}
- - ); + } + /> + +
+ +
+
+ + + + +
+ + {instance.slack && ( +
+ +
+ alertsStore.edit({ slackInput: selected })} + /> +
+
+ )} + {instance.msteams && ( +
+ +
+ alertsStore.edit({ msteamsInput: selected })} + /> +
+
+ )} + + {instance.email && ( +
+ +
+ alertsStore.edit({ emailInput: selected })} + /> +
+
+ )} + + {instance.webhook && ( +
+ + alertsStore.edit({ webhookInput: selected })} + /> +
+ )} +
+ } + /> +
+ +
+
+ +
+ +
+
+ {instance.exists() && ( + + )} +
+
+ + ); }; -export default connect( - (state) => ({ - instance: state.getIn(['alerts', 'instance']), - triggerOptions: state.getIn(['alerts', 'triggerOptions']), - loading: state.getIn(['alerts', 'saveRequest', 'loading']), - deleting: state.getIn(['alerts', 'removeRequest', 'loading']), - }), - { fetchTriggerOptions } -)(AlertForm); +export default observer(AlertForm); diff --git a/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx b/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx index dc4c9db15..93999281a 100644 --- a/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx +++ b/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx @@ -1,44 +1,52 @@ import React, { useEffect, useState } from 'react'; -import { SlideModal, IconButton } from 'UI'; -import { init, edit, save, remove } from 'Duck/alerts'; -import { fetchList as fetchWebhooks } from 'Duck/webhook'; +import { SlideModal } from 'UI'; +import { useStore } from 'App/mstore' +import { observer } from 'mobx-react-lite' import AlertForm from '../AlertForm'; -import { connect } from 'react-redux'; -import { setShowAlerts } from 'Duck/dashboard'; -import { EMAIL, SLACK, WEBHOOK } from 'App/constants/schedule'; +import { SLACK, TEAMS, WEBHOOK } from 'App/constants/schedule'; import { confirm } from 'UI'; +interface Select { + label: string; + value: string | number +} + + interface Props { showModal?: boolean; metricId?: number; onClose?: () => void; - webhooks: any; - fetchWebhooks: Function; - save: Function; - remove: Function; - init: Function; - edit: Function; } function AlertFormModal(props: Props) { - const { metricId = null, showModal = false, webhooks } = props; + const { alertsStore, settingsStore } = useStore() + const { metricId = null, showModal = false } = props; const [showForm, setShowForm] = useState(false); - + const webhooks = settingsStore.webhooks useEffect(() => { - props.fetchWebhooks(); + settingsStore.fetchWebhooks(); }, []); - const slackChannels = webhooks - .filter((hook) => hook.type === SLACK) - .map(({ webhookId, name }) => ({ value: webhookId, text: name })) - .toJS(); - const hooks = webhooks - .filter((hook) => hook.type === WEBHOOK) - .map(({ webhookId, name }) => ({ value: webhookId, text: name })) - .toJS(); + + const slackChannels: Select[] = [] + const hooks: Select[] = [] + const msTeamsChannels: Select[] = [] + + webhooks.forEach((hook) => { + const option = { value: hook.webhookId, label: hook.name } + if (hook.type === SLACK) { + slackChannels.push(option) + } + if (hook.type === WEBHOOK) { + hooks.push(option) + } + if (hook.type === TEAMS) { + msTeamsChannels.push(option) + } + }) const saveAlert = (instance) => { const wasUpdating = instance.exists(); - props.save(instance).then(() => { + alertsStore.save(instance).then(() => { if (!wasUpdating) { toggleForm(null, false); } @@ -56,7 +64,7 @@ function AlertFormModal(props: Props) { confirmation: `Are you sure you want to permanently delete this alert?`, }) ) { - props.remove(instance.alertId).then(() => { + alertsStore.remove(instance.alertId).then(() => { toggleForm(null, false); }); } @@ -64,7 +72,7 @@ function AlertFormModal(props: Props) { const toggleForm = (instance, state) => { if (instance) { - props.init(instance); + alertsStore.init(instance); } return setShowForm(state ? state : !showForm); }; @@ -73,7 +81,7 @@ function AlertFormModal(props: Props) { - {'Create Alert'} + {'Create Alert'}
} isDisplayed={showModal} @@ -83,8 +91,9 @@ function AlertFormModal(props: Props) { showModal && ( ({ - webhooks: state.getIn(['webhooks', 'list']), - instance: state.getIn(['alerts', 'instance']), - }), - { init, edit, save, remove, fetchWebhooks, setShowAlerts } -)(AlertFormModal); +export default observer(AlertFormModal); diff --git a/frontend/app/components/Alerts/AlertItem.js b/frontend/app/components/Alerts/AlertItem.js deleted file mode 100644 index 9dbb204b8..000000000 --- a/frontend/app/components/Alerts/AlertItem.js +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react' -import cn from 'classnames'; -import stl from './alertItem.module.css'; -import AlertTypeLabel from './AlertTypeLabel'; - -const AlertItem = props => { - const { alert, onEdit, active } = props; - - const getThreshold = threshold => { - if (threshold === 15) return '15 Minutes'; - if (threshold === 30) return '30 Minutes'; - if (threshold === 60) return '1 Hour'; - if (threshold === 120) return '2 Hours'; - if (threshold === 240) return '4 Hours'; - if (threshold === 1440) return '1 Day'; - } - - const getNotifyChannel = alert => { - let str = ''; - if (alert.slack) - str = 'Slack'; - if (alert.email) - str += (str === '' ? '' : ' and ')+ 'Email'; - if (alert.webhool) - str += (str === '' ? '' : ' and ')+ 'Webhook'; - if (str === '') - return 'OpenReplay'; - - return str; - } - - const isThreshold = alert.detectionMethod === 'threshold'; - - return ( -
- -
{alert.name}
-
- {alert.detectionMethod === 'threshold' && ( -
When {alert.metric.text} is {alert.condition.text} {alert.query.right} {alert.metric.unit} over the past {getThreshold(alert.currentPeriod)}, notify me on {getNotifyChannel(alert)}.
- )} - - {alert.detectionMethod === 'change' && ( -
When the {alert.options.change} of {alert.metric.text} is {alert.condition.text} {alert.query.right} {alert.metric.unit} over the past {getThreshold(alert.currentPeriod)} compared to the previous {getThreshold(alert.previousPeriod)}, notify me on {getNotifyChannel(alert)}.
- )} -
-
- ) -} - -export default AlertItem diff --git a/frontend/app/components/Alerts/AlertTypeLabel.js b/frontend/app/components/Alerts/AlertTypeLabel.js deleted file mode 100644 index f867dfdc4..000000000 --- a/frontend/app/components/Alerts/AlertTypeLabel.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' -import cn from 'classnames' -import stl from './alertTypeLabel.module.css' - -function AlertTypeLabel({ filterKey, type = '' }) { - return ( -
- { type } -
- ) -} - -export default AlertTypeLabel diff --git a/frontend/app/components/Alerts/Alerts.js b/frontend/app/components/Alerts/Alerts.js index ed825abaf..e69de29bb 100644 --- a/frontend/app/components/Alerts/Alerts.js +++ b/frontend/app/components/Alerts/Alerts.js @@ -1,109 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { toast } from 'react-toastify'; -import AlertsList from './AlertsList'; -import { SlideModal, IconButton } from 'UI'; -import { init, edit, save, remove } from 'Duck/alerts'; -import { fetchList as fetchWebhooks } from 'Duck/webhook'; -import AlertForm from './AlertForm'; -import { connect } from 'react-redux'; -import { setShowAlerts } from 'Duck/dashboard'; -import { EMAIL, SLACK, WEBHOOK } from 'App/constants/schedule'; -import { confirm } from 'UI'; - -const Alerts = (props) => { - const { webhooks, setShowAlerts } = props; - const [showForm, setShowForm] = useState(false); - - useEffect(() => { - props.fetchWebhooks(); - }, []); - - const slackChannels = webhooks - .filter((hook) => hook.type === SLACK) - .map(({ webhookId, name }) => ({ value: webhookId, label: name })) - .toJS(); - const hooks = webhooks - .filter((hook) => hook.type === WEBHOOK) - .map(({ webhookId, name }) => ({ value: webhookId, label: name })) - .toJS(); - - const saveAlert = (instance) => { - const wasUpdating = instance.exists(); - props.save(instance).then(() => { - if (!wasUpdating) { - toast.success('New alert saved'); - toggleForm(null, false); - } else { - toast.success('Alert updated'); - } - }); - }; - - const onDelete = async (instance) => { - if ( - await confirm({ - header: 'Confirm', - confirmButton: 'Yes, delete', - confirmation: `Are you sure you want to permanently delete this alert?`, - }) - ) { - props.remove(instance.alertId).then(() => { - toggleForm(null, false); - }); - } - }; - - const toggleForm = (instance, state) => { - if (instance) { - props.init(instance); - } - return setShowForm(state ? state : !showForm); - }; - - return ( -
- - {'Alerts'} - toggleForm({}, true)} /> -
- } - isDisplayed={true} - onClose={() => { - toggleForm({}, false); - setShowAlerts(false); - }} - size="small" - content={ - { - toggleForm(alert, true); - }} - onClickCreate={() => toggleForm({}, true)} - /> - } - detailContent={ - showForm && ( - toggleForm({}, false)} - onDelete={onDelete} - /> - ) - } - /> -
- ); -}; - -export default connect( - (state) => ({ - webhooks: state.getIn(['webhooks', 'list']), - instance: state.getIn(['alerts', 'instance']), - }), - { init, edit, save, remove, fetchWebhooks, setShowAlerts } -)(Alerts); diff --git a/frontend/app/components/Alerts/AlertsList.js b/frontend/app/components/Alerts/AlertsList.js deleted file mode 100644 index 5a874e0fa..000000000 --- a/frontend/app/components/Alerts/AlertsList.js +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Loader, NoContent, Input, Button } from 'UI'; -import AlertItem from './AlertItem'; -import { fetchList, init } from 'Duck/alerts'; -import { connect } from 'react-redux'; -import { getRE } from 'App/utils'; - -const AlertsList = (props) => { - const { loading, list, instance, onEdit } = props; - const [query, setQuery] = useState(''); - - useEffect(() => { - props.fetchList(); - }, []); - - const filterRE = getRE(query, 'i'); - const _filteredList = list.filter(({ name, query: { left } }) => filterRE.test(name) || filterRE.test(left)); - - return ( -
-
- setQuery(value)} /> -
- - -
Alerts helps your team stay up to date with the activity on your app.
- -
- } - size="small" - show={list.size === 0} - > -
- {_filteredList.map((a) => ( -
- onEdit(a.toData())} /> -
- ))} -
- - -
- ); -}; - -export default connect( - (state) => ({ - list: state.getIn(['alerts', 'list']).sort((a, b) => b.createdAt - a.createdAt), - instance: state.getIn(['alerts', 'instance']), - loading: state.getIn(['alerts', 'loading']), - }), - { fetchList, init } -)(AlertsList); diff --git a/frontend/app/components/Alerts/Notifications/ListItem/ListItem.js b/frontend/app/components/Alerts/Notifications/ListItem/ListItem.js deleted file mode 100644 index 994ffcfb4..000000000 --- a/frontend/app/components/Alerts/Notifications/ListItem/ListItem.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { Button } from 'UI'; -import stl from './listItem.module.css'; -import cn from 'classnames'; -import AlertTypeLabel from '../../AlertTypeLabel'; - -const ListItem = ({ alert, onClear, loading, onNavigate }) => { - return ( -
-
-
{alert.createdAt && alert.createdAt.toFormat('LLL dd, yyyy, hh:mm a')}
-
- -
-
- - -
-

- {alert.title} -

-
{alert.description}
-
-
- ) -} - -export default ListItem diff --git a/frontend/app/components/Alerts/Notifications/ListItem/index.js b/frontend/app/components/Alerts/Notifications/ListItem/index.js deleted file mode 100644 index 741aed270..000000000 --- a/frontend/app/components/Alerts/Notifications/ListItem/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ListItem'; diff --git a/frontend/app/components/Alerts/Notifications/ListItem/listItem.module.css b/frontend/app/components/Alerts/Notifications/ListItem/listItem.module.css deleted file mode 100644 index 8472a777c..000000000 --- a/frontend/app/components/Alerts/Notifications/ListItem/listItem.module.css +++ /dev/null @@ -1,7 +0,0 @@ -.wrapper { - padding: 15px; -} - -.viewed { - background-color: $gray-lightest; -} \ No newline at end of file diff --git a/frontend/app/components/Alerts/Notifications/Notifications.tsx b/frontend/app/components/Alerts/Notifications/Notifications.tsx index 05bcea06b..2bc78384f 100644 --- a/frontend/app/components/Alerts/Notifications/Notifications.tsx +++ b/frontend/app/components/Alerts/Notifications/Notifications.tsx @@ -1,34 +1,27 @@ import React, { useEffect } from 'react'; import stl from './notifications.module.css'; -import { connect } from 'react-redux'; import { Icon, Tooltip } from 'UI'; -import { fetchList, setViewed, clearAll } from 'Duck/notifications'; -import { setLastRead } from 'Duck/announcements'; import { useModal } from 'App/components/Modal'; import AlertTriggersModal from 'Shared/AlertTriggersModal'; import { useStore } from 'App/mstore'; -import { useObserver } from 'mobx-react-lite'; +import { observer } from 'mobx-react-lite'; const AUTOREFRESH_INTERVAL = 5 * 60 * 1000; -interface Props { - notifications: any; - fetchList: any; -} -function Notifications(props: Props) { +function Notifications() { const { showModal } = useModal(); const { notificationStore } = useStore(); - const count = useObserver(() => notificationStore.notificationsCount); + const count = notificationStore.notificationsCount; useEffect(() => { const interval = setInterval(() => { - notificationStore.fetchNotificationsCount(); + void notificationStore.fetchNotificationsCount(); }, AUTOREFRESH_INTERVAL); return () => clearInterval(interval); }, []); - return useObserver(() => ( + return (
- )); + ); } -export default connect( - (state: any) => ({ - notifications: state.getIn(['notifications', 'list']), - }), - { fetchList, setLastRead, setViewed, clearAll } -)(Notifications); +export default observer(Notifications) \ No newline at end of file diff --git a/frontend/app/components/Alerts/alertItem.module.css b/frontend/app/components/Alerts/alertItem.module.css deleted file mode 100644 index fe26fe57e..000000000 --- a/frontend/app/components/Alerts/alertItem.module.css +++ /dev/null @@ -1,9 +0,0 @@ -.wrapper { - &:hover { - background-color: $active-blue; - } - - &.active { - background-color: $active-blue; - } -} diff --git a/frontend/app/components/Alerts/alertTypeLabel.module.css b/frontend/app/components/Alerts/alertTypeLabel.module.css deleted file mode 100644 index 62ed31460..000000000 --- a/frontend/app/components/Alerts/alertTypeLabel.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.wrapper { - background-color: white; - color: $gray-dark; - border: solid thin $gray-light; -} - -.alert { - background: #C3E9EA; - color: #32888C; - border: none; -} diff --git a/frontend/app/components/Alerts/alerts.stories.js b/frontend/app/components/Alerts/alerts.stories.js deleted file mode 100644 index 781a7e734..000000000 --- a/frontend/app/components/Alerts/alerts.stories.js +++ /dev/null @@ -1,130 +0,0 @@ -import { storiesOf } from '@storybook/react'; -import { List } from 'immutable'; -import Alert from 'Types/alert'; -import Notification from 'Types/notification'; -import Alerts from '.'; -import Notifications from './Notifications'; -import AlertsList from './AlertsList'; -import AlertForm from './AlertForm'; - -const list = [ - { - "alertId": 2, - "projectId": 1, - "name": "new alert", - "description": null, - "active": true, - "threshold": 240, - "detectionMethod": "threshold", - "query": { - "left": "avgPageLoad", - "right": 1.0, - "operator": ">=" - }, - "createdAt": 1591893324078, - "options": { - "message": [ - { - "type": "slack", - "value": "51" - }, - ], - "LastNotification": 1592929583000, - "renotifyInterval": 120 - } - }, - { - "alertId": 14, - "projectId": 1, - "name": "alert 19.06", - "description": null, - "active": true, - "threshold": 30, - "detectionMethod": "threshold", - "query": { - "left": "avgPageLoad", - "right": 3000.0, - "operator": ">=" - }, - "createdAt": 1592579750935, - "options": { - "message": [ - { - "type": "slack", - "value": "51" - } - ], - "renotifyInterval": 120 - } - }, - { - "alertId": 15, - "projectId": 1, - "name": "notify every 60min", - "description": null, - "active": true, - "threshold": 30, - "detectionMethod": "threshold", - "query": { - "left": "avgPageLoad", - "right": 1.0, - "operator": ">=" - }, - "createdAt": 1592848779604, - "options": { - "message": [ - { - "type": "slack", - "value": "51" - }, - ], - "LastNotification": 1599135058000, - "renotifyInterval": 60 - } - }, - { - "alertId": 21, - "projectId": 1, - "name": "always notify", - "description": null, - "active": true, - "threshold": 30, - "detectionMethod": "threshold", - "query": { - "left": "avgPageLoad", - "right": 1.0, - "operator": ">=" - }, - "createdAt": 1592849011350, - "options": { - "message": [ - { - "type": "slack", - "value": "51" - } - ], - "LastNotification": 1599135058000, - "renotifyInterval": 10 - } - } -] - -const notifications = List([ - { title: 'test', type: 'change', createdAt: 1591893324078, description: 'Lorem ipusm'}, - { title: 'test', type: 'threshold', createdAt: 1591893324078, description: 'Lorem ipusm'}, - { title: 'test', type: 'threshold', createdAt: 1591893324078, description: 'Lorem ipusm'}, - { title: 'test', type: 'threshold', createdAt: 1591893324078, description: 'Lorem ipusm'}, -]) -storiesOf('Alerts', module) - .add('Alerts', () => ( - - )) - .add('List', () => ( - - )) - .add('AlertForm', () => ( - - )) - .add('AlertNotifications', () => ( - - )) diff --git a/frontend/app/components/Alerts/index.js b/frontend/app/components/Alerts/index.js deleted file mode 100644 index 4f6a6f772..000000000 --- a/frontend/app/components/Alerts/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Alerts' \ No newline at end of file diff --git a/frontend/app/components/Announcements/Announcements.js b/frontend/app/components/Announcements/Announcements.js deleted file mode 100644 index 55306e643..000000000 --- a/frontend/app/components/Announcements/Announcements.js +++ /dev/null @@ -1,99 +0,0 @@ -import React from 'react'; -import stl from './announcements.module.css'; -import ListItem from './ListItem'; -import { connect } from 'react-redux'; -import { SlideModal, Icon, NoContent, Tooltip } from 'UI'; -import { fetchList, setLastRead } from 'Duck/announcements'; -import withToggle from 'Components/hocs/withToggle'; -import { withRouter } from 'react-router-dom'; -import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; - -@withToggle('visible', 'toggleVisisble') -@withRouter -class Announcements extends React.Component { - - navigateToUrl = url => { - if (url) { - if (url.startsWith(window.env.ORIGIN || window.location.origin)) { - const { history } = this.props; - var path = new URL(url).pathname - if (path.includes('/metrics')) { - const { siteId, sites } = this.props; - const activeSite = sites.find(s => s.id == siteId); - history.push(`/${activeSite.id + path}`); - } else { - history.push(path) - } - } else { - window.open(url, "_blank") - } - this.toggleModal() - } - } - - toggleModal = () => { - if (!this.props.visible) { - const { setLastRead, fetchList } = this.props; - fetchList().then(() => { setTimeout(() => { setLastRead() }, 5000); }); - } - this.props.toggleVisisble(!this.props.visible); - } - - render() { - const { announcements, visible, loading } = this.props; - const unReadNotificationsCount = announcements.filter(({viewed}) => !viewed).size - - return ( -
- -
-
- { unReadNotificationsCount } -
- -
-
- - - - -
No announcements to show.
-
- } - size="small" - show={ !loading && announcements.size === 0 } - > - { - announcements.map(item => ( - - )) - } - - - } - /> - - ); - } -} - -export default connect(state => ({ - announcements: state.getIn(['announcements', 'list']), - loading: state.getIn(['announcements', 'fetchList', 'loading']), - siteId: state.getIn([ 'site', 'siteId' ]), - sites: state.getIn([ 'site', 'list' ]), -}), { fetchList, setLastRead })(Announcements); \ No newline at end of file diff --git a/frontend/app/components/Announcements/ListItem/ListItem.js b/frontend/app/components/Announcements/ListItem/ListItem.js deleted file mode 100644 index dd777c719..000000000 --- a/frontend/app/components/Announcements/ListItem/ListItem.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { Button, Label } from 'UI'; -import stl from './listItem.module.css'; - -const ListItem = ({ announcement, onButtonClick }) => { - return ( -
-
-
{announcement.createdAt && announcement.createdAt.toFormat('LLL dd, yyyy')}
- -
- {announcement.imageUrl && - - } -
-

{announcement.title}

-
{announcement.description}
- {announcement.buttonUrl && - - } -
-
- ) -} - -export default ListItem diff --git a/frontend/app/components/Announcements/ListItem/index.js b/frontend/app/components/Announcements/ListItem/index.js deleted file mode 100644 index 741aed270..000000000 --- a/frontend/app/components/Announcements/ListItem/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ListItem'; diff --git a/frontend/app/components/Announcements/ListItem/listItem.module.css b/frontend/app/components/Announcements/ListItem/listItem.module.css deleted file mode 100644 index 5bc3a44c8..000000000 --- a/frontend/app/components/Announcements/ListItem/listItem.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.wrapper { - background-color: white; - margin-bottom: 20px; - padding: 15px; -} \ No newline at end of file diff --git a/frontend/app/components/Announcements/announcements.module.css b/frontend/app/components/Announcements/announcements.module.css deleted file mode 100644 index 5a3704af2..000000000 --- a/frontend/app/components/Announcements/announcements.module.css +++ /dev/null @@ -1,39 +0,0 @@ -.wrapper { - position: relative; -} - -.button { - position: relative; - cursor: pointer; - display: flex; - align-items: center; - padding: 0 15px; - height: 50px; - transition: all 0.3s; - - &:hover { - background-color: $gray-lightest; - transition: all 0.2s; - } - - &[data-active=true] { - background-color: $gray-lightest; - } -} - -.counter { - position: absolute; - top: 8px; - left: 24px; - background-color: #CC0000; - color: white; - font-size: 9px; - font-weight: 300; - min-width: 16px; - height: 16px; - border-radius: 8px; - display: flex; - align-items: center; - justify-content: center; - padding: 3px; -} diff --git a/frontend/app/components/Announcements/index.js b/frontend/app/components/Announcements/index.js deleted file mode 100644 index faeffcfcd..000000000 --- a/frontend/app/components/Announcements/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Announcements'; \ No newline at end of file diff --git a/frontend/app/components/Assist/Assist.tsx b/frontend/app/components/Assist/Assist.tsx index 3ef99c573..b58df1352 100644 --- a/frontend/app/components/Assist/Assist.tsx +++ b/frontend/app/components/Assist/Assist.tsx @@ -1,26 +1,70 @@ import React from 'react'; -import LiveSessionList from 'Shared/LiveSessionList'; -import LiveSessionSearch from 'Shared/LiveSessionSearch'; -import cn from 'classnames' +import { withRouter, RouteComponentProps } from 'react-router-dom'; import withPageTitle from 'HOCs/withPageTitle'; -import withPermissions from 'HOCs/withPermissions' -// import SessionSearch from '../shared/SessionSearch'; -// import MainSearchBar from '../shared/MainSearchBar'; -import AssistSearchField from './AssistSearchField'; +import withPermissions from 'HOCs/withPermissions'; +import AssistRouter from './AssistRouter'; +import { SideMenuitem } from 'UI'; +import { withSiteId, assist, recordings } from 'App/routes'; +import { connect } from 'react-redux'; +import { ENTERPRISE_REQUEIRED } from 'App/constants'; -function Assist() { - return ( -
-
-
- - -
- -
-
-
- ) +interface Props extends RouteComponentProps { + siteId: string; + history: any; + isEnterprise: boolean; } -export default withPageTitle("Assist - OpenReplay")(withPermissions(['ASSIST_LIVE'])(Assist)); +function Assist(props: Props) { + const { history, siteId, isEnterprise } = props; + const isAssist = history.location.pathname.includes('assist'); + const isRecords = history.location.pathname.includes('recordings'); + + const redirect = (path: string) => { + history.push(withSiteId(path, siteId)); + }; + // if (isEnterprise) { + return ( +
+
+
+ redirect(assist())} + /> + redirect(recordings())} + disabled={!isEnterprise} + tooltipTitle={ENTERPRISE_REQUEIRED} + /> +
+
+ +
+
+
+ ); + // } + + // return ( + //
+ // + //
+ // ) +} + +const Cont = connect((state: any) => ({ + isEnterprise: + state.getIn(['user', 'account', 'edition']) === 'ee' || + state.getIn(['user', 'authDetails', 'edition']) === 'ee', +}))(Assist); + +export default withPageTitle('Assist - OpenReplay')( + withPermissions(['ASSIST_LIVE'])(withRouter(Cont)) +); diff --git a/frontend/app/components/Assist/AssistRouter.tsx b/frontend/app/components/Assist/AssistRouter.tsx new file mode 100644 index 000000000..941362dbf --- /dev/null +++ b/frontend/app/components/Assist/AssistRouter.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Switch, Route } from 'react-router'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; + +import { + assist, + recordings, + withSiteId, +} from 'App/routes'; +import AssistView from './AssistView' +import Recordings from './RecordingsList/Recordings' + +interface Props extends RouteComponentProps { + match: any; +} + +function AssistRouter(props: Props) { + const { + match: { + params: { siteId }, + }, + } = props; + + return ( +
+ + + + + + + + + +
+ ); +} + +export default withRouter(AssistRouter); diff --git a/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx b/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx index a8c04a98a..df53e4404 100644 --- a/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx +++ b/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx @@ -16,7 +16,7 @@ function AssistSearchField(props: Props) { const hasEvents = props.appliedFilter.filters.filter((i: any) => i.isEvent).size > 0; const hasFilters = props.appliedFilter.filters.filter((i: any) => !i.isEvent).size > 0; return ( -
+
diff --git a/frontend/app/components/Assist/AssistView.tsx b/frontend/app/components/Assist/AssistView.tsx new file mode 100644 index 000000000..36e7b7137 --- /dev/null +++ b/frontend/app/components/Assist/AssistView.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import LiveSessionList from 'Shared/LiveSessionList'; +import LiveSessionSearch from 'Shared/LiveSessionSearch'; +import AssistSearchField from './AssistSearchField'; + +function AssistView() { + return ( +
+ + +
+ +
+ ) +} + +export default AssistView; diff --git a/frontend/app/components/Assist/ChatWindow/ChatWindow.tsx b/frontend/app/components/Assist/ChatWindow/ChatWindow.tsx index 167db8281..29e4826cc 100644 --- a/frontend/app/components/Assist/ChatWindow/ChatWindow.tsx +++ b/frontend/app/components/Assist/ChatWindow/ChatWindow.tsx @@ -6,7 +6,7 @@ import stl from './chatWindow.module.css'; import ChatControls from '../ChatControls/ChatControls'; import Draggable from 'react-draggable'; import type { LocalStream } from 'Player'; -import { toggleVideoLocalStream } from 'Player' +import { PlayerContext } from 'App/components/Session/playerContext'; export interface Props { incomeStream: MediaStream[] | null; @@ -17,6 +17,10 @@ export interface Props { } function ChatWindow({ userId, incomeStream, localStream, endCall, isPrestart }: Props) { + const { player } = React.useContext(PlayerContext) + + const toggleVideoLocalStream = player.assistManager.toggleVideoLocalStream; + const [localVideoEnabled, setLocalVideoEnabled] = useState(false); const [anyRemoteEnabled, setRemoteEnabled] = useState(false); diff --git a/frontend/app/components/Assist/RecordingsList/EditRecordingModal.tsx b/frontend/app/components/Assist/RecordingsList/EditRecordingModal.tsx new file mode 100644 index 000000000..7be5b1dca --- /dev/null +++ b/frontend/app/components/Assist/RecordingsList/EditRecordingModal.tsx @@ -0,0 +1,73 @@ +import { useObserver } from 'mobx-react-lite'; +import React from 'react'; +import { Button, Modal, Form, Icon, Input } from 'UI'; + +interface Props { + show: boolean; + title: string; + closeHandler?: () => void; + onSave: (title: string) => void; +} +function EditRecordingModal(props: Props) { + const { show, closeHandler, title, onSave } = props; + const [text, setText] = React.useState(title) + + React.useEffect(() => { + const handleEsc = (e: any) => e.key === 'Escape' && closeHandler?.() + document.addEventListener("keydown", handleEsc, false); + return () => { + document.removeEventListener("keydown", handleEsc, false); + } + }, []) + + const write = ({ target: { value, name } }: any) => setText(value) + + const save = () => { + onSave(text) + } + return useObserver(() => ( + + +
{ 'Rename' }
+
+ +
+
+ + +
+ + + + +
+
+ +
+ + +
+
+
+ )); +} + +export default EditRecordingModal; diff --git a/frontend/app/components/Assist/RecordingsList/Recordings.tsx b/frontend/app/components/Assist/RecordingsList/Recordings.tsx new file mode 100644 index 000000000..c03b703f3 --- /dev/null +++ b/frontend/app/components/Assist/RecordingsList/Recordings.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { PageTitle } from 'UI'; +import Select from 'Shared/Select'; +import RecordingsSearch from './RecordingsSearch'; +import RecordingsList from './RecordingsList'; +import { useStore } from 'App/mstore'; +import { connect } from 'react-redux'; + +function Recordings({ userId }: { userId: string }) { + const { recordingsStore } = useStore(); + + const recordingsOwner = [ + { value: '0', label: 'All Recordings' }, + { value: userId, label: 'My Recordings' }, + ]; + + return ( +
+
+
+ +
+
+ +
+ ); +} + +export default observer(RecordingsSearch); diff --git a/frontend/app/components/Assist/RecordingsList/RecordsListItem.tsx b/frontend/app/components/Assist/RecordingsList/RecordsListItem.tsx new file mode 100644 index 000000000..3d5f70cc6 --- /dev/null +++ b/frontend/app/components/Assist/RecordingsList/RecordsListItem.tsx @@ -0,0 +1,116 @@ +import React from 'react'; +import { Icon, ItemMenu } from 'UI'; +import { durationFromMs, formatTimeOrDate } from 'App/date'; +import { IRecord } from 'App/services/RecordingsService'; +import { useStore } from 'App/mstore'; +import { toast } from 'react-toastify'; +import cn from 'classnames'; +import EditRecordingModal from './EditRecordingModal'; + +interface Props { + record: IRecord; +} + +function RecordsListItem(props: Props) { + const { record } = props; + const { recordingsStore, settingsStore } = useStore(); + const { timezone } = settingsStore.sessionSettings; + const [isEdit, setEdit] = React.useState(false); + const [recordingTitle, setRecordingTitle] = React.useState(record.name); + const inputRef = React.useRef(null); + + const onRecordClick = () => { + recordingsStore.fetchRecordingUrl(record.recordId).then((url) => { + window.open(url, '_blank'); + }); + }; + + React.useEffect(() => { + if (inputRef.current) { + inputRef.current.style.width = `${record.name.length}ch`; + } + }, [isEdit, inputRef.current]); + + const onDelete = () => { + recordingsStore.deleteRecording(record.recordId).then(() => { + recordingsStore.setRecordings( + recordingsStore.recordings.filter((rec) => rec.recordId !== record.recordId) + ); + toast.success('Recording deleted'); + }); + }; + + const menuItems = [ + { icon: 'pencil', text: 'Rename', onClick: () => setEdit(true) }, + { + icon: 'trash', + text: 'Delete', + onClick: onDelete, + }, + ]; + + const onSave = (title: string) => { + recordingsStore + .updateRecordingName(record.recordId, title) + .then(() => { + setRecordingTitle(title); + toast.success('Recording name updated'); + }) + .catch(() => toast.error("Couldn't update recording name")); + setEdit(false); + }; + + const onCancel = () => setEdit(false); + + return ( +
+ +
+
+
+
+ +
+
+
{recordingTitle}
+
{durationFromMs(record.duration)}
+
+
+
+
+
+
{record.createdBy}
+
+ {formatTimeOrDate(record.createdAt, timezone, true)} +
+
+
+
+
+ + +
Play Video
+
+
+ +
+
+
+
+ ); +} + +export default RecordsListItem; diff --git a/frontend/app/components/Assist/RequestingWindow/RequestingWindow.tsx b/frontend/app/components/Assist/RequestingWindow/RequestingWindow.tsx index 6d702acdb..212fea91c 100644 --- a/frontend/app/components/Assist/RequestingWindow/RequestingWindow.tsx +++ b/frontend/app/components/Assist/RequestingWindow/RequestingWindow.tsx @@ -2,45 +2,78 @@ import React from 'react'; import { INDEXES } from 'App/constants/zindex'; import { connect } from 'react-redux'; import { Button, Loader, Icon } from 'UI'; -import { initiateCallEnd, releaseRemoteControl } from 'Player'; +import { PlayerContext } from 'App/components/Session/playerContext'; interface Props { userDisplayName: string; - type: WindowType; + getWindowType: () => WindowType | null; } export enum WindowType { Call, Control, + Record, +} + +enum Actions { + CallEnd, + ControlEnd, + RecordingEnd, } const WIN_VARIANTS = { [WindowType.Call]: { text: 'to accept the call', icon: 'call' as const, - action: initiateCallEnd, + action: Actions.CallEnd, + iconColor: 'teal', }, [WindowType.Control]: { text: 'to accept remote control request', icon: 'remote-control' as const, - action: releaseRemoteControl, + action: Actions.ControlEnd, + iconColor: 'teal', }, + [WindowType.Record]: { + text: 'to accept recording request', + icon: 'record-circle' as const, + iconColor: 'red', + action: Actions.RecordingEnd, + } }; -function RequestingWindow({ userDisplayName, type }: Props) { +function RequestingWindow({ userDisplayName, getWindowType }: Props) { + const windowType = getWindowType() + if (!windowType) return; + const { player } = React.useContext(PlayerContext) + + + const { + assistManager: { + initiateCallEnd, + releaseRemoteControl, + stopRecording, + } + } = player + + const actions = { + [Actions.CallEnd]: initiateCallEnd, + [Actions.ControlEnd]: releaseRemoteControl, + [Actions.RecordingEnd]: stopRecording, + } return (
- +
Waiting for {userDisplayName}
- {WIN_VARIANTS[type].text} + {WIN_VARIANTS[windowType].text} -
@@ -48,6 +81,6 @@ function RequestingWindow({ userDisplayName, type }: Props) { ); } -export default connect((state) => ({ - userDisplayName: state.getIn(['sessions', 'current', 'userDisplayName']), +export default connect((state: any) => ({ + userDisplayName: state.getIn(['sessions', 'current']).userDisplayName, }))(RequestingWindow); diff --git a/frontend/app/components/Assist/ScreenSharing/ScreenSharing.tsx b/frontend/app/components/Assist/ScreenSharing/ScreenSharing.tsx deleted file mode 100644 index 35d67f91b..000000000 --- a/frontend/app/components/Assist/ScreenSharing/ScreenSharing.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react' -import { Button } from 'UI' - -function ScreenSharing() { - const videoRef = React.createRef() - - function handleSuccess(stream) { - // startButton.disabled = true; - //videoRef.current?.srcObject = stream; - // @ts-ignore - window.stream = stream; // make variable available to browser console - - stream.getVideoTracks()[0].addEventListener('ended', () => { - console.log('The user has ended sharing the screen'); - }); - } - - function handleError(error) { - console.log(`getDisplayMedia error: ${error.name}`, error); - } - - const startScreenSharing = () => { - // @ts-ignore - navigator.mediaDevices.getDisplayMedia({video: true}) - .then(handleSuccess, handleError); - } - - const stopScreenSharing = () => { - // @ts-ignore - window.stream.stop() - console.log('Stop screen sharing') - } - - return ( -
- -
- - -
-
- ) -} - -export default ScreenSharing diff --git a/frontend/app/components/Assist/ScreenSharing/index.js b/frontend/app/components/Assist/ScreenSharing/index.js deleted file mode 100644 index f2bc82ab6..000000000 --- a/frontend/app/components/Assist/ScreenSharing/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ScreenSharing' \ No newline at end of file diff --git a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx index e377cd3ba..18c717cf4 100644 --- a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx +++ b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx @@ -2,16 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Button, Tooltip } from 'UI'; import { connect } from 'react-redux'; import cn from 'classnames'; -import { toggleChatWindow } from 'Duck/sessions'; -import { connectPlayer } from 'Player'; import ChatWindow from '../../ChatWindow'; -import { - callPeer, - setCallArgs, - requestReleaseRemoteControl, - toggleAnnotation, - toggleUserName, -} from 'Player'; import { CallingState, ConnectionStatus, @@ -19,9 +10,12 @@ import { RequestLocalStream, } from 'Player'; import type { LocalStream } from 'Player'; +import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; import { toast } from 'react-toastify'; import { confirm } from 'UI'; import stl from './AassistActions.module.css'; +import ScreenRecorder from 'App/components/Session_/ScreenRecorder/ScreenRecorder'; function onReject() { toast.info(`Call was rejected.`); @@ -34,31 +28,41 @@ function onError(e: any) { interface Props { userId: string; - calling: CallingState; - annotating: boolean; - peerConnectionStatus: ConnectionStatus; - remoteControlStatus: RemoteControlStatus; hasPermission: boolean; isEnterprise: boolean; isCallActive: boolean; agentIds: string[]; - livePlay: boolean; userDisplayName: string; } function AssistActions({ userId, - calling, - annotating, - peerConnectionStatus, - remoteControlStatus, hasPermission, isEnterprise, isCallActive, agentIds, - livePlay, userDisplayName, }: Props) { + // @ts-ignore ??? + const { player, store } = React.useContext(PlayerContext) + + const { + assistManager: { + call: callPeer, + setCallArgs, + requestReleaseRemoteControl, + toggleAnnotation, + }, + toggleUserName, + } = player + const { + calling, + annotating, + peerConnectionStatus, + remoteControl: remoteControlStatus, + livePlay, + } = store.get() + const [isPrestart, setPrestart] = useState(false); const [incomeStream, setIncomeStream] = useState([]); const [localStream, setLocalStream] = useState(null); @@ -113,6 +117,7 @@ function AssistActions({ const addIncomeStream = (stream: MediaStream) => { setIncomeStream((oldState) => { + if (oldState === null) return [stream] if (!oldState.find((existingStream) => existingStream.id === stream.id)) { return [...oldState, stream]; } @@ -175,6 +180,10 @@ function AssistActions({ )} + {/* @ts-ignore wtf? */} + +
+ {/* @ts-ignore */}
{ + (state: any) => { const permissions = state.getIn(['user', 'account', 'permissions']) || []; return { hasPermission: permissions.includes('ASSIST_CALL'), isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee', - userDisplayName: state.getIn(['sessions', 'current', 'userDisplayName']), + userDisplayName: state.getIn(['sessions', 'current']).userDisplayName, }; - }, - { toggleChatWindow } + } ); export default con( - connectPlayer((state) => ({ - calling: state.calling, - annotating: state.annotating, - remoteControlStatus: state.remoteControl, - peerConnectionStatus: state.peerConnectionStatus, - livePlay: state.livePlay, - }))(AssistActions) + observer(AssistActions) ); diff --git a/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx b/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx index 2fd09105b..47f93eaa8 100644 --- a/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx +++ b/frontend/app/components/Assist/components/AssistTabs/AssistTabs.tsx @@ -1,6 +1,5 @@ import { useModal } from 'App/components/Modal'; -import React, { useEffect, useState } from 'react'; -import { SlideModal, Avatar, TextEllipsis, Icon } from 'UI'; +import React, { useState } from 'react'; import SessionList from '../SessionList'; import stl from './assistTabs.module.css' @@ -20,7 +19,7 @@ const AssistTabs = (props: Props) => { <>
showModal(, { right: true })} + onClick={() => showModal(, { right: true, width: 700 })} > Active Sessions
diff --git a/frontend/app/components/Assist/components/SessionList/SessionList.tsx b/frontend/app/components/Assist/components/SessionList/SessionList.tsx index ad4eb4df5..a919fcf09 100644 --- a/frontend/app/components/Assist/components/SessionList/SessionList.tsx +++ b/frontend/app/components/Assist/components/SessionList/SessionList.tsx @@ -24,45 +24,43 @@ function SessionList(props: Props) { }, []); return ( -
-
-
-
- {props.userId}'s Live Sessions{' '} -
+
+
+
+ {props.userId}'s Live Sessions{' '}
- - - -
-
No live sessions found.
-
- } - > -
- {props.list.map((session: any) => ( -
- {session.pageTitle && session.pageTitle !== '' && ( -
- - {session.pageTitle} -
- )} - hideModal()} key={session.sessionId} session={session} /> -
- ))} -
-
-
+ + + +
+
No live sessions found.
+
+ } + > +
+ {props.list.map((session: any) => ( +
+ {session.pageTitle && session.pageTitle !== '' && ( +
+ + {session.pageTitle} +
+ )} + hideModal()} key={session.sessionId} session={session} /> +
+ ))} +
+
+
); } diff --git a/frontend/app/components/BugFinder/AutoComplete/AutoComplete.js b/frontend/app/components/BugFinder/AutoComplete/AutoComplete.js deleted file mode 100644 index 59ed9d9e9..000000000 --- a/frontend/app/components/BugFinder/AutoComplete/AutoComplete.js +++ /dev/null @@ -1,199 +0,0 @@ -import React from 'react'; -import APIClient from 'App/api_client'; -import cn from 'classnames'; -import { Input, Icon } from 'UI'; -import { debounce } from 'App/utils'; -import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; -import EventSearchInput from 'Shared/EventSearchInput'; -import stl from './autoComplete.module.css'; -import FilterItem from '../CustomFilters/FilterItem'; - -const TYPE_TO_SEARCH_MSG = "Start typing to search..."; -const NO_RESULTS_MSG = "No results found."; -const SOME_ERROR_MSG = "Some error occured."; -const defaultValueToText = value => value; -const defaultOptionMapping = (values, valueToText) => values.map(value => ({ text: valueToText(value), value })); - -const hiddenStyle = { - whiteSpace: 'pre-wrap', - opacity: 0, position: 'fixed', left: '-3000px' -}; - -let pasted = false; -let changed = false; - -class AutoComplete extends React.PureComponent { - static defaultProps = { - method: 'GET', - params: {}, - } - - state = { - values: [], - noResultsMessage: TYPE_TO_SEARCH_MSG, - ddOpen: false, - query: this.props.value, - loading: false, - error: false - } - - componentWillReceiveProps(newProps) { - if (this.props.value !== newProps.value) { - this.setState({ query: newProps.value}); - } - } - - onClickOutside = () => { - this.setState({ ddOpen: false }); - } - - requestValues = (q) => { - const { params, endpoint, method } = this.props; - this.setState({ - loading: true, - error: false, - }); - return new APIClient()[ method.toLowerCase() ](endpoint, { ...params, q }) - .then(response => response.json()) - .then(({ errors, data }) => { - if (errors) { - this.setError(); - } else { - this.setState({ - ddOpen: true, - values: data, - loading: false, - noResultsMessage: NO_RESULTS_MSG, - }); - } - }) - .catch(this.setError); - } - - debouncedRequestValues = debounce(this.requestValues, 1000) - - setError = () => this.setState({ - loading: false, - error: true, - noResultsMessage: SOME_ERROR_MSG, - }) - - - onInputChange = ({ target: { value } }) => { - changed = true; - this.setState({ query: value, updated: true }) - const _value = value ? value.trim() : undefined; - if (_value !== '' && _value !== ' ') { - this.debouncedRequestValues(_value) - } - } - - onBlur = ({ target: { value } }) => { - // to avoid sending unnecessary request on focus in/out without changing - if (!changed && !pasted) return; - - value = pasted ? this.hiddenInput.value : value; - const { onSelect, name } = this.props; - if (value !== this.props.value) { - const _value = value ? value.trim() : undefined; - onSelect(null, {name, value: _value}); - } - - changed = false; - pasted = false; - } - - onItemClick = (e, item) => { - e.stopPropagation(); - e.preventDefault(); - const { onSelect, name } = this.props; - - this.setState({ query: item.value, ddOpen: false}) - onSelect(e, {name, ...item.toJS()}); - } - - render() { - const { ddOpen, query, loading, values } = this.state; - const { - optionMapping = defaultOptionMapping, - valueToText = defaultValueToText, - placeholder = 'Type to search...', - headerText = '', - fullWidth = false, - onRemoveValue = () => {}, - onAddValue = () => {}, - showCloseButton = false, - } = this.props; - - const options = optionMapping(values, valueToText) - - return ( - - {/* */} -
- this.setState({ddOpen: true})} - onChange={ this.onInputChange } - onBlur={ this.onBlur } - value={ query } - autoFocus={ true } - type="text" - placeholder={ placeholder } - onPaste={(e) => { - const text = e.clipboardData.getData('Text'); - this.hiddenInput.value = text; - pasted = true; // to use only the hidden input - } } - autocomplete="do-not-autofill-bad-chrome" - /> -
- { showCloseButton ? : or} -
-
- - {showCloseButton &&
or
} - {/* this.setState({ddOpen: true})} - value={ query } - // icon="search" - label={{ basic: true, content:
test
}} - labelPosition='right' - loading={ loading } - autoFocus={ true } - type="search" - placeholder={ placeholder } - onPaste={(e) => { - const text = e.clipboardData.getData('Text'); - this.hiddenInput.value = text; - pasted = true; // to use only the hidden input - } } - /> */} - - { ddOpen && options.length > 0 && -
- { headerText && headerText } - { - options.map(item => ( - this.onItemClick(e, item) } - /> - )) - } -
- } -
- ); - } -} - -export default AutoComplete; diff --git a/frontend/app/components/BugFinder/AutoComplete/DropdownItem.js b/frontend/app/components/BugFinder/AutoComplete/DropdownItem.js deleted file mode 100644 index dc2b97304..000000000 --- a/frontend/app/components/BugFinder/AutoComplete/DropdownItem.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import stl from './dropdownItem.module.css'; - -const DropdownItem = ({ value, onSelect }) => { - return ( -
{ value }
- ); -}; - -export default DropdownItem; diff --git a/frontend/app/components/BugFinder/AutoComplete/autoComplete.module.css b/frontend/app/components/BugFinder/AutoComplete/autoComplete.module.css deleted file mode 100644 index 09a9a6571..000000000 --- a/frontend/app/components/BugFinder/AutoComplete/autoComplete.module.css +++ /dev/null @@ -1,64 +0,0 @@ -.menu { - border-radius: 0 0 3px 3px; - box-shadow: 0 2px 10px 0 $gray-light; - padding: 20px; - background-color: white; - max-height: 350px; - overflow-y: auto; - position: absolute; - top: 28px; - left: 0; - width: 500px; - z-index: 99; -} - -.searchInput { - & input { - font-size: 13px !important; - padding: 5px !important; - color: $gray-darkest !important; - font-size: 14px !important; - background-color: rgba(255, 255, 255, 0.8) !important; - - & .label { - padding: 0px !important; - display: flex; - align-items: center; - justify-content: center; - } - } - height: 28px !important; - width: 280px; - color: $gray-darkest !important; -} - -.fullWidth { - width: 100% !important; -} - -.inputWrapper { - border: solid thin $gray-light !important; - border-radius: 3px; - border-radius: 3px; - display: flex; - align-items: center; - & input { - height: 28px; - font-size: 13px !important; - padding: 0 5px !important; - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; - } - - & .right { - height: 28px; - display: flex; - align-items: center; - padding: 0 5px; - background-color: $gray-lightest; - border-left: solid thin $gray-light !important; - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; - cursor: pointer; - } -} \ No newline at end of file diff --git a/frontend/app/components/BugFinder/AutoComplete/dropdownItem.module.css b/frontend/app/components/BugFinder/AutoComplete/dropdownItem.module.css deleted file mode 100644 index f5646a470..000000000 --- a/frontend/app/components/BugFinder/AutoComplete/dropdownItem.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.wrapper { - padding: 8px; - border-bottom: solid thin rgba(0, 0, 0, 0.05); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - cursor: pointer; - &:hover { - background-color: $active-blue; - } -} \ No newline at end of file diff --git a/frontend/app/components/BugFinder/AutoComplete/index.js b/frontend/app/components/BugFinder/AutoComplete/index.js deleted file mode 100644 index fa63241a4..000000000 --- a/frontend/app/components/BugFinder/AutoComplete/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './AutoComplete'; \ No newline at end of file diff --git a/frontend/app/components/BugFinder/BugFinder.js b/frontend/app/components/BugFinder/BugFinder.js deleted file mode 100644 index 2c31a3ca8..000000000 --- a/frontend/app/components/BugFinder/BugFinder.js +++ /dev/null @@ -1,131 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { connect } from 'react-redux'; -import withPageTitle from 'HOCs/withPageTitle'; -import { fetchFavoriteList as fetchFavoriteSessionList } from 'Duck/sessions'; -import { applyFilter, clearEvents, addAttribute } from 'Duck/filters'; -import { KEYS } from 'Types/filter/customFilter'; -import SessionList from './SessionList'; -import stl from './bugFinder.module.css'; -import withLocationHandlers from 'HOCs/withLocationHandlers'; -import { fetch as fetchFilterVariables } from 'Duck/sources'; -import { fetchSources } from 'Duck/customField'; -import { setActiveTab } from 'Duck/search'; -import SessionsMenu from './SessionsMenu/SessionsMenu'; -import NoSessionsMessage from 'Shared/NoSessionsMessage'; -import SessionSearch from 'Shared/SessionSearch'; -import MainSearchBar from 'Shared/MainSearchBar'; -import { clearSearch, fetchSessions, addFilterByKeyAndValue } from 'Duck/search'; -import { FilterKey } from 'Types/filter/filterType'; - -const weakEqual = (val1, val2) => { - if (!!val1 === false && !!val2 === false) return true; - if (!val1 !== !val2) return false; - return `${val1}` === `${val2}`; -}; - -const allowedQueryKeys = [ - 'userOs', - 'userId', - 'userBrowser', - 'userDevice', - 'userCountry', - 'startDate', - 'endDate', - 'minDuration', - 'maxDuration', - 'referrer', - 'sort', - 'order', -]; - -@withLocationHandlers() -@connect( - (state) => ({ - filter: state.getIn(['filters', 'appliedFilter']), - variables: state.getIn(['customFields', 'list']), - sources: state.getIn(['customFields', 'sources']), - filterValues: state.get('filterValues'), - favoriteList: state.getIn(['sessions', 'favoriteList']), - currentProjectId: state.getIn(['site', 'siteId']), - sites: state.getIn(['site', 'list']), - watchdogs: state.getIn(['watchdogs', 'list']), - activeFlow: state.getIn(['filters', 'activeFlow']), - sessions: state.getIn(['sessions', 'list']), - }), - { - fetchFavoriteSessionList, - applyFilter, - addAttribute, - fetchFilterVariables, - fetchSources, - clearEvents, - setActiveTab, - clearSearch, - fetchSessions, - addFilterByKeyAndValue, - } -) -@withPageTitle('Sessions - OpenReplay') -export default class BugFinder extends React.PureComponent { - state = { showRehydratePanel: false }; - constructor(props) { - super(props); - - // TODO should cache the response - // props.fetchSources().then(() => { - // defaultFilters[6] = { - // category: 'Collaboration', - // type: 'CUSTOM', - // keys: this.props.sources.filter(({type}) => type === 'collaborationTool').map(({ label, key }) => ({ type: 'CUSTOM', source: key, label: label, key, icon: 'integrations/' + key, isFilter: false })).toJS() - // }; - // defaultFilters[7] = { - // category: 'Logging Tools', - // type: 'ERROR', - // keys: this.props.sources.filter(({type}) => type === 'logTool').map(({ label, key }) => ({ type: 'ERROR', source: key, label: label, key, icon: 'integrations/' + key, isFilter: false })).toJS() - // }; - // }); - // if (props.sessions.size === 0) { - // props.fetchSessions(); - // } - - const queryFilter = this.props.query.all(allowedQueryKeys); - if (queryFilter.hasOwnProperty('userId')) { - props.addFilterByKeyAndValue(FilterKey.USERID, queryFilter.userId); - } else { - if (props.sessions.size === 0) { - props.fetchSessions(); - } - } - } - - toggleRehydratePanel = () => { - this.setState({ showRehydratePanel: !this.state.showRehydratePanel }); - }; - - setActiveTab = (tab) => { - this.props.setActiveTab(tab); - }; - - render() { - const { showRehydratePanel } = this.state; - - return ( -
-
-
- -
-
- -
- - -
- -
-
-
- ); - } -} diff --git a/frontend/app/components/BugFinder/DateRange.js b/frontend/app/components/BugFinder/DateRange.js deleted file mode 100644 index 9a6e77f12..000000000 --- a/frontend/app/components/BugFinder/DateRange.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import { applyFilter } from 'Duck/search'; -import { fetchList as fetchFunnelsList } from 'Duck/funnels'; -import DateRangeDropdown from 'Shared/DateRangeDropdown'; - -@connect(state => ({ - filter: state.getIn([ 'search', 'instance' ]), -}), { - applyFilter, fetchFunnelsList -}) -export default class DateRange extends React.PureComponent { - onDateChange = (e) => { - // this.props.fetchFunnelsList(e.rangeValue) - this.props.applyFilter(e) - } - render() { - const { filter: { rangeValue, startDate, endDate }, className } = this.props; - - return ( - - ); - } -} \ No newline at end of file diff --git a/frontend/app/components/BugFinder/FilterSelectionButton.js b/frontend/app/components/BugFinder/FilterSelectionButton.js deleted file mode 100644 index 9854b29a3..000000000 --- a/frontend/app/components/BugFinder/FilterSelectionButton.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { Icon } from 'UI'; -import stl from './filterSelectionButton.module.css'; - -const FilterSelectionButton = ({ label }) => { - return ( -
- { label } - -
- ); -}; - -export default FilterSelectionButton; diff --git a/frontend/app/components/BugFinder/Filters/SortDropdown.js b/frontend/app/components/BugFinder/Filters/SortDropdown.js deleted file mode 100644 index 398902ec5..000000000 --- a/frontend/app/components/BugFinder/Filters/SortDropdown.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import Select from 'Shared/Select'; -import { Icon } from 'UI'; -import { sort } from 'Duck/sessions'; -import { applyFilter } from 'Duck/search'; -import stl from './sortDropdown.module.css'; - -@connect(null, { sort, applyFilter }) -export default class SortDropdown extends React.PureComponent { - state = { value: null } - sort = ({ value }) => { - value = value.value - this.setState({ value: value }) - const [ sort, order ] = value.split('-'); - const sign = order === 'desc' ? -1 : 1; - this.props.applyFilter({ order, sort }); - - this.props.sort(sort, sign) - setTimeout(() => this.props.sort(sort, sign), 3000); //AAA - } - - render() { - const { options } = this.props; - return ( - + + + + + +
+
+ - render() { - const { instance, saving, errors, onClose } = this.props; - return ( -
-
- - - - - - - - -
-
- - - -
- - -
-
- - {errors && ( -
- {errors.map((error) => ( - - {error} - - ))} -
- )} +
- ); - } + + +
+ + + {errors && ( +
+ {errors.map((error) => ( + + {error} + + ))} +
+ )} +
+ ); + } } export default connect( - (state) => ({ - instance: state.getIn(['slack', 'instance']), - saving: state.getIn(['slack', 'saveRequest', 'loading']), - errors: state.getIn(['slack', 'saveRequest', 'errors']), - }), - { edit, save, init, remove, update } + (state) => ({ + instance: state.getIn(['slack', 'instance']), + saving: + state.getIn(['slack', 'saveRequest', 'loading']) || + state.getIn(['slack', 'updateRequest', 'loading']), + errors: state.getIn(['slack', 'saveRequest', 'errors']), + }), + { edit, save, init, remove, update } )(SlackAddForm); diff --git a/frontend/app/components/Client/Integrations/SlackForm.tsx b/frontend/app/components/Client/Integrations/SlackForm.tsx index 79c6b2a00..018dbe885 100644 --- a/frontend/app/components/Client/Integrations/SlackForm.tsx +++ b/frontend/app/components/Client/Integrations/SlackForm.tsx @@ -3,7 +3,6 @@ import SlackChannelList from './SlackChannelList/SlackChannelList'; import { fetchList, init } from 'Duck/integrations/slack'; import { connect } from 'react-redux'; import SlackAddForm from './SlackAddForm'; -import { useModal } from 'App/components/Modal'; import { Button } from 'UI'; interface Props { @@ -38,7 +37,7 @@ const SlackForm = (props: Props) => {

Slack

-
diff --git a/frontend/app/components/Client/Integrations/Teams/TeamsAddForm.tsx b/frontend/app/components/Client/Integrations/Teams/TeamsAddForm.tsx new file mode 100644 index 000000000..f13efc535 --- /dev/null +++ b/frontend/app/components/Client/Integrations/Teams/TeamsAddForm.tsx @@ -0,0 +1,117 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { edit, save, init, update, remove } from 'Duck/integrations/teams'; +import { Form, Input, Button, Message } from 'UI'; +import { confirm } from 'UI'; + +interface Props { + edit: (inst: any) => void; + save: (inst: any) => void; + init: (inst: any) => void; + update: (inst: any) => void; + remove: (id: string) => void; + onClose: () => void; + instance: any; + saving: boolean; + errors: any; +} + +class TeamsAddForm extends React.PureComponent { + componentWillUnmount() { + this.props.init({}); + } + + save = () => { + const instance = this.props.instance; + if (instance.exists()) { + this.props.update(this.props.instance); + } else { + this.props.save(this.props.instance); + } + }; + + remove = async (id: string) => { + if ( + await confirm({ + header: 'Confirm', + confirmButton: 'Yes, delete', + confirmation: `Are you sure you want to permanently delete this channel?`, + }) + ) { + this.props.remove(id); + } + }; + + write = ({ target: { name, value } }: { target: { name: string; value: string } }) => + this.props.edit({ [name]: value }); + + render() { + const { instance, saving, errors, onClose } = this.props; + return ( +
+
+ + + + + + + + +
+
+ + + +
+ + +
+
+ + {errors && ( +
+ {errors.map((error: any) => ( + + {error} + + ))} +
+ )} +
+ ); + } +} + +export default connect( + (state: any) => ({ + instance: state.getIn(['teams', 'instance']), + saving: + state.getIn(['teams', 'saveRequest', 'loading']) || + state.getIn(['teams', 'updateRequest', 'loading']), + errors: state.getIn(['teams', 'saveRequest', 'errors']), + }), + { edit, save, init, remove, update } +)(TeamsAddForm); diff --git a/frontend/app/components/Client/Integrations/Teams/TeamsChannelList.tsx b/frontend/app/components/Client/Integrations/Teams/TeamsChannelList.tsx new file mode 100644 index 000000000..942e1e32c --- /dev/null +++ b/frontend/app/components/Client/Integrations/Teams/TeamsChannelList.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { NoContent } from 'UI'; +import { remove, edit, init } from 'Duck/integrations/teams'; +import DocLink from 'Shared/DocLink/DocLink'; + +function TeamsChannelList(props: { list: any, edit: (inst: any) => any, onEdit: () => void }) { + const { list } = props; + + const onEdit = (instance: Record) => { + props.edit(instance); + props.onEdit(); + }; + + return ( +
+ +
+ Integrate MS Teams with OpenReplay and share insights with the rest of the team, directly from the recording page. +
+ +
+ } + size="small" + show={list.size === 0} + > + {list.map((c: any) => ( +
onEdit(c)} + > +
+
{c.name}
+
{c.endpoint}
+
+
+ ))} + +
+ ); +} + +export default connect( + (state: any) => ({ + list: state.getIn(['teams', 'list']), + }), + { remove, edit, init } +)(TeamsChannelList); diff --git a/frontend/app/components/Client/Integrations/Teams/index.tsx b/frontend/app/components/Client/Integrations/Teams/index.tsx new file mode 100644 index 000000000..e51bd64b1 --- /dev/null +++ b/frontend/app/components/Client/Integrations/Teams/index.tsx @@ -0,0 +1,55 @@ +import React, { useEffect } from 'react'; +import TeamsChannelList from './TeamsChannelList'; +import { fetchList, init } from 'Duck/integrations/teams'; +import { connect } from 'react-redux'; +import TeamsAddForm from './TeamsAddForm'; +import { Button } from 'UI'; + +interface Props { + onEdit?: (integration: any) => void; + istance: any; + fetchList: any; + init: any; +} +const MSTeams = (props: Props) => { + const [active, setActive] = React.useState(false); + + const onEdit = () => { + setActive(true); + }; + + const onNew = () => { + setActive(true); + props.init({}); + } + + useEffect(() => { + props.fetchList(); + }, []); + + return ( +
+ {active && ( +
+ setActive(false)} /> +
+ )} +
+
+

Microsoft Teams

+
+ +
+
+ ); +}; + +MSTeams.displayName = 'MSTeams'; + +export default connect( + (state: any) => ({ + istance: state.getIn(['teams', 'instance']), + }), + { fetchList, init } +)(MSTeams); diff --git a/frontend/app/components/Client/Integrations/integrationItem.module.css b/frontend/app/components/Client/Integrations/integrationItem.module.css index fca162909..72b3be522 100644 --- a/frontend/app/components/Client/Integrations/integrationItem.module.css +++ b/frontend/app/components/Client/Integrations/integrationItem.module.css @@ -1,7 +1,6 @@ .wrapper { border-radius: 3px; /* border: solid thin $gray-light-shade; */ - margin-right: 10px; padding: 20px; cursor: pointer; width: 130px; diff --git a/frontend/app/components/Client/Notifications/Notifications.js b/frontend/app/components/Client/Notifications/Notifications.js deleted file mode 100644 index 88855dd45..000000000 --- a/frontend/app/components/Client/Notifications/Notifications.js +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useEffect } from 'react'; -import cn from 'classnames'; -import stl from './notifications.module.css'; -import { Checkbox, Toggler } from 'UI'; -import { connect } from 'react-redux'; -import { withRequest } from 'HOCs'; -import { fetch as fetchConfig, edit as editConfig, save as saveConfig } from 'Duck/config'; -import withPageTitle from 'HOCs/withPageTitle'; - -function Notifications(props) { - const { config } = props; - - useEffect(() => { - props.fetchConfig(); - }, []); - - const onChange = () => { - const _config = { weeklyReport: !config.weeklyReport }; - props.editConfig(_config); - props.saveConfig(_config); - }; - - return ( -
-
{

{'Notifications'}

}
-
-
Weekly project summary
-
Receive wekly report for each project on email.
- - {/* */} - {/* */} -
-
- ); -} - -export default connect( - (state) => ({ - config: state.getIn(['config', 'options']), - }), - { fetchConfig, editConfig, saveConfig } -)(withPageTitle('Notifications - OpenReplay Preferences')(Notifications)); diff --git a/frontend/app/components/Client/Notifications/Notifications.tsx b/frontend/app/components/Client/Notifications/Notifications.tsx new file mode 100644 index 000000000..4a5d9a542 --- /dev/null +++ b/frontend/app/components/Client/Notifications/Notifications.tsx @@ -0,0 +1,39 @@ +import React, { useEffect } from 'react'; +import cn from 'classnames'; +import stl from './notifications.module.css'; +import { Toggler } from 'UI'; +import { useStore } from "App/mstore"; +import { observer } from 'mobx-react-lite' +import withPageTitle from 'HOCs/withPageTitle'; + +function Notifications() { + const { weeklyReportStore } = useStore() + + + useEffect(() => { + void weeklyReportStore.fetchReport() + }, []); + + const onChange = () => { + const newValue = !weeklyReportStore.weeklyReport + void weeklyReportStore.fetchEditReport(newValue) + }; + + return ( +
+
{

{'Notifications'}

}
+
+
Weekly project summary
+
Receive weekly report for each project on email.
+ +
+
+ ); +} + +export default withPageTitle('Notifications - OpenReplay Preferences')(observer(Notifications)) \ No newline at end of file diff --git a/frontend/app/components/Client/ProfileSettings/ChangePassword.js b/frontend/app/components/Client/ProfileSettings/ChangePassword.js index d0222abf0..2640ab612 100644 --- a/frontend/app/components/Client/ProfileSettings/ChangePassword.js +++ b/frontend/app/components/Client/ProfileSettings/ChangePassword.js @@ -3,6 +3,7 @@ import { connect } from 'react-redux'; import { Button, Message, Form, Input } from 'UI'; import styles from './profileSettings.module.css'; import { updatePassword } from 'Duck/user'; +import { toast } from 'react-toastify'; const ERROR_DOESNT_MATCH = "Passwords doesn't match"; const MIN_LENGTH = 8; @@ -48,12 +49,15 @@ export default class ChangePassword extends React.PureComponent { oldPassword, newPassword, }) - .then(() => { - if (this.props.passwordErrors.size === 0) { - this.setState({ - ...defaultState, - success: true, - }); + .then((e) => { + const success = !e || !e.errors || e.errors.length === 0; + this.setState({ + ...defaultState, + success: success, + show: !success + }); + if (success) { + toast.success(`Successfully changed password`); } }); }; @@ -98,7 +102,7 @@ export default class ChangePassword extends React.PureComponent { -
+
@@ -107,9 +111,6 @@ export default class ChangePassword extends React.PureComponent { Cancel
- ) : (
this.setState({ show: true })}> diff --git a/frontend/app/components/Client/ProfileSettings/Settings.js b/frontend/app/components/Client/ProfileSettings/Settings.js index f0de1358c..e8343ebda 100644 --- a/frontend/app/components/Client/ProfileSettings/Settings.js +++ b/frontend/app/components/Client/ProfileSettings/Settings.js @@ -1,5 +1,4 @@ import React from 'react'; -import copy from 'copy-to-clipboard'; import { connect } from 'react-redux'; import { Button, Input, Form } from 'UI'; import { updateAccount, updateClient } from 'Duck/user'; @@ -52,6 +51,7 @@ export default class Settings extends React.PureComponent { type="text" onChange={ this.onChange } value={ accountName } + maxLength={50} /> @@ -63,6 +63,7 @@ export default class Settings extends React.PureComponent { type="text" onChange={ this.onChange } value={ organizationName } + maxLength={50} /> diff --git a/frontend/app/components/Client/Roles/components/RoleForm/RoleForm.tsx b/frontend/app/components/Client/Roles/components/RoleForm/RoleForm.tsx index f189c09ec..b6b9efe92 100644 --- a/frontend/app/components/Client/Roles/components/RoleForm/RoleForm.tsx +++ b/frontend/app/components/Client/Roles/components/RoleForm/RoleForm.tsx @@ -77,6 +77,7 @@ const RoleForm = (props: Props) => { name="name" value={role.name} onChange={write} + maxLength={40} className={stl.input} id="name-field" placeholder="Ex. Admin" diff --git a/frontend/app/components/Client/Sites/AddProjectButton/AddProjectButton.tsx b/frontend/app/components/Client/Sites/AddProjectButton/AddProjectButton.tsx index 36bf497e2..0a2712462 100644 --- a/frontend/app/components/Client/Sites/AddProjectButton/AddProjectButton.tsx +++ b/frontend/app/components/Client/Sites/AddProjectButton/AddProjectButton.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Tooltip, Button, IconButton } from 'UI'; +import { Tooltip, Button } from 'UI'; import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; import { init, remove, fetchGDPR } from 'Duck/site'; diff --git a/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx b/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx index 0fe5fce65..da1a2fb44 100644 --- a/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx +++ b/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx @@ -12,7 +12,7 @@ function InstallButton(props: Props) { const onClick = () => { showModal( , - { right: true } + { right: true, width: 700 } ); }; return ( diff --git a/frontend/app/components/Client/Sites/NewSiteForm.js b/frontend/app/components/Client/Sites/NewSiteForm.js index 75776e1f1..6ce0f7d4c 100644 --- a/frontend/app/components/Client/Sites/NewSiteForm.js +++ b/frontend/app/components/Client/Sites/NewSiteForm.js @@ -10,6 +10,7 @@ import { confirm } from 'UI'; import { clearSearch } from 'Duck/search'; import { clearSearch as clearSearchLive } from 'Duck/liveSearch'; import { withStore } from 'App/mstore'; +import { toast } from 'react-toastify'; @connect( (state) => ({ @@ -61,9 +62,14 @@ export default class NewSiteForm extends React.PureComponent { return this.setState({ existsError: true }); } if (site.exists()) { - this.props.update(this.props.site, this.props.site.id).then(() => { - this.props.onClose(null); - this.props.fetchList(); + this.props.update(this.props.site, this.props.site.id).then((response) => { + if (!response || !response.errors || response.errors.size === 0) { + this.props.onClose(null); + this.props.fetchList(); + toast.success('Project updated successfully'); + } else { + toast.error(response.errors[0]); + } }); } else { this.props.save(this.props.site).then(() => { @@ -102,7 +108,7 @@ export default class NewSiteForm extends React.PureComponent {
- +
)}
- {this.state.existsError &&
{'Site exists already. Please choose another one.'}
} + {this.state.existsError &&
{'Project exists already.'}
}
diff --git a/frontend/app/components/Client/TabItem.js b/frontend/app/components/Client/TabItem.js index dae16e7df..8d97eb060 100644 --- a/frontend/app/components/Client/TabItem.js +++ b/frontend/app/components/Client/TabItem.js @@ -1,6 +1,5 @@ import React from 'react'; import { Icon } from 'UI'; -import styles from './client.module.css'; const TabItem = ({ active = false, onClick, icon, label }) => { return ( diff --git a/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx b/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx index d1799cc6a..44f7dfb2e 100644 --- a/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx +++ b/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx @@ -49,7 +49,7 @@ function UserForm(props: Props) { } return useObserver(() => ( -
+

{`${user.exists() ? 'Update' : 'Invite'} User`}

@@ -59,6 +59,7 @@ function UserForm(props: Props) { userStore.searchQuery); const { showModal } = useModal(); - const filterList = (list) => { - const filterRE = getRE(searchQuery, 'i'); - let _list = list.filter((w) => { - return filterRE.test(w.email) || filterRE.test(w.roleName); - }); - return _list; - }; + // const filterList = (list) => { + // const filterRE = getRE(searchQuery, 'i'); + // let _list = list.filter((w) => { + // return filterRE.test(w.email) || filterRE.test(w.roleName); + // }); + // return _list; + // }; + const getList = (list) => filterList(list, searchQuery, ['email', 'roleName', 'name']) - const list: any = searchQuery !== '' ? filterList(users) : users; + const list: any = searchQuery !== '' ? getList(users) : users; const length = list.length; useEffect(() => { diff --git a/frontend/app/components/Client/Users/components/UserListItem/UserListItem.tsx b/frontend/app/components/Client/Users/components/UserListItem/UserListItem.tsx index abae1cbe1..72b99929d 100644 --- a/frontend/app/components/Client/Users/components/UserListItem/UserListItem.tsx +++ b/frontend/app/components/Client/Users/components/UserListItem/UserListItem.tsx @@ -42,7 +42,7 @@ function UserListItem(props: Props) { onClick={editHandler} >
- {user.name} + {user.name} {/* {isEnterprise && } */}
diff --git a/frontend/app/components/Client/Webhooks/ListItem.js b/frontend/app/components/Client/Webhooks/ListItem.js index 3e177d47e..3ea0e691b 100644 --- a/frontend/app/components/Client/Webhooks/ListItem.js +++ b/frontend/app/components/Client/Webhooks/ListItem.js @@ -1,7 +1,7 @@ import React from 'react'; import { Button } from 'UI'; -const ListItem = ({ webhook, onEdit, onDelete }) => { +const ListItem = ({ webhook, onEdit }) => { return (
diff --git a/frontend/app/components/Client/Webhooks/WebhookForm.js b/frontend/app/components/Client/Webhooks/WebhookForm.js index b64a63af8..08799456f 100644 --- a/frontend/app/components/Client/Webhooks/WebhookForm.js +++ b/frontend/app/components/Client/Webhooks/WebhookForm.js @@ -1,94 +1,86 @@ import React from 'react'; -import { connect } from 'react-redux'; -import { edit, save } from 'Duck/webhook'; import { Form, Button, Input } from 'UI'; import styles from './webhookForm.module.css'; +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite'; +import { toast } from 'react-toastify'; -@connect( - (state) => ({ - webhook: state.getIn(['webhooks', 'instance']), - loading: state.getIn(['webhooks', 'saveRequest', 'loading']), - }), - { - edit, - save, - } -) -class WebhookForm extends React.PureComponent { - setFocus = () => this.focusElement.focus(); - onChangeSelect = (event, { name, value }) => this.props.edit({ [name]: value }); - write = ({ target: { value, name } }) => this.props.edit({ [name]: value }); +function WebhookForm(props) { + const { settingsStore } = useStore(); + const { webhookInst: webhook, hooksLoading: loading, saveWebhook, editWebhook } = settingsStore; + const write = ({ target: { value, name } }) => editWebhook({ [name]: value }); - save = () => { - this.props.save(this.props.webhook).then(() => { - this.props.onClose(); - }); - }; + const save = () => { + saveWebhook(webhook) + .then(() => { + props.onClose(); + }) + .catch((e) => { + const baseStr = 'Error saving webhook'; + if (e.response) { + e.response.json().then(({ errors }) => { + toast.error(baseStr + ': ' + errors.join(',')); + }); + } else { + toast.error(baseStr); + } + }); + }; - render() { - const { webhook, loading } = this.props; - return ( -
-

{webhook.exists() ? 'Update' : 'Add'} Webhook

-
- - - { - this.focusElement = ref; - }} - name="name" - value={webhook.name} - onChange={this.write} - placeholder="Name" - /> - + return ( +
+

{webhook.exists() ? 'Update' : 'Add'} Webhook

+ + + + + - - - { - this.focusElement = ref; - }} - name="endpoint" - value={webhook.endpoint} - onChange={this.write} - placeholder="Endpoint" - /> - + + + + - - - { - this.focusElement = ref; - }} - name="authHeader" - value={webhook.authHeader} - onChange={this.write} - placeholder="Auth Header" - /> - + + + + -
-
- - {webhook.exists() && } -
- {webhook.exists() && } -
- -
- ); - } +
+
+ + {webhook.exists() && } +
+ {webhook.exists() && ( + + )} +
+ +
+ ); } -export default WebhookForm; +export default observer(WebhookForm); diff --git a/frontend/app/components/Client/Webhooks/Webhooks.js b/frontend/app/components/Client/Webhooks/Webhooks.js deleted file mode 100644 index e005a893a..000000000 --- a/frontend/app/components/Client/Webhooks/Webhooks.js +++ /dev/null @@ -1,89 +0,0 @@ -import React, { useEffect } from 'react'; -import { connect } from 'react-redux'; -import cn from 'classnames'; -import withPageTitle from 'HOCs/withPageTitle'; -import { Button, Loader, NoContent, Icon } from 'UI'; -import { init, fetchList, remove } from 'Duck/webhook'; -import WebhookForm from './WebhookForm'; -import ListItem from './ListItem'; -import styles from './webhooks.module.css'; -import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; -import { confirm } from 'UI'; -import { toast } from 'react-toastify'; -import { useModal } from 'App/components/Modal'; - -function Webhooks(props) { - const { webhooks, loading } = props; - const { showModal, hideModal } = useModal(); - - const noSlackWebhooks = webhooks.filter((hook) => hook.type !== 'slack'); - useEffect(() => { - props.fetchList(); - }, []); - - const init = (v) => { - props.init(v); - showModal(); - }; - - const removeWebhook = async (id) => { - if ( - await confirm({ - header: 'Confirm', - confirmButton: 'Yes, delete', - confirmation: `Are you sure you want to remove this webhook?`, - }) - ) { - props.remove(id).then(() => { - toast.success('Webhook removed successfully'); - }); - hideModal(); - } - }; - - return ( -
-
-

{'Webhooks'}

- {/* -
- -
- - Leverage webhooks to push OpenReplay data to other systems. -
- - - - -
None added yet
-
- } - size="small" - show={noSlackWebhooks.size === 0} - > -
- {noSlackWebhooks.map((webhook) => ( - init(webhook)} /> - ))} -
- - -
- ); -} - -export default connect( - (state) => ({ - webhooks: state.getIn(['webhooks', 'list']), - loading: state.getIn(['webhooks', 'loading']), - }), - { - init, - fetchList, - remove, - } -)(withPageTitle('Webhooks - OpenReplay Preferences')(Webhooks)); diff --git a/frontend/app/components/Client/Webhooks/Webhooks.tsx b/frontend/app/components/Client/Webhooks/Webhooks.tsx new file mode 100644 index 000000000..df829072d --- /dev/null +++ b/frontend/app/components/Client/Webhooks/Webhooks.tsx @@ -0,0 +1,80 @@ +import React, { useEffect } from 'react'; +import cn from 'classnames'; +import withPageTitle from 'HOCs/withPageTitle'; +import { Button, Loader, NoContent, Icon } from 'UI'; +import WebhookForm from './WebhookForm'; +import ListItem from './ListItem'; +import styles from './webhooks.module.css'; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +import { confirm } from 'UI'; +import { toast } from 'react-toastify'; +import { useModal } from 'App/components/Modal'; +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite' +import { IWebhook } from 'Types/webhook'; + +function Webhooks() { + const { settingsStore } = useStore() + const { webhooks, hooksLoading: loading } = settingsStore; + const { showModal, hideModal } = useModal(); + + const customWebhooks = webhooks.filter((hook) => hook.type === 'webhook'); + useEffect(() => { + void settingsStore.fetchWebhooks(); + }, []); + + const init = (webhookInst?: Partial) => { + settingsStore.initWebhook(webhookInst); + showModal(, { right: true }); + }; + + const removeWebhook = async (id: string) => { + if ( + await confirm({ + header: 'Confirm', + confirmButton: 'Yes, delete', + confirmation: `Are you sure you want to remove this webhook?`, + }) + ) { + settingsStore.removeWebhook(id).then(() => { + toast.success('Webhook removed successfully'); + }); + hideModal(); + } + }; + + return ( +
+
+

{'Webhooks'}

+ +
+ +
+ + Leverage webhook notifications on alerts to trigger custom callbacks. +
+ + + + +
None added yet
+
+ } + size="small" + show={customWebhooks.length === 0} + > +
+ {customWebhooks.map((webhook) => ( + init(webhook)} /> + ))} +
+ + +
+ ); +} + +export default withPageTitle('Webhooks - OpenReplay Preferences')(observer(Webhooks)); diff --git a/frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.js b/frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.js deleted file mode 100644 index cc8792848..000000000 --- a/frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react' -import { LAST_24_HOURS, LAST_30_MINUTES, LAST_7_DAYS, LAST_30_DAYS, CUSTOM_RANGE } from 'Types/app/period'; -import { ALL, DESKTOP, MOBILE } from 'Types/app/platform'; -import { connect } from 'react-redux'; -import { setPeriod, setPlatform } from 'Duck/dashboard'; -import cn from 'classnames'; -import styles from './DashboardHeader.module.css'; -import Filters from '../Filters/Filters'; - -export const PERIOD_OPTIONS = [ - { text: 'Past 30 Min', value: LAST_30_MINUTES }, - { text: 'Past 24 Hours', value: LAST_24_HOURS }, - { text: 'Past 7 Days', value: LAST_7_DAYS }, - { text: 'Past 30 Days', value: LAST_30_DAYS }, - { text: 'Choose Date', value: CUSTOM_RANGE }, -]; - -const PLATFORM_OPTIONS = [ - { text: 'All Platforms', value: ALL }, - { text: 'Desktop', value: DESKTOP }, - { text: 'Mobile', value: MOBILE } -]; - -const DashboardHeader = props => { - return ( -
- - -
-
-
- ) -} - -export default connect(state => ({ - period: state.getIn([ 'dashboard', 'period' ]), - platform: state.getIn([ 'dashboard', 'platform' ]), - currentProjectId: state.getIn([ 'site', 'siteId' ]), - sites: state.getIn([ 'site', 'list' ]), -}), { setPeriod, setPlatform })(DashboardHeader) diff --git a/frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.module.css b/frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.module.css deleted file mode 100644 index bef1bd7f1..000000000 --- a/frontend/app/components/Dashboard/DashboardHeader/DashboardHeader.module.css +++ /dev/null @@ -1,27 +0,0 @@ -.dropdown { - display: 'flex' !important; - align-items: 'center'; - padding: 5px 8px; - border-radius: 3px; - transition: all 0.3s; - font-weight: 500; - - &:hover { - background-color: #DDDDDD; - transition: all 0.2s; - } -} - -.dateInput { - display: flex; - align-items: center; - padding: 4px; - font-weight: 500; - font-size: 14px; - color: $gray-darkest; - - &:hover { - background-color: lightgray; - border-radius: 3px; - } -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/DashboardHeader/index.js b/frontend/app/components/Dashboard/DashboardHeader/index.js deleted file mode 100644 index 924f0ec2c..000000000 --- a/frontend/app/components/Dashboard/DashboardHeader/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default as DashboardHeader } from './DashboardHeader'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/ApplicationActivity.js b/frontend/app/components/Dashboard/Widgets/ApplicationActivity.js deleted file mode 100644 index d091e0a66..000000000 --- a/frontend/app/components/Dashboard/Widgets/ApplicationActivity.js +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import { Loader } from 'UI'; -import { msToSec } from 'App/date'; -import { CountBadge, Divider, widgetHOC } from './common'; - -@widgetHOC('applicationActivity') -export default class ApplicationActivity extends React.PureComponent { - render() { - const { data, loading } = this.props; - return ( -
- - - - - - - -
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.js b/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.js deleted file mode 100644 index 99ffc931b..000000000 --- a/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.js +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { - BarChart, Bar, CartesianGrid, Legend, ResponsiveContainer, - XAxis, YAxis, Tooltip -} from 'recharts'; - -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 28 } - - if (rangeName === LAST_24_HOURS) params.density = 21 - if (rangeName === LAST_30_MINUTES) params.density = 28 - if (rangeName === YESTERDAY) params.density = 28 - if (rangeName === LAST_7_DAYS) params.density = 28 - - return params -} - -@widgetHOC('resourcesCountByType', { customParams }) -export default class BreakdownOfLoadedResources extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - - return ( - - - - - - - - - - - - - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/index.js b/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/index.js deleted file mode 100644 index 6e2e706fb..000000000 --- a/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './BreakdownOfLoadedResources'; diff --git a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/BusiestTimeOfTheDay.js b/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/BusiestTimeOfTheDay.js index 31deb2cba..e69de29bb 100644 --- a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/BusiestTimeOfTheDay.js +++ b/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/BusiestTimeOfTheDay.js @@ -1,43 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { Table, widgetHOC, domain } from '../common'; -import { - Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, - PieChart, Pie, Cell, Tooltip, ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area } from 'recharts'; - -const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042']; -const RADIAN = Math.PI / 180; - -@widgetHOC('busiestTimeOfDay', { fitContent: true }) -export default class BusiestTimeOfTheDay extends React.PureComponent { - renderCustomizedLabel = ({ - cx, cy, midAngle, innerRadius, outerRadius, percent, index, - }) => { - const radius = innerRadius + (outerRadius - innerRadius) * 0.5; - const x = cx + radius * Math.cos(-midAngle * RADIAN); - const y = cy + radius * Math.sin(-midAngle * RADIAN); - - return ( - cx ? 'start' : 'end'} dominantBaseline="central"> - {`${(percent * 100).toFixed(0)}%`} - - ); - }; - - render() { - const { data, loading } = this.props; - return ( - - - - - - - - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/Chart.js b/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/Chart.js deleted file mode 100644 index a66c2801d..000000000 --- a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/Chart.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { AreaChart, Area } from 'recharts'; - -const Chart = ({ data }) => { - return ( - - - - ); -} - -Chart.displayName = 'Chart'; - -export default Chart; diff --git a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/ImageInfo.js b/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/ImageInfo.js deleted file mode 100644 index 28fd52a75..000000000 --- a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/ImageInfo.js +++ /dev/null @@ -1,29 +0,0 @@ -import { Tooltip, Icon } from 'UI'; -import styles from './imageInfo.module.css'; - -const ImageInfo = ({ data }) => ( -
- - } - > -
- -
{'Preview'}
-
-
- - {data.name} - -
-); - -ImageInfo.displayName = 'ImageInfo'; - -export default ImageInfo; diff --git a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/imageInfo.module.css b/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/imageInfo.module.css deleted file mode 100644 index 69030a582..000000000 --- a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/imageInfo.module.css +++ /dev/null @@ -1,39 +0,0 @@ -.name { - display: flex; - align-items: center; - - & > span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 60%; - } -} - -.imagePreview { - max-width: 200px; - max-height: 200px; -} - -.imageWrapper { - display: flex; - flex-flow: column; - align-items: center; - width: 40px; - text-align: center; - margin-right: 10px; - & > span { - height: 16px; - } - & .label { - font-size: 9px; - color: $gray-light; - } -} - -.popup { - background-color: #f5f5f5 !important; - &:before { - background-color: #f5f5f5 !important; - } -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/index.js b/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/index.js deleted file mode 100644 index ce82e31a0..000000000 --- a/frontend/app/components/Dashboard/Widgets/BusiestTimeOfTheDay/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './BusiestTimeOfTheDay'; diff --git a/frontend/app/components/Dashboard/Widgets/CallWithErrors/CallWithErrors.js b/frontend/app/components/Dashboard/Widgets/CallWithErrors/CallWithErrors.js deleted file mode 100644 index 4b3cadef6..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallWithErrors/CallWithErrors.js +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { Table, widgetHOC } from '../common'; -import { getRE } from 'App/utils'; -import ImageInfo from './ImageInfo'; -import MethodType from './MethodType'; -import cn from 'classnames'; -import stl from './callWithErrors.module.css'; - -const cols = [ - { - key: 'method', - title: 'Method', - className: 'text-left', - Component: MethodType, - cellClass: 'ml-2', - width: '8%', - }, - { - key: 'urlHostpath', - title: 'Path', - Component: ImageInfo, - width: '40%', - }, - { - key: 'allRequests', - title: 'Requests', - className: 'text-left', - width: '15%', - }, - { - key: '4xx', - title: '4xx', - className: 'text-left', - width: '15%', - }, - { - key: '5xx', - title: '5xx', - className: 'text-left', - width: '15%', - } -]; - -@widgetHOC('callsErrors', { fitContent: true }) -export default class CallWithErrors extends React.PureComponent { - state = { search: ''} - - test = (value = '', serach) => getRE(serach, 'i').test(value); - - write = ({ target: { name, value } }) => { - this.setState({ [ name ]: value }) - }; - - render() { - const { data: images, loading } = this.props; - const { search } = this.state; - const _data = search ? images.filter(i => this.test(i.urlHostpath, search)) : images; - - return ( - -
- -
- - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/CallWithErrors/Chart.js b/frontend/app/components/Dashboard/Widgets/CallWithErrors/Chart.js deleted file mode 100644 index a66c2801d..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallWithErrors/Chart.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { AreaChart, Area } from 'recharts'; - -const Chart = ({ data }) => { - return ( - - - - ); -} - -Chart.displayName = 'Chart'; - -export default Chart; diff --git a/frontend/app/components/Dashboard/Widgets/CallWithErrors/ImageInfo.js b/frontend/app/components/Dashboard/Widgets/CallWithErrors/ImageInfo.js deleted file mode 100644 index 80a2010b9..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallWithErrors/ImageInfo.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import { TextEllipsis } from 'UI'; -import styles from './imageInfo.module.css'; - -const ImageInfo = ({ data }) => ( -
- -
-); - -ImageInfo.displayName = 'ImageInfo'; - -export default ImageInfo; diff --git a/frontend/app/components/Dashboard/Widgets/CallWithErrors/MethodType.js b/frontend/app/components/Dashboard/Widgets/CallWithErrors/MethodType.js deleted file mode 100644 index ba370b481..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallWithErrors/MethodType.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { Label } from 'UI'; - -const MethodType = ({ data }) => { - return ( - - ) -} - -export default MethodType diff --git a/frontend/app/components/Dashboard/Widgets/CallWithErrors/callWithErrors.module.css b/frontend/app/components/Dashboard/Widgets/CallWithErrors/callWithErrors.module.css deleted file mode 100644 index bc37a3991..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallWithErrors/callWithErrors.module.css +++ /dev/null @@ -1,22 +0,0 @@ -.topActions { - position: absolute; - top: -4px; - right: 50px; - display: flex; - justify-content: flex-end; -} - -.searchField { - padding: 4px 5px; - border-bottom: dotted thin $gray-light; - border-radius: 3px; - &:focus, - &:active { - border: solid thin transparent !important; - box-shadow: none; - background-color: $gray-light; - } - &:hover { - border: solid thin $gray-light !important; - } -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CallWithErrors/imageInfo.module.css b/frontend/app/components/Dashboard/Widgets/CallWithErrors/imageInfo.module.css deleted file mode 100644 index 69030a582..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallWithErrors/imageInfo.module.css +++ /dev/null @@ -1,39 +0,0 @@ -.name { - display: flex; - align-items: center; - - & > span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 60%; - } -} - -.imagePreview { - max-width: 200px; - max-height: 200px; -} - -.imageWrapper { - display: flex; - flex-flow: column; - align-items: center; - width: 40px; - text-align: center; - margin-right: 10px; - & > span { - height: 16px; - } - & .label { - font-size: 9px; - color: $gray-light; - } -} - -.popup { - background-color: #f5f5f5 !important; - &:before { - background-color: #f5f5f5 !important; - } -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CallWithErrors/index.js b/frontend/app/components/Dashboard/Widgets/CallWithErrors/index.js deleted file mode 100644 index 6b0db1bd0..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallWithErrors/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './CallWithErrors'; diff --git a/frontend/app/components/Dashboard/Widgets/CallsErrors4xx/CallsErrors4xx.js b/frontend/app/components/Dashboard/Widgets/CallsErrors4xx/CallsErrors4xx.js deleted file mode 100644 index d387b53cc..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallsErrors4xx/CallsErrors4xx.js +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, - LineChart, Line, Legend, Tooltip -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@widgetHOC('domainsErrors_4xx', { customParams }) -export default class CallsErrors4xx extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - - const namesMap = data.chart - .map(i => Object.keys(i)) - .flat() - .filter(i => i !== 'time' && i !== 'timestamp') - .reduce( - (unique, item) => (unique.includes(item) ? unique : [...unique, item]), - [] - ); - - return ( - - - - - - - - - - - - - - - - { namesMap.map((key, index) => ( - - ))} - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/CallsErrors4xx/index.js b/frontend/app/components/Dashboard/Widgets/CallsErrors4xx/index.js deleted file mode 100644 index 78fa75b53..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallsErrors4xx/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './CallsErrors4xx'; diff --git a/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/CallsErrors5xx.js b/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/CallsErrors5xx.js deleted file mode 100644 index 3c655da5f..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/CallsErrors5xx.js +++ /dev/null @@ -1,85 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, - LineChart, Line, Legend, Tooltip -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@widgetHOC('domainsErrors_5xx', { customParams }) -export default class CallsErrors5xx extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - - const namesMap = data.chart - .map(i => Object.keys(i)) - .flat() - .filter(i => i !== 'time' && i !== 'timestamp') - .reduce( - (unique, item) => (unique.includes(item) ? unique : [...unique, item]), - [] - ); - - return ( - - - - - - - - - - { namesMap.map((key, index) => ( - - ))} - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/index.js b/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/index.js deleted file mode 100644 index 61b9900d7..000000000 --- a/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './CallsErrors5xx'; diff --git a/frontend/app/components/Dashboard/Widgets/CpuLoad/CpuLoad.js b/frontend/app/components/Dashboard/Widgets/CpuLoad/CpuLoad.js deleted file mode 100644 index ee448dac2..000000000 --- a/frontend/app/components/Dashboard/Widgets/CpuLoad/CpuLoad.js +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles, AvgLabel } from '../common'; -import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, AreaChart, Area, Tooltip } from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@widgetHOC('cpu', { customParams }) -export default class CpuLoad extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); - - return ( - - -
- -
- - - {gradientDef} - - - Styles.tickFormatter(val)} - label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }} - /> - - - - -
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/CpuLoad/index.js b/frontend/app/components/Dashboard/Widgets/CpuLoad/index.js deleted file mode 100644 index 84bd7bc61..000000000 --- a/frontend/app/components/Dashboard/Widgets/CpuLoad/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './CpuLoad'; diff --git a/frontend/app/components/Dashboard/Widgets/Crashes/Crashes.js b/frontend/app/components/Dashboard/Widgets/Crashes/Crashes.js deleted file mode 100644 index 576c9c13f..000000000 --- a/frontend/app/components/Dashboard/Widgets/Crashes/Crashes.js +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { - AreaChart, Area, ResponsiveContainer, - XAxis, YAxis, CartesianGrid, Tooltip -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@widgetHOC('crashes', { customParams }) -export default class Crashes extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); - - return ( - - - - - {gradientDef} - - - Styles.tickFormatter(val)} - label={{ ...Styles.axisLabelLeft, value: "Number of Crashes" }} - /> - - - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/Crashes/index.js b/frontend/app/components/Dashboard/Widgets/Crashes/index.js deleted file mode 100644 index cb869ef09..000000000 --- a/frontend/app/components/Dashboard/Widgets/Crashes/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Crashes'; diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx new file mode 100644 index 000000000..e8ff709c9 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/ClickMapCard.tsx @@ -0,0 +1,93 @@ +import React from 'react' +import { useStore } from 'App/mstore' +import { observer } from 'mobx-react-lite' +import ClickMapRenderer from 'App/components/Session/Player/ClickMapRenderer' +import { connect } from 'react-redux' +import { setCustomSession, clearCurrentSession } from 'App/duck/sessions' +import { fetchInsights } from 'Duck/sessions'; +import { NoContent, Icon } from 'App/components/ui' + +function ClickMapCard({ + setCustomSession, + visitedEvents, + insights, + fetchInsights, + insightsFilters, + host, + clearCurrentSession, +}: any) { + const { metricStore, dashboardStore } = useStore(); + const onMarkerClick = (s: string, innerText: string) => { + metricStore.changeClickMapSearch(s, innerText) + } + + React.useEffect(() => { + return () => clearCurrentSession() + }, []) + React.useEffect(() => { + if (metricStore.instance.data.domURL) { + setCustomSession(metricStore.instance.data) + } + }, [metricStore.instance]) + + React.useEffect(() => { + if (visitedEvents.length) { + const urlOptions = visitedEvents.map(({ url, host }: any) => ({ label: url, value: url, host })) + const url = insightsFilters.url ? insightsFilters.url : host + urlOptions[0].value; + const rangeValue = dashboardStore.drillDownPeriod.rangeValue + const startDate = dashboardStore.drillDownPeriod.start + const endDate = dashboardStore.drillDownPeriod.end + fetchInsights({ ...insightsFilters, url, startDate, endDate, rangeValue, clickRage: metricStore.clickMapFilter }) + } + }, [visitedEvents, metricStore.clickMapFilter]) + + if (!metricStore.instance.data.domURL || insights.size === 0) { + return ( + + + No data for selected period or URL. +
+ +
+ + } + show={true} + /> + ) + } + if (!visitedEvents || !visitedEvents.length) { + return
Loading session
+ } + + const searchUrl = metricStore.instance.series[0].filter.filters[0].value[0] + const jumpToEvent = metricStore.instance.data.events.find((evt: Record) => { + if (searchUrl) return evt.path.includes(searchUrl) + return evt + }) || { timestamp: metricStore.instance.data.startTs } + + const jumpTimestamp = (jumpToEvent.timestamp - metricStore.instance.data.startTs) + jumpToEvent.domBuildingTime + 99 // 99ms safety margin to give some time for the DOM to load + + return ( +
+ +
+ ) +} + +export default connect( + (state: any) => ({ + insightsFilters: state.getIn(['sessions', 'insightFilters']), + visitedEvents: state.getIn(['sessions', 'visitedEvents']), + insights: state.getIn(['sessions', 'insights']), + host: state.getIn(['sessions', 'host']), + }), + { setCustomSession, fetchInsights, clearCurrentSession } +) +(observer(ClickMapCard)) diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/index.ts new file mode 100644 index 000000000..c72a4090b --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard/index.ts @@ -0,0 +1 @@ +export { default } from './ClickMapCard' diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx index 4da7631fa..0118617ba 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx @@ -1,6 +1,6 @@ import React from 'react' import { Styles } from '../../common'; -import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; +import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts'; import { LineChart, Line, Legend } from 'recharts'; interface Props { diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx index dbc3c5504..94a3e5c4f 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors/CustomMetricTableErrors.tsx @@ -30,6 +30,7 @@ function CustomMetricTableErrors(props: RouteComponentProps & Props) { showModal(, { right: true, + width: 1200, onClose: () => { if (props.history.location.pathname.includes("/dashboard") || props.history.location.pathname.includes("/metrics/")) { props.history.replace({ search: "" }); diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx index ffb489b11..0f0f3aafa 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx @@ -1,83 +1,74 @@ -import { useObserver } from "mobx-react-lite"; -import React from "react"; -import SessionItem from "Shared/SessionItem"; -import { Pagination, NoContent } from "UI"; -import { useStore } from "App/mstore"; +import { observer, useObserver } from 'mobx-react-lite'; +import React, { useMemo } from 'react'; +import SessionItem from 'Shared/SessionItem'; +import { Pagination, NoContent } from 'UI'; +import { useStore } from 'App/mstore'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +import Session from 'App/mstore/types/session'; interface Props { - metric: any; - isTemplate?: boolean; - isEdit?: boolean; - data: any; + metric: any; + isTemplate?: boolean; + isEdit?: boolean; + data: any; } function CustomMetricTableSessions(props: Props) { - const { isEdit = false, metric, data } = props; - const { dashboardStore } = useStore(); - const period = dashboardStore.period; + const { isEdit = false, metric, data } = props; - return useObserver(() => ( - - -
-
No relevant sessions found for the selected time period.
-
- } - > -
- {data.sessions && - data.sessions.map((session: any, index: any) => ( -
- -
- ))} + const sessions = useMemo(() => { + return data && data.sessions ? data.sessions.map((session: any) => new Session().fromJson(session)) : []; + }, []); - {isEdit && ( -
- - metric.updateKey("page", page) - } - limit={data.total} - debounceRequest={500} - /> -
- )} - - {!isEdit && ( - - )} + return useObserver(() => ( + + +
+
+ No relevant sessions found for the selected time period. +
+
+ } + > +
+ {sessions && + sessions.map((session: any, index: any) => ( +
+
- - )); + ))} + + {isEdit && ( +
+ metric.updateKey('page', page)} + limit={data.total} + debounceRequest={500} + /> +
+ )} + + {!isEdit && } +
+
+ )); } -export default CustomMetricTableSessions; +export default observer(CustomMetricTableSessions); const ViewMore = ({ total, limit }: any) => - total > limit && ( -
-
-
- All {total} sessions -
-
+ total > limit ? ( +
+
+
+ All {total} sessions
- ); +
+
+ ) : null; diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.module.css b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.module.css deleted file mode 100644 index 1d1ef3ee4..000000000 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.wrapper { - background-color: white; - /* border: solid thin $gray-medium; */ - border-radius: 3px; - padding: 10px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx deleted file mode 100644 index b804ca439..000000000 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import React, { useState } from 'react'; -import { connect } from 'react-redux'; -import { Loader, NoContent, Icon, Tooltip } from 'UI'; -import { Styles } from '../../common'; -import { ResponsiveContainer } from 'recharts'; -import stl from './CustomMetricWidget.module.css'; -import { getStartAndEndTimestampsByDensity } from 'Types/dashboard/helper'; -import { - init, - edit, - remove, - setAlertMetricId, - setActiveWidget, - updateActiveState, -} from 'Duck/customMetrics'; -import { setShowAlerts } from 'Duck/dashboard'; -import CustomMetriLineChart from '../CustomMetriLineChart'; -import CustomMetricPieChart from '../CustomMetricPieChart'; -import CustomMetricPercentage from '../CustomMetricPercentage'; -import CustomMetricTable from '../CustomMetricTable'; -import { NO_METRIC_DATA } from 'App/constants/messages'; - -const customParams = (rangeName) => { - const params = { density: 70 }; - - // if (rangeName === LAST_24_HOURS) params.density = 70 - // if (rangeName === LAST_30_MINUTES) params.density = 70 - // if (rangeName === YESTERDAY) params.density = 70 - // if (rangeName === LAST_7_DAYS) params.density = 70 - - return params; -}; - -interface Props { - metric: any; - // loading?: boolean; - data?: any; - compare?: boolean; - period?: any; - onClickEdit: (e) => void; - remove: (id) => void; - setShowAlerts: (showAlerts) => void; - setAlertMetricId: (id) => void; - onAlertClick: (e) => void; - init: (metric: any) => void; - edit: (setDefault?) => void; - setActiveWidget: (widget) => void; - updateActiveState: (metricId, state) => void; - isTemplate?: boolean; -} -function CustomMetricWidget(props: Props) { - const { metric, period, isTemplate } = props; - const [loading, setLoading] = useState(false); - const [data, setData] = useState([]); - // const [seriesMap, setSeriesMap] = useState([]); - - const colors = Styles.customMetricColors; - const params = customParams(period.rangeName); - // const metricParams = { ...params, metricId: metric.metricId, viewType: 'lineChart', startDate: period.start, endDate: period.end } - const isLineChart = metric.viewType === 'lineChart'; - const isProgress = metric.viewType === 'progress'; - const isTable = metric.viewType === 'table'; - const isPieChart = metric.viewType === 'pieChart'; - - const clickHandlerTable = (filters) => { - const activeWidget = { - widget: metric, - period: period, - ...period.toTimestamps(), - filters, - }; - props.setActiveWidget(activeWidget); - }; - - const clickHandler = (event, index) => { - if (event) { - const payload = event.activePayload[0].payload; - const timestamp = payload.timestamp; - const periodTimestamps = - metric.metricType === 'timeseries' - ? getStartAndEndTimestampsByDensity(timestamp, period.start, period.end, params.density) - : period.toTimestamps(); - - const activeWidget = { - widget: metric, - period: period, - ...periodTimestamps, - timestamp: payload.timestamp, - index, - }; - - props.setActiveWidget(activeWidget); - } - }; - - const updateActiveState = (metricId, state) => { - props.updateActiveState(metricId, state); - }; - - return ( -
-
-
{metric.name}
-
- {!isTable && !isPieChart && ( - - )} - props.init(metric)} - /> - updateActiveState(metric.metricId, false)} - /> -
-
-
- - - - <> - {isLineChart && ( - - )} - - {isPieChart && ( - - )} - - {isProgress && ( - - )} - - {isTable && ( - - )} - - - - -
-
- ); -} - -export default connect( - (state) => ({ - period: state.getIn(['dashboard', 'period']), - }), - { - remove, - setShowAlerts, - setAlertMetricId, - edit, - setActiveWidget, - updateActiveState, - init, - } -)(CustomMetricWidget); - -const WidgetIcon = ({ - className = '', - tooltip = '', - icon, - onClick, -}: { - className: string; - tooltip: string; - icon: string; - onClick: any; -}) => ( - -
- {/* @ts-ignore */} - -
-
-); diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/index.ts deleted file mode 100644 index 4a6d9b653..000000000 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './CustomMetricWidget'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx new file mode 100644 index 000000000..a8ac5b54d --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightItem.tsx @@ -0,0 +1,114 @@ +import { IssueCategory } from 'App/types/filter/filterType'; +import React from 'react'; +import { Icon } from 'UI'; +import cn from 'classnames'; + +interface Props { + item: any; + onClick?: (e: React.MouseEvent) => void; +} +function InsightItem(props: Props) { + const { item, onClick = () => {} } = props; + const className = + 'flex items-center py-4 hover:bg-active-blue -mx-4 px-4 border-b last:border-transparent cursor-pointer'; + + switch (item.category) { + case IssueCategory.RAGE: + return ; + case IssueCategory.RESOURCES: + return ; + case IssueCategory.ERRORS: + return ; + case IssueCategory.NETWORK: + return ; + default: + return null; + } +} + +export default InsightItem; + + +function Change({ change, isIncreased }: any) { + return ( +
+ + {change}% +
+ ); +} + +function ErrorItem({ item, className, onClick }: any) { + return ( +
+ + {item.isNew ? ( + <> +
{item.name}
+
error observed
+
{item.ratio}%
+
more than other new errors
+ + ) : ( + <> +
Increase
+
in
+
{item.name}
+ + + )} +
+ ); +} + +function NetworkItem({ item, className, onClick }: any) { + return ( +
+ +
Network request
+
{item.name}
+
{item.change > 0 ? 'increased' : 'decreased'}
+ +
+ ); +} + +function ResourcesItem({ item, className, onClick }: any) { + return ( +
+ +
{item.change > 0 ? 'Inrease' : 'Decrease'}
+
in
+
{item.name}
+ +
+ ); +} + +function RageItem({ item, className, onClick }: any) { + return ( +
+ +
{item.isNew ? item.name : 'Click Rage'}
+ {item.isNew &&
has
} + {!item.isNew &&
on {item.name}
} + {item.isNew &&
{item.ratio}%
} + {item.isNew &&
more clickrage than other raged elements.
} + {!item.isNew && ( + <> +
increase by
+ + + )} +
+ ); +} diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx new file mode 100644 index 000000000..a12449437 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/InsightsCard.tsx @@ -0,0 +1,79 @@ +import { NoContent, Icon } from 'UI'; +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite'; +import React from 'react'; +import InsightItem from './InsightItem'; +import { InishtIssue } from 'App/mstore/types/widget'; +import FilterItem from 'App/mstore/types/filterItem'; +import { FilterKey, IssueCategory, IssueType } from 'App/types/filter/filterType'; +import { filtersMap } from 'Types/filter/newFilter'; + +function InsightsCard({ data }: any) { + const { dashboardStore } = useStore(); + const drillDownFilter = dashboardStore.drillDownFilter; + + const clickHanddler = (e: React.MouseEvent, item: InishtIssue) => { + let filter: any = {}; + switch (item.category) { + case IssueCategory.RESOURCES: + filter = { + ...filtersMap[ + item.name === IssueType.MEMORY ? FilterKey.AVG_MEMORY_USAGE : FilterKey.AVG_CPU_LOAD + ], + }; + filter.source = [item.oldValue]; + filter.value = []; + break; + case IssueCategory.RAGE: + filter = { ...filtersMap[FilterKey.CLICK] }; + filter.value = [item.name]; + break; + case IssueCategory.NETWORK: + filter = { ...filtersMap[FilterKey.FETCH] }; + filter.value = []; + filter.filters.forEach((f: any) => { + f.value = []; + if (f.key === FilterKey.FETCH_URL) { + f.value = [item.name]; + } + + if (f.key === FilterKey.FETCH_DURATION) { + f.operator = '>='; + f.value = [item.oldValue]; + } + }); + break; + case IssueCategory.ERRORS: + filter = { ...filtersMap[FilterKey.ERROR] }; + filter.value = [item.name]; + break; + } + + filter = new FilterItem(filter); + drillDownFilter.merge({ + filters: [filter.toJson()], + }); + }; + + return ( + + + No data for selected period. +
+ } + show={data.issues && data.issues.length === 0} + > +
+ {data.issues && + data.issues.map((item: any) => ( + clickHanddler(e, item)} /> + ))} +
+ + ); +} + +export default observer(InsightsCard); diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/index.ts new file mode 100644 index 000000000..bd85f2e4b --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard/index.ts @@ -0,0 +1 @@ +export { default } from './InsightsCard' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/DomBuildingTime/DomBuildingTime.js b/frontend/app/components/Dashboard/Widgets/DomBuildingTime/DomBuildingTime.js deleted file mode 100644 index 970bfdbad..000000000 --- a/frontend/app/components/Dashboard/Widgets/DomBuildingTime/DomBuildingTime.js +++ /dev/null @@ -1,103 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles, AvgLabel } from '../common'; -import { withRequest } from 'HOCs'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; -import WidgetAutoComplete from 'Shared/WidgetAutoComplete'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const WIDGET_KEY = 'pagesDomBuildtime'; -const toUnderscore = s => s.split(/(?=[A-Z])/).join('_').toLowerCase(); - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} -@withRequest({ - dataName: "options", - initialData: [], - dataWrapper: data => data, - loadingName: 'optionsLoading', - requestName: "fetchOptions", - endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', - method: 'GET' -}) -@widgetHOC(WIDGET_KEY, { customParams }) -export default class DomBuildingTime extends React.PureComponent { - onSelect = (params) => { - const _params = customParams(this.props.period.rangeName) - this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, { ..._params, url: params.value }) - } - - render() { - const { data, loading, period, optionsLoading, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); - - return ( - - -
- - -
- - - - - {gradientDef} - - - Styles.tickFormatter(val)} - /> - - - - - - -
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/DomBuildingTime/index.js b/frontend/app/components/Dashboard/Widgets/DomBuildingTime/index.js deleted file mode 100644 index b5d6c5682..000000000 --- a/frontend/app/components/Dashboard/Widgets/DomBuildingTime/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './DomBuildingTime'; diff --git a/frontend/app/components/Dashboard/Widgets/Errors/Errors.js b/frontend/app/components/Dashboard/Widgets/Errors/Errors.js deleted file mode 100644 index ad2313c7f..000000000 --- a/frontend/app/components/Dashboard/Widgets/Errors/Errors.js +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area } from 'recharts'; -import { Loader, NoContent } from 'UI'; -import { CountBadge, domain, widgetHOC } from '../common'; -import styles from './errors.module.css'; - -@widgetHOC('errors') -export default class Errors extends React.PureComponent { - render() { - const { data, loading } = this.props; - - const isMoreThanKSessions = data.impactedSessions > 1000; - const impactedSessionsView = isMoreThanKSessions ? Math.trunc(data.impactedSessions / 1000) : data.impactedSessions; - return ( -
- - - { 'Events' }
} - count={ data.count } - change={ data.progress } - oppositeColors - /> - { 'Sessions' }
} - count={ impactedSessionsView } - change={ data.impactedSessionsProgress } - unit={ isMoreThanKSessions ? 'k' : '' } - oppositeColors - /> - - - - - - - - - - - - - - -
- - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/Errors/errors.module.css b/frontend/app/components/Dashboard/Widgets/Errors/errors.module.css deleted file mode 100644 index 1de075dc2..000000000 --- a/frontend/app/components/Dashboard/Widgets/Errors/errors.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.label { - max-width: 65px; - line-height: 14px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/Errors/index.js b/frontend/app/components/Dashboard/Widgets/Errors/index.js deleted file mode 100644 index 87b132550..000000000 --- a/frontend/app/components/Dashboard/Widgets/Errors/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Errors'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/ErrorsByOrigin.js b/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/ErrorsByOrigin.js deleted file mode 100644 index d77bac5f4..000000000 --- a/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/ErrorsByOrigin.js +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { - BarChart, Bar, CartesianGrid, Tooltip, Legend, ResponsiveContainer, - XAxis, YAxis -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 28 } - - if (rangeName === LAST_24_HOURS) params.density = 28 - if (rangeName === LAST_30_MINUTES) params.density = 28 - if (rangeName === YESTERDAY) params.density = 28 - if (rangeName === LAST_7_DAYS) params.density = 28 - - return params -} - -@widgetHOC('resourcesByParty', { fullwidth: true, customParams }) -export default class ErrorsByOrigin extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - - return ( - - - - - - - - - - 1st Party} dataKey="firstParty" stackId="a" fill={colors[0]} /> - 3rd Party} dataKey="thirdParty" stackId="a" fill={colors[2]} /> - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/index.js b/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/index.js deleted file mode 100644 index 9ef5b0117..000000000 --- a/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ErrorsByOrigin'; diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js b/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js index 4421a3fbb..e69de29bb 100644 --- a/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js +++ b/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js @@ -1,63 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, domain, Styles } from '../common'; -import { numberWithCommas} from 'App/utils'; -import { - BarChart, Bar, CartesianGrid, Tooltip, - LineChart, Line, Legend, ResponsiveContainer, - XAxis, YAxis -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 28 } - - if (rangeName === LAST_24_HOURS) params.density = 21 - if (rangeName === LAST_30_MINUTES) params.density = 28 - if (rangeName === YESTERDAY) params.density = 28 - if (rangeName === LAST_7_DAYS) params.density = 28 - - return params -} - -@widgetHOC('errorsPerType', { fullwidth: true, customParams }) -export default class ErrorsByType extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - - return ( - - - - - - - - - - - - - - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsByType/index.js b/frontend/app/components/Dashboard/Widgets/ErrorsByType/index.js deleted file mode 100644 index 53f976df3..000000000 --- a/frontend/app/components/Dashboard/Widgets/ErrorsByType/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ErrorsByType'; diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.module.css b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.module.css deleted file mode 100644 index 529aa15eb..000000000 --- a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.bar { - height: 5px; - background-color: red; - width: 100%; - border-radius: 3px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js index 11af3f7d7..e69de29bb 100644 --- a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js +++ b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js @@ -1,37 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { Table, widgetHOC, domain, AvgLabel, Styles } from '../common'; -import Bar from './Bar'; -import { numberWithCommas } from 'App/utils'; - -@widgetHOC('errorsPerDomains') -export default class ErrorsPerDomain extends React.PureComponent { - render() { - const { data, loading, compare = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const firstAvg = data.first() && data.first().errorsCount; - - return ( - - -
- {data.map((item, i) => - - )} -
-
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/index.js b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/index.js deleted file mode 100644 index 3a60d15c0..000000000 --- a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ErrorsPerDomain'; diff --git a/frontend/app/components/Dashboard/Widgets/FPS/FPS.js b/frontend/app/components/Dashboard/Widgets/FPS/FPS.js deleted file mode 100644 index 843cea3db..000000000 --- a/frontend/app/components/Dashboard/Widgets/FPS/FPS.js +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles, AvgLabel } from '../common'; -import { ResponsiveContainer, Tooltip, XAxis, YAxis, CartesianGrid, AreaChart, Area } from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@widgetHOC('fps', { customParams }) -export default class FPS extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); - - return ( - - -
- -
- - - {gradientDef} - - - Styles.tickFormatter(val)} - label={{ ...Styles.axisLabelLeft, value: "Frames Per Second" }} - /> - - - - -
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/FPS/index.js b/frontend/app/components/Dashboard/Widgets/FPS/index.js deleted file mode 100644 index 55394ee20..000000000 --- a/frontend/app/components/Dashboard/Widgets/FPS/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './FPS'; diff --git a/frontend/app/components/Dashboard/Widgets/LastFrustrations/LastFrustrations.js b/frontend/app/components/Dashboard/Widgets/LastFrustrations/LastFrustrations.js deleted file mode 100644 index fcd36d98e..000000000 --- a/frontend/app/components/Dashboard/Widgets/LastFrustrations/LastFrustrations.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import { Loader, NoContent, BrowserIcon, OsIcon } from 'UI'; -import { countries } from 'App/constants'; -import { diffFromNowString } from 'App/date'; -import { widgetHOC, SessionLine } from '../common'; - -@widgetHOC('sessionsFrustration', { fitContent: true }) -export default class LastFeedbacks extends React.PureComponent { - render() { - const { data: sessions, loading } = this.props; - return ( - - - { sessions.map(({ - startedAt, - sessionId, - clickRage, - returningLocation, - userBrowser, - userOs, - userCountry, - }) => ( - - { clickRage ? "Click Rage" : "Returning Location" } - - - - } - subInfo={ `${ diffFromNowString(startedAt) } ago - ${ countries[ userCountry ] || '' }` } - /> - ))} - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/LastFrustrations/index.js b/frontend/app/components/Dashboard/Widgets/LastFrustrations/index.js deleted file mode 100644 index caaafd7e5..000000000 --- a/frontend/app/components/Dashboard/Widgets/LastFrustrations/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './LastFrustrations'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/LastPerformance/LastPerformance.js b/frontend/app/components/Dashboard/Widgets/LastPerformance/LastPerformance.js deleted file mode 100644 index e44e556aa..000000000 --- a/frontend/app/components/Dashboard/Widgets/LastPerformance/LastPerformance.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, SessionLine } from '../common'; - -@widgetHOC('sessionsPerformance', { fitContent: true }) -export default class LastFeedbacks extends React.PureComponent { - render() { - const { data: sessions, loading } = this.props; - return ( - - - { sessions.map(({ sessionId, missedResources }) => ( - - ))} - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/LastPerformance/index.js b/frontend/app/components/Dashboard/Widgets/LastPerformance/index.js deleted file mode 100644 index 6563813c2..000000000 --- a/frontend/app/components/Dashboard/Widgets/LastPerformance/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './LastPerformance'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/MemoryConsumption/MemoryConsumption.js b/frontend/app/components/Dashboard/Widgets/MemoryConsumption/MemoryConsumption.js deleted file mode 100644 index 14ed08d93..000000000 --- a/frontend/app/components/Dashboard/Widgets/MemoryConsumption/MemoryConsumption.js +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles, AvgLabel } from '../common'; -import { ResponsiveContainer, Tooltip, XAxis, YAxis, CartesianGrid, AreaChart, Area } from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} -@widgetHOC('memoryConsumption', { customParams }) -export default class MemoryConsumption extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); - - return ( - - -
- -
- - - {gradientDef} - - - Styles.tickFormatterBytes(val)} - label={{ ...Styles.axisLabelLeft, value: "JS Heap Size (mb)" }} - /> - - - - -
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/MemoryConsumption/index.js b/frontend/app/components/Dashboard/Widgets/MemoryConsumption/index.js deleted file mode 100644 index 9e6644855..000000000 --- a/frontend/app/components/Dashboard/Widgets/MemoryConsumption/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './MemoryConsumption'; diff --git a/frontend/app/components/Dashboard/Widgets/MissingResources/Chart.js b/frontend/app/components/Dashboard/Widgets/MissingResources/Chart.js deleted file mode 100644 index cf2e5758e..000000000 --- a/frontend/app/components/Dashboard/Widgets/MissingResources/Chart.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { AreaChart, Area } from 'recharts'; -import { Styles } from '../common'; - -const Chart = ({ data, compare }) => { - const colors = compare ? Styles.compareColors : Styles.colors; - - return ( - - - - ); -} - -Chart.displayName = 'Chart'; - -export default Chart; diff --git a/frontend/app/components/Dashboard/Widgets/MissingResources/CopyPath.js b/frontend/app/components/Dashboard/Widgets/MissingResources/CopyPath.js deleted file mode 100644 index 6b7e709e7..000000000 --- a/frontend/app/components/Dashboard/Widgets/MissingResources/CopyPath.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react' -import copy from 'copy-to-clipboard' -import { useState } from 'react' - -const CopyPath = ({ data }) => { - const [copied, setCopied] = useState(false) - - const copyHandler = () => { - copy(data.url); - setCopied(true); - setTimeout(function() { - setCopied(false) - }, 500); - } - - return ( -
- { copied ? 'Copied' : 'Copy Path'} -
- ) -} - -export default CopyPath diff --git a/frontend/app/components/Dashboard/Widgets/MissingResources/MissingResources.js b/frontend/app/components/Dashboard/Widgets/MissingResources/MissingResources.js deleted file mode 100644 index af2444418..000000000 --- a/frontend/app/components/Dashboard/Widgets/MissingResources/MissingResources.js +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { Table, widgetHOC } from '../common'; -import Chart from './Chart'; -import ResourceInfo from './ResourceInfo'; -import CopyPath from './CopyPath'; - -const cols = [ - { - key: 'resource', - title: 'Resource', - Component: ResourceInfo, - width: '40%', - }, - { - key: 'sessions', - title: 'Sessions', - toText: count => `${ count > 1000 ? Math.trunc(count / 1000) : count }${ count > 1000 ? 'k' : '' }`, - width: '20%', - }, - { - key: 'trend', - title: 'Trend', - Component: Chart, - width: '20%', - }, - { - key: 'copy-path', - title: '', - Component: CopyPath, - cellClass: 'invisible group-hover:visible text-right', - width: '20%', - } -]; - -@widgetHOC('missingResources', { }) -export default class MissingResources extends React.PureComponent { - render() { - const { data: resources, loading, compare } = this.props; - return ( - - -
- - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/MissingResources/ResourceInfo.js b/frontend/app/components/Dashboard/Widgets/MissingResources/ResourceInfo.js deleted file mode 100644 index 0a3ba485e..000000000 --- a/frontend/app/components/Dashboard/Widgets/MissingResources/ResourceInfo.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import { diffFromNowString } from 'App/date'; -import { TextEllipsis } from 'UI'; - -import styles from './resourceInfo.module.css'; - -export default class ResourceInfo extends React.PureComponent { - render() { - const { data } = this.props; - return ( -
- -
- { data.endedAt && data.startedAt && `${ diffFromNowString(data.endedAt) } ago - ${ diffFromNowString(data.startedAt) } old` } -
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/MissingResources/index.js b/frontend/app/components/Dashboard/Widgets/MissingResources/index.js deleted file mode 100644 index 27229bb1c..000000000 --- a/frontend/app/components/Dashboard/Widgets/MissingResources/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './MissingResources'; diff --git a/frontend/app/components/Dashboard/Widgets/MissingResources/resourceInfo.module.css b/frontend/app/components/Dashboard/Widgets/MissingResources/resourceInfo.module.css deleted file mode 100644 index d73d23530..000000000 --- a/frontend/app/components/Dashboard/Widgets/MissingResources/resourceInfo.module.css +++ /dev/null @@ -1,10 +0,0 @@ -.name { - letter-spacing: -.04em; - font-size: .9rem; - cursor: pointer; -} - -.timings { - color: $gray-medium; - font-size: 12px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/Chart.js b/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/Chart.js deleted file mode 100644 index 1f97c6605..000000000 --- a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/Chart.js +++ /dev/null @@ -1,11 +0,0 @@ -import { BarChart, Bar } from 'recharts'; - -const Chart = ({ data }) => ( - - - -); - -Chart.displaName = 'Chart'; - -export default Chart; diff --git a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/ErrorInfo.js b/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/ErrorInfo.js deleted file mode 100644 index 4ac5e5f67..000000000 --- a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/ErrorInfo.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { diffFromNowString } from 'App/date'; -import { TextEllipsis } from 'UI'; -import styles from './errorInfo.module.css'; - -export default class ErrorInfo extends React.PureComponent { - findJourneys = () => this.props.findJourneys(this.props.data.error) - - render() { - const { data } = this.props; - return ( -
- -
- { `${ diffFromNowString(data.lastOccurrenceAt) } ago - ${ diffFromNowString(data.firstOccurrenceAt) } old` } -
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/MostImpactfulErrors.js b/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/MostImpactfulErrors.js deleted file mode 100644 index a86e23220..000000000 --- a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/MostImpactfulErrors.js +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import withSiteIdRouter from 'HOCs/withSiteIdRouter'; -import { Loader, NoContent } from 'UI'; -import { addEvent } from 'Duck/filters'; -import { TYPES } from 'Types/filter/event'; -import { sessions } from 'App/routes'; -import { Table, widgetHOC } from '../common'; -import Chart from './Chart'; -import ErrorInfo from './ErrorInfo'; - -const cols = [ - { - key: 'error', - title: 'Error Info', - Component: ErrorInfo, - width: '80%', - }, - { - key: 'trend', - title: 'Trend', - Component: Chart, - width: '10%', - }, - { - key: 'sessions', - title: 'Sessions', - toText: count => `${ count > 1000 ? Math.trunc(count / 1000) : count }${ count > 1000 ? 'k' : '' }`, - width: '10%', - }, -]; - -@withSiteIdRouter -@widgetHOC('errorsTrend', { fullwidth: true }) -@connect(null, { addEvent }) -export default class MostImpactfulErrors extends React.PureComponent { - findJourneys = (error) => { - this.props.addEvent({ - type: TYPES.CONSOLE, - value: error, - }, true); - this.props.history.push(sessions()); - } - - render() { - const { data: errors, loading } = this.props; - return ( - - -
- - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/errorInfo.module.css b/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/errorInfo.module.css deleted file mode 100644 index 7aa0df799..000000000 --- a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/errorInfo.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.errorText { - font-family: 'menlo', 'monaco', 'consolas', monospace; - letter-spacing: -.04em; - font-size: .9rem; - cursor: pointer; -} - -.timings { - color: $gray-medium; - font-size: 12px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/index.js b/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/index.js deleted file mode 100644 index c072ed32c..000000000 --- a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './MostImpactfulErrors'; diff --git a/frontend/app/components/Dashboard/Widgets/PageMetrics.js b/frontend/app/components/Dashboard/Widgets/PageMetrics.js deleted file mode 100644 index 0df75ae99..000000000 --- a/frontend/app/components/Dashboard/Widgets/PageMetrics.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { Loader } from 'UI'; -import { CountBadge, Divider, widgetHOC } from './common'; - -@widgetHOC('pageMetrics') -export default class PageMetrics extends React.PureComponent { - render() { - const { data, loading } = this.props; - return ( -
- - - - - -
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/Performance/Performance.js b/frontend/app/components/Dashboard/Widgets/Performance/Performance.js deleted file mode 100644 index 6c585305f..000000000 --- a/frontend/app/components/Dashboard/Widgets/Performance/Performance.js +++ /dev/null @@ -1,198 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import { Map } from 'immutable'; -import cn from 'classnames'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Legend } from 'recharts'; -import { Loader, TextEllipsis, Tooltip } from 'UI'; -import { TYPES } from 'Types/resource'; -import { LAST_24_HOURS, LAST_30_MINUTES, LAST_7_DAYS, LAST_30_DAYS } from 'Types/app/period'; -import { fetchPerformanseSearch } from 'Duck/dashboard'; -import { widgetHOC } from '../common'; - -import styles from './performance.module.css'; - -const BASE_KEY = 'resource'; - -const pagesColor = '#7FCC33'; -const imagesColor = '#40C4FF'; -const requestsColor = '#DAB72F'; - -@widgetHOC('performance', { fullwidth: true }) -@connect((state, props) => ({ - performanceChartSpecified: state.getIn([ 'dashboard', 'performanceChart' ]), - period: state.getIn([ 'dashboard', 'period' ]), - loading: state.getIn([ 'dashboard', 'performanceSearchRequest', 'loading' ]) || - props.loading, -}), { - fetchPerformanseSearch, -}) -export default class Performance extends React.PureComponent { - state = { - comparing: false, - resources: Map(), - opacity: {}, - } - - onResourceSelect = (resource) => { - if (!resource || this.state.resources.size > 1) return; - - resource.fillColor = this.getFillColor(resource); - resource.strokeColor = this.getStrokeColor(resource); - this.setResources(this.state.resources.set(this.state.resources.size, resource)); - } - - onResourceSelect0 = resource => this.onResourceSelect(0, resource) - onResourceSelect1 = resource => this.onResourceSelect(1, resource) - - getInterval = () => { - switch (this.props.period.rangeName) { - case LAST_30_MINUTES: - return 0; - case LAST_24_HOURS: - return 2; - case LAST_7_DAYS: - return 3; - case LAST_30_DAYS: - return 2; - default: - return 0; - } - } - - setResources = (resources) => { - this.setState({ - resources, - }); - this.props.fetchPerformanseSearch({ - ...this.props.period.toTimestamps(), - resources: resources.valueSeq().toJS(), - }); - } - - getFillColor = (resource) => { - switch (resource.type) { - case TYPES.IMAGE: - return 'url(#colorAvgImageLoadTime)'; - case TYPES.PAGE: - return 'url(#colorAvgPageLoadTime)'; - case TYPES.REQUEST: - return 'url(#colorAvgRequestLoadTime)'; - default: - return 'blue'; - } - } - - getStrokeColor = (resource) => { - switch (resource.type) { - case TYPES.IMAGE: - return imagesColor; - case TYPES.PAGE: - return pagesColor; - case TYPES.REQUEST: - return requestsColor; - default: - return 'blue'; - } - } - - removeResource = (index) => { - this.setResources(this.state.resources.remove(index)); - } - - compare = () => this.setState({ comparing: true }) - - legendPopup = (component, trigger) => {trigger} - - legendFormatter = (value, entry, index) => { - const { opacity } = this.state; - - if (value === 'avgPageLoadTime') return (this.legendPopup(opacity.avgPageLoadTime === 0 ? 'Show' : 'Hide', {'Pages'})); - if (value === 'avgRequestLoadTime') return (this.legendPopup(opacity.avgRequestLoadTime === 0 ? 'Show' : 'Hide', {'Requests'})); - if (value === 'avgImageLoadTime') return (this.legendPopup(opacity.avgImageLoadTime === 0 ? 'Show' : 'Hide', {'Images'})); - // if (value === 'avgImageLoadTime') return ({'Images'}); - if (value.includes(BASE_KEY)) { - const resourceIndex = Number.parseInt(value.substr(BASE_KEY.length)); - return ( - - - - ); - } - } - - handleLegendClick = (legend) => { - const { dataKey } = legend; - const { opacity } = this.state; - - if (dataKey === 'resource0') { - this.removeResource(0); - } else if (dataKey === 'resource1') { - this.removeResource(1); - } else { - this.setState({ - opacity: { ...opacity, [ dataKey ]: opacity[ dataKey ] === 0 ? 1 : 0 }, - }); - } - } - - // eslint-disable-next-line complexity - render() { - const { comparing, resources, opacity } = this.state; - const { data, performanceChartSpecified, loading } = this.props; - const r0Presented = !!resources.get(0); - const r1Presented = !!resources.get(1); - const resourcesPresented = r0Presented || r1Presented; - const defaultData = !resourcesPresented || performanceChartSpecified.length === 0; // TODO: more safe? - const interval = this.getInterval(); - - return ( - -
-
-
-
- - - - - - - - - - - - - - - - - - - - - { defaultData && } - { defaultData && } - { defaultData && } - { !defaultData && r0Presented && } - { !defaultData && r1Presented && } - - - - -
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/Performance/index.js b/frontend/app/components/Dashboard/Widgets/Performance/index.js deleted file mode 100644 index 63b30d682..000000000 --- a/frontend/app/components/Dashboard/Widgets/Performance/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Performance'; diff --git a/frontend/app/components/Dashboard/Widgets/Performance/performance.module.css b/frontend/app/components/Dashboard/Widgets/Performance/performance.module.css deleted file mode 100644 index ab974bdc3..000000000 --- a/frontend/app/components/Dashboard/Widgets/Performance/performance.module.css +++ /dev/null @@ -1,3 +0,0 @@ -.muted { - color: rgba(0, 0, 0, 0.3); -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.tsx index f29ef22ac..f28f81fff 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.tsx @@ -2,9 +2,8 @@ import React from 'react'; import { NoContent } from 'UI'; import { Styles } from '../../common'; import { - AreaChart, Area, BarChart, Bar, CartesianGrid, Tooltip, - LineChart, Line, Legend, ResponsiveContainer, + Legend, ResponsiveContainer, XAxis, YAxis } from 'recharts'; @@ -20,7 +19,7 @@ function BreakdownOfLoadedResources(props: Props) { diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx index 9fc69d018..3ef08f392 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { Styles, Table } from '../../common'; +import { NoContent } from 'UI'; +import { Table } from '../../common'; import { getRE } from 'App/utils'; import ImageInfo from './ImageInfo'; import MethodType from './MethodType'; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx index 09c86b60c..ee8bb056f 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { NoContent } from 'UI'; import { Styles } from '../../common'; import { - BarChart, Bar, CartesianGrid, Tooltip, + CartesianGrid, Tooltip, LineChart, Line, Legend, ResponsiveContainer, XAxis, YAxis } from 'recharts'; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx index 30463860c..ff801eb07 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx @@ -3,8 +3,8 @@ import { NoContent } from 'UI'; import { Styles } from '../../common'; import { AreaChart, Area, - BarChart, Bar, CartesianGrid, Tooltip, - LineChart, Line, Legend, ResponsiveContainer, + CartesianGrid, Tooltip, + ResponsiveContainer, XAxis, YAxis } from 'recharts'; import { NO_METRIC_DATA } from 'App/constants/messages' diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx index e405ba422..cd95853fe 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx @@ -20,7 +20,7 @@ function ErrorsByOrigin(props: Props) { diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx index ec952487c..d2acb58a5 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx @@ -3,7 +3,7 @@ import { NoContent } from 'UI'; import { Styles } from '../../common'; import { BarChart, Bar, CartesianGrid, Tooltip, - LineChart, Line, Legend, ResponsiveContainer, + Legend, ResponsiveContainer, XAxis, YAxis } from 'recharts'; import { NO_METRIC_DATA } from 'App/constants/messages' diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.js b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/Bar.tsx similarity index 90% rename from frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.js rename to frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/Bar.tsx index 14f52349d..163c7d0a5 100644 --- a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.js +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/Bar.tsx @@ -1,5 +1,5 @@ import React from 'react' -import stl from './Bar.module.css' +import stl from './bar.module.css' const Bar = ({ className = '', width = 0, avg, domain, color }) => { return ( @@ -15,4 +15,4 @@ const Bar = ({ className = '', width = 0, avg, domain, color }) => { ) } -export default Bar +export default Bar \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx index 13643c769..d9e773948 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { NoContent } from 'UI'; import { Styles } from '../../common'; import { numberWithCommas } from 'App/utils'; -import Bar from 'App/components/Dashboard/Widgets/ErrorsPerDomain/Bar'; +import Bar from './Bar'; import { NO_METRIC_DATA } from 'App/constants/messages' interface Props { diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/bar.module.css b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/bar.module.css new file mode 100644 index 000000000..6dfde11a5 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/bar.module.css @@ -0,0 +1,6 @@ +.bar { + height: 5px; + background-color: red; + width: 100%; + border-radius: 3px; +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx index 5a5efb961..2c534a7fa 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx @@ -3,8 +3,8 @@ import { NoContent } from 'UI'; import { Styles, AvgLabel } from '../../common'; import { AreaChart, Area, - BarChart, Bar, CartesianGrid, Tooltip, - LineChart, Line, Legend, ResponsiveContainer, + CartesianGrid, Tooltip, + ResponsiveContainer, XAxis, YAxis } from 'recharts'; import { NO_METRIC_DATA } from 'App/constants/messages' diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx index 6fb22c784..3a3a491b0 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx @@ -3,8 +3,8 @@ import { NoContent } from 'UI'; import { Styles, AvgLabel } from '../../common'; import { AreaChart, Area, - BarChart, Bar, CartesianGrid, Tooltip, - LineChart, Line, Legend, ResponsiveContainer, + CartesianGrid, Tooltip, + ResponsiveContainer, XAxis, YAxis } from 'recharts'; import { NO_METRIC_DATA } from 'App/constants/messages' diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MissingResources/MissingResources.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MissingResources/MissingResources.tsx index c0b3767e2..6a2b5ed80 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MissingResources/MissingResources.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MissingResources/MissingResources.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import { NoContent } from 'UI'; -import { Styles, Table } from '../../common'; +import { Table } from '../../common'; import { List } from 'immutable'; import Chart from './Chart'; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx index 548a229ab..687778b5f 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { Loader, NoContent } from 'UI'; +import { NoContent } from 'UI'; import { Styles, AvgLabel } from '../../common'; import { ComposedChart, Bar, BarChart, CartesianGrid, ResponsiveContainer, - XAxis, YAxis, ReferenceLine, Tooltip, Legend + XAxis, YAxis, ReferenceLine, Tooltip } from 'recharts'; import { NO_METRIC_DATA } from 'App/constants/messages' @@ -87,7 +87,7 @@ function ResponseTimeDistribution(props: Props) { /> 'Page Response Time: ' + val} /> - { metric.data.percentiles.map((item, i) => ( + { metric.data.percentiles && metric.data.percentiles.map((item: any, i: number) => ( { ) } -export default Bar +export default Bar \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx index fa4b703f2..758a6575a 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { NoContent } from 'UI'; import { Styles } from '../../common'; import { numberWithCommas } from 'App/utils'; -import Bar from 'App/components/Dashboard/Widgets/SlowestDomains/Bar'; +import Bar from './Bar'; import { NO_METRIC_DATA } from 'App/constants/messages' interface Props { diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/SlowestResources.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/SlowestResources.tsx index ae873f2fb..e514d9c18 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/SlowestResources.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestResources/SlowestResources.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import { NoContent } from 'UI'; -import { Styles, Table } from '../../common'; +import { Table } from '../../common'; import { List } from 'immutable'; import { numberWithCommas } from 'App/utils'; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx index 82603eee5..ca049a677 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx @@ -24,6 +24,7 @@ function SpeedIndexByLocation(props: Props) { const max = metric.data.chart.reduce((acc: any, item: any) => Math.max(acc, item.value), 0); const min = metric.data.chart.reduce((acc: any, item: any) => Math.min(acc, item.value), 0); metric.data.chart.forEach((item: any) => { + if (!item || !item.userCountry) { return } item.perNumber = positionOfTheNumber(min, max, item.value, 5); data[item.userCountry.toLowerCase()] = item; }); diff --git a/frontend/app/components/Dashboard/Widgets/ProcessedSessions.js b/frontend/app/components/Dashboard/Widgets/ProcessedSessions.js deleted file mode 100644 index 2e6d3e743..000000000 --- a/frontend/app/components/Dashboard/Widgets/ProcessedSessions.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area } from 'recharts'; -import { Loader } from 'UI'; -import { CountBadge, domain, widgetHOC, Styles } from './common'; - -@widgetHOC('sessions', { trendChart: true, fitContent: true }) -export default class ProcessedSessions extends React.PureComponent { - render() { - const { data, loading } = this.props; - const isMoreThanK = data.count > 1000; - const countView = isMoreThanK ? Math.trunc(data.count / 1000) : data.count; - - return ( -
- - - - - - - - - - - - - - - - -
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.js b/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.js deleted file mode 100644 index d04a5cef5..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.js +++ /dev/null @@ -1,77 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { ComposedChart, Bar, CartesianGrid, Line, Legend, ResponsiveContainer, - XAxis, YAxis, Tooltip -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 30 } - - if (rangeName === LAST_24_HOURS) params.density = 24 - if (rangeName === LAST_30_MINUTES) params.density = 18 - if (rangeName === YESTERDAY) params.density = 24 - if (rangeName === LAST_7_DAYS) params.density = 30 - - return params -} - -@widgetHOC('resourceTypeVsResponseEnd', { customParams }) -export default class ResourceLoadedVsResponseEnd extends React.PureComponent { - render() { - const { data, loading, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - - return ( - - - - - - - Styles.tickFormatter(val, 'ms')} - yAxisId="left" - /> - Styles.tickFormatter(val, 'ms')} - label={{ - ...Styles.axisLabelLeft, - offset: 70, - value: "Response End (ms)" - }} - yAxisId="right" - orientation="right" - /> - - - - - - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/index.js b/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/index.js deleted file mode 100644 index e56d66bcd..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ResourceLoadedVsResponseEnd'; diff --git a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsVisuallyComplete/ResourceLoadedVsVisuallyComplete.js b/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsVisuallyComplete/ResourceLoadedVsVisuallyComplete.js deleted file mode 100644 index b56d6fed3..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsVisuallyComplete/ResourceLoadedVsVisuallyComplete.js +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { - ComposedChart, Bar, CartesianGrid, Line, Legend, ResponsiveContainer, - XAxis, YAxis, Tooltip -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 21 } - - if (rangeName === LAST_24_HOURS) params.density = 21 - if (rangeName === LAST_30_MINUTES) params.density = 21 - if (rangeName === YESTERDAY) params.density = 21 - if (rangeName === LAST_7_DAYS) params.density = 21 - - return params -} - -@widgetHOC('resourcesVsVisuallyComplete', { customParams }) -export default class ResourceLoadedVsVisuallyComplete extends React.PureComponent { - render() { - const {className, data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - - return ( - - - - - - - Styles.tickFormatter(val, 'ms')} - /> - Styles.tickFormatter(val)} - /> - - - - - - - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsVisuallyComplete/index.js b/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsVisuallyComplete/index.js deleted file mode 100644 index 6f3621b2e..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsVisuallyComplete/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ResourceLoadedVsVisuallyComplete'; diff --git a/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/ResourceLoadingTime.js b/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/ResourceLoadingTime.js deleted file mode 100644 index 8f95a3479..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/ResourceLoadingTime.js +++ /dev/null @@ -1,136 +0,0 @@ -import React from 'react'; -import { Loader, NoContent, DropdownPlain } from 'UI'; -import { widgetHOC, Styles, AvgLabel } from '../common'; -import { withRequest } from 'HOCs'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; -import WidgetAutoComplete from 'Shared/WidgetAutoComplete'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const WIDGET_KEY = 'resourcesLoadingTime'; -const toUnderscore = s => s.split(/(?=[A-Z])/).join('_').toLowerCase(); - -// other' = -1, 'script' = 0, 'stylesheet' = 1, 'fetch' = 2, 'img' = 3, 'media' = 4 -export const RESOURCE_OPTIONS = [ - { text: 'All', value: 'all', }, - { text: 'JS', value: "SCRIPT", }, - { text: 'CSS', value: "STYLESHEET", }, - { text: 'Fetch', value: "REQUEST", }, - { text: 'Image', value: "IMG", }, - { text: 'Media', value: "MEDIA", }, - { text: 'Other', value: "OTHER", }, -]; - -const customParams = rangeName => { - const params = {density: 70, type: null } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@withRequest({ - dataName: "options", - initialData: [], - dataWrapper: data => data, - loadingName: 'optionsLoading', - requestName: "fetchOptions", - endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', - method: 'GET' -}) -@widgetHOC(WIDGET_KEY, { customParams }) -export default class ResourceLoadingTime extends React.PureComponent { - state = { autoCompleteSelected: null, type: null } - onSelect = (params) => { - const _params = customParams(this.props.period.rangeName) - this.setState({ autoCompleteSelected: params.value }); - this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, { ..._params, url: params.value }) - } - - writeOption = (e, { name, value }) => { - this.setState({ [name]: value }) - const _params = customParams(this.props.period.rangeName) - this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, { ..._params, [ name ]: value === 'all' ? null : value }) - } - - render() { - const { data, loading, period, optionsLoading, compare = false, showSync = false } = this.props; - const { autoCompleteSelected, type } = this.state; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); - - return ( - - -
- - - -
- - - - - {gradientDef} - - - Styles.tickFormatter(val)} - label={{ ...Styles.axisLabelLeft, value: "Resource Fetch Time (ms)" }} - /> - - - - - - -
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/index.js b/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/index.js deleted file mode 100644 index 91072b5ec..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ResourceLoadingTime'; diff --git a/frontend/app/components/Dashboard/Widgets/ResponseTime/ResponseTime.js b/frontend/app/components/Dashboard/Widgets/ResponseTime/ResponseTime.js deleted file mode 100644 index 5bd400d37..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResponseTime/ResponseTime.js +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, domain, Styles, AvgLabel } from '../common'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; -import WidgetAutoComplete from 'Shared/WidgetAutoComplete'; -import { withRequest } from 'HOCs'; -import { toUnderscore } from 'App/utils'; - -const WIDGET_KEY = 'pagesResponseTime'; - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@withRequest({ - dataName: "options", - initialData: [], - dataWrapper: data => data, - // resetBeforeRequest: true, - loadingName: "optionsLoading", - requestName: "fetchOptions", - endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', - method: 'GET' -}) -@widgetHOC(WIDGET_KEY, { customParams }) -export default class ResponseTime extends React.PureComponent { - onSelect = (params) => { - const _params = customParams(this.props.period.rangeName) - this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, {..._params, url: params.value }, this.props.filters) - } - - render() { - const { data, loading, optionsLoading, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const gradientDef = Styles.gradientDef(); - - return ( - -
- -
- -
-
- - - - - {gradientDef} - - - `${val}` } - label={{ ...Styles.axisLabelLeft, value: "Page Response Time (ms)" }} - /> - - - - - - -
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ResponseTime/index.js b/frontend/app/components/Dashboard/Widgets/ResponseTime/index.js deleted file mode 100644 index c4228f56e..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResponseTime/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ResponseTime'; diff --git a/frontend/app/components/Dashboard/Widgets/ResponseTimeDistribution/ResponseTimeDistribution.js b/frontend/app/components/Dashboard/Widgets/ResponseTimeDistribution/ResponseTimeDistribution.js deleted file mode 100644 index ea4762fb1..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResponseTimeDistribution/ResponseTimeDistribution.js +++ /dev/null @@ -1,140 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles, AvgLabel } from '../common'; -import { - ComposedChart, Bar, BarChart, CartesianGrid, ResponsiveContainer, - XAxis, YAxis, ReferenceLine, Tooltip -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 40 } - - if (rangeName === LAST_24_HOURS) params.density = 40 - if (rangeName === LAST_30_MINUTES) params.density = 40 - if (rangeName === YESTERDAY) params.density = 40 - if (rangeName === LAST_7_DAYS) params.density = 40 - - return params -} - -const PercentileLine = props => { - const { - viewBox: { x, y }, - xoffset, - yheight, - height, - label - } = props; - return ( - - - - {label} - - - ); -}; - -@widgetHOC('pagesResponseTimeDistribution', { customParams }) -export default class ResponseTimeDistribution extends React.PureComponent { - render() { - const { data, loading, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - - return ( - - -
- -
-
- - - - - - - 'Page Response Time: ' + val} /> - { data.percentiles.map((item, i) => ( - - } - allowDecimals={false} - x={item.responseTime} - strokeWidth={0} - strokeOpacity={1} - /> - ))} - - - - - - - - - - - -
-
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/ResponseTimeDistribution/index.js b/frontend/app/components/Dashboard/Widgets/ResponseTimeDistribution/index.js deleted file mode 100644 index 2ddc12a3c..000000000 --- a/frontend/app/components/Dashboard/Widgets/ResponseTimeDistribution/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ResponseTimeDistribution'; diff --git a/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.js b/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.js deleted file mode 100644 index 057122195..000000000 --- a/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.js +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles, AvgLabel } from '../common'; -import { - ComposedChart, Bar, CartesianGrid, Legend, ResponsiveContainer, - XAxis, YAxis, Tooltip -} from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 28 } - - if (rangeName === LAST_24_HOURS) params.density = 28 - if (rangeName === LAST_30_MINUTES) params.density = 28 - if (rangeName === YESTERDAY) params.density = 28 - if (rangeName === LAST_7_DAYS) params.density = 28 - - return params -} - -@widgetHOC('impactedSessionsByJsErrors', { customParams }) -export default class SessionsAffectedByJSErrors extends React.PureComponent { - render() { - const { data, loading, period, compare, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - - return ( - -
-
- -
-
- - - - - - - - - - - - - -
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/index.js b/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/index.js deleted file mode 100644 index 2af7f10db..000000000 --- a/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SessionsAffectedByJSErrors'; diff --git a/frontend/app/components/Dashboard/Widgets/SessionsImpactedBySlowRequests/SessionsImpactedBySlowRequests.js b/frontend/app/components/Dashboard/Widgets/SessionsImpactedBySlowRequests/SessionsImpactedBySlowRequests.js deleted file mode 100644 index af321c1c0..000000000 --- a/frontend/app/components/Dashboard/Widgets/SessionsImpactedBySlowRequests/SessionsImpactedBySlowRequests.js +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@widgetHOC('impactedSessionsBySlowPages', { customParams }) -export default class SessionsImpactedBySlowRequests extends React.PureComponent { - render() { - const { data, loading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); - - return ( - - - - - {gradientDef} - - - - - - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/SessionsImpactedBySlowRequests/index.js b/frontend/app/components/Dashboard/Widgets/SessionsImpactedBySlowRequests/index.js deleted file mode 100644 index 7e4adcc74..000000000 --- a/frontend/app/components/Dashboard/Widgets/SessionsImpactedBySlowRequests/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SessionsImpactedBySlowRequests'; diff --git a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.js b/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.js index ffcace6ba..e69de29bb 100644 --- a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.js +++ b/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.js @@ -1,34 +0,0 @@ -import React from 'react' -import stl from './Bar.module.css' -import { Styles } from '../common' -import { TextEllipsis } from 'UI'; - -const Bar = ({ className = '', versions = [], width = 0, avg, domain, colors }) => { - return ( -
-
-
- {versions.map((v, i) => { - const w = (v.value * 100)/ avg; - return ( -
- -
Version: {v.key}
-
Sessions: {v.value}
-
- } /> -
- ) - })} -
-
- {`${avg}`} -
-
-
{domain}
- - ) -} - -export default Bar \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.module.css b/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.module.css deleted file mode 100644 index dde6009e4..000000000 --- a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/Bar.module.css +++ /dev/null @@ -1,20 +0,0 @@ -.bar { - height: 5px; - width: 100%; - border-radius: 3px; - display: flex; - align-items: center; - & div { - padding: 0 5px; - height: 20px; - color: #FFF; - } - & div:first-child { - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; - } - & div:last-child { - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; - } -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/SessionsPerBrowser.js b/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/SessionsPerBrowser.js deleted file mode 100644 index 3179f64da..000000000 --- a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/SessionsPerBrowser.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, AvgLabel, Styles } from '../common'; -import Bar from './Bar'; - -@widgetHOC('sessionsPerBrowser') -export default class SessionsPerBrowser extends React.PureComponent { - - getVersions = item => { - return Object.keys(item) - .filter(i => i !== 'browser' && i !== 'count') - .map(i => ({ key: 'v' +i, value: item[i]})) - } - - render() { - const { data, loading, compare = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const firstAvg = data.chart[0] && data.chart[0].count; - - return ( - - -
- {data.chart.map((item, i) => - - )} -
-
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/index.js b/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/index.js deleted file mode 100644 index facd495bd..000000000 --- a/frontend/app/components/Dashboard/Widgets/SessionsPerBrowser/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SessionsPerBrowser'; diff --git a/frontend/app/components/Dashboard/Widgets/SlowestDomains/Bar.module.css b/frontend/app/components/Dashboard/Widgets/SlowestDomains/Bar.module.css deleted file mode 100644 index d3d399918..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestDomains/Bar.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.bar { - height: 10px; - background-color: red; - width: 100%; - border-radius: 3px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/SlowestDomains/SlowestDomains.js b/frontend/app/components/Dashboard/Widgets/SlowestDomains/SlowestDomains.js deleted file mode 100644 index 9f85ae412..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestDomains/SlowestDomains.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import Bar from './Bar'; -import { numberWithCommas } from 'App/utils'; - -@widgetHOC('slowestDomains') -export default class ResponseTime extends React.PureComponent { - render() { - const { data, loading, compare = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const firstAvg = data.partition.first() && data.partition.first().avg; - - return ( - - -
- {data.partition && data.partition.map((item, i) => - - )} -
-
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/SlowestDomains/index.js b/frontend/app/components/Dashboard/Widgets/SlowestDomains/index.js deleted file mode 100644 index 87f1e002a..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestDomains/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SlowestDomains'; diff --git a/frontend/app/components/Dashboard/Widgets/SlowestImages/Chart.js b/frontend/app/components/Dashboard/Widgets/SlowestImages/Chart.js deleted file mode 100644 index a66c2801d..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestImages/Chart.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { AreaChart, Area } from 'recharts'; - -const Chart = ({ data }) => { - return ( - - - - ); -} - -Chart.displayName = 'Chart'; - -export default Chart; diff --git a/frontend/app/components/Dashboard/Widgets/SlowestImages/ImageInfo.js b/frontend/app/components/Dashboard/Widgets/SlowestImages/ImageInfo.js deleted file mode 100644 index cf7402a80..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestImages/ImageInfo.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { Tooltip, Icon } from 'UI'; -import styles from './imageInfo.module.css'; - -const ImageInfo = ({ data }) => ( -
- - } - > -
- -
{'Preview'}
-
-
- - {data.name} - -
-); - -ImageInfo.displayName = 'ImageInfo'; - -export default ImageInfo; diff --git a/frontend/app/components/Dashboard/Widgets/SlowestImages/SlowestImages.js b/frontend/app/components/Dashboard/Widgets/SlowestImages/SlowestImages.js deleted file mode 100644 index 7bfc0cfd9..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestImages/SlowestImages.js +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { Table, widgetHOC } from '../common'; -import Chart from './Chart'; -import ImageInfo from './ImageInfo'; - -const cols = [ - { - key: 'image', - title: 'Image', - Component: ImageInfo, - width: '40%', - }, - { - key: 'avgDuration', - title: 'Load Time', - toText: time => `${ Math.trunc(time) }ms`, - width: '25%', - }, - { - key: 'trend', - title: 'Trend', - Component: Chart, - width: '20%', - }, - { - key: 'sessions', - title: 'Sessions', - width: '15%', - toText: count => `${ count > 1000 ? Math.trunc(count / 1000) : count }${ count > 1000 ? 'k' : '' }`, - className: 'text-left' - }, -]; - -@widgetHOC('slowestImages', { fitContent: true }) -export default class SlowestImages extends React.PureComponent { - render() { - const { data: images, loading } = this.props; - return ( - - -
- - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/SlowestImages/imageInfo.module.css b/frontend/app/components/Dashboard/Widgets/SlowestImages/imageInfo.module.css deleted file mode 100644 index 69030a582..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestImages/imageInfo.module.css +++ /dev/null @@ -1,39 +0,0 @@ -.name { - display: flex; - align-items: center; - - & > span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 60%; - } -} - -.imagePreview { - max-width: 200px; - max-height: 200px; -} - -.imageWrapper { - display: flex; - flex-flow: column; - align-items: center; - width: 40px; - text-align: center; - margin-right: 10px; - & > span { - height: 16px; - } - & .label { - font-size: 9px; - color: $gray-light; - } -} - -.popup { - background-color: #f5f5f5 !important; - &:before { - background-color: #f5f5f5 !important; - } -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/SlowestImages/index.js b/frontend/app/components/Dashboard/Widgets/SlowestImages/index.js deleted file mode 100644 index 54bcac137..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestImages/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SlowestImages'; diff --git a/frontend/app/components/Dashboard/Widgets/SlowestResources/Chart.js b/frontend/app/components/Dashboard/Widgets/SlowestResources/Chart.js deleted file mode 100644 index 1990733fb..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestResources/Chart.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import { AreaChart, Area } from 'recharts'; -import { Styles } from '../common'; - -const Chart = ({ data, compare }) => { - const colors = compare ? Styles.compareColors : Styles.colors; - return ( - - - - ); -} - -Chart.displayName = 'Chart'; - -export default Chart; diff --git a/frontend/app/components/Dashboard/Widgets/SlowestResources/CopyPath.js b/frontend/app/components/Dashboard/Widgets/SlowestResources/CopyPath.js deleted file mode 100644 index 6b7e709e7..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestResources/CopyPath.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react' -import copy from 'copy-to-clipboard' -import { useState } from 'react' - -const CopyPath = ({ data }) => { - const [copied, setCopied] = useState(false) - - const copyHandler = () => { - copy(data.url); - setCopied(true); - setTimeout(function() { - setCopied(false) - }, 500); - } - - return ( -
- { copied ? 'Copied' : 'Copy Path'} -
- ) -} - -export default CopyPath diff --git a/frontend/app/components/Dashboard/Widgets/SlowestResources/ImageInfo.js b/frontend/app/components/Dashboard/Widgets/SlowestResources/ImageInfo.js deleted file mode 100644 index c8b3890e3..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestResources/ImageInfo.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { Tooltip } from 'UI'; -import cn from 'classnames'; -import styles from './imageInfo.module.css'; - -const supportedTypes = ['png', 'jpg', 'jpeg', 'svg']; - -const ImageInfo = ({ data }) => { - const canPreview = supportedTypes.includes(data.type); - return ( -
- - } - > -
-
{data.name}
-
-
-
- ); -}; - -ImageInfo.displayName = 'ImageInfo'; - -export default ImageInfo; diff --git a/frontend/app/components/Dashboard/Widgets/SlowestResources/ResourceType.js b/frontend/app/components/Dashboard/Widgets/SlowestResources/ResourceType.js deleted file mode 100644 index 9803a050f..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestResources/ResourceType.js +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react' -import cn from 'classnames' - -const ResourceType = ({ data : { type = 'js' }, compare }) => { - return ( -
- { type.toUpperCase() } -
- ) -} - -export default ResourceType diff --git a/frontend/app/components/Dashboard/Widgets/SlowestResources/SlowestResources.js b/frontend/app/components/Dashboard/Widgets/SlowestResources/SlowestResources.js deleted file mode 100644 index 4de486e49..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestResources/SlowestResources.js +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react'; -import { Loader, NoContent, DropdownPlain } from 'UI'; -import { Table, widgetHOC } from '../common'; -import Chart from './Chart'; -import ImageInfo from './ImageInfo'; -import { getRE } from 'App/utils'; -import cn from 'classnames'; -import stl from './SlowestResources.module.css'; -import ResourceType from './ResourceType'; -import CopyPath from './CopyPath'; -import { numberWithCommas } from 'App/utils'; - -export const RESOURCE_OPTIONS = [ - { text: 'All', value: 'ALL', }, - { text: 'CSS', value: 'STYLESHEET', }, - { text: 'JS', value: 'SCRIPT', }, -]; - -const cols = [ - { - key: 'type', - title: 'Type', - Component: ResourceType, - className: 'text-center justify-center', - cellClass: 'ml-2', - width: '8%', - }, - { - key: 'name', - title: 'File Name', - Component: ImageInfo, - cellClass: '-ml-2', - width: '40%', - }, - { - key: 'avg', - title: 'Load Time', - toText: avg => `${ avg ? numberWithCommas(Math.trunc(avg)) : 0} ms`, - className: 'justify-center', - width: '15%', - }, - { - key: 'trend', - title: 'Trend', - Component: Chart, - width: '15%', - }, - { - key: 'copy-path', - title: '', - Component: CopyPath, - cellClass: 'invisible group-hover:visible text-right', - width: '15%', - } -]; -const WIDGET_KEY = 'slowestResources' - -@widgetHOC(WIDGET_KEY, { fitContent: true }) -export default class SlowestResources extends React.PureComponent { - state = { resource: 'all', search: ''} - - test = (key, value = '') => getRE(key, 'i').test(value); - - write = ({ target: { name, value } }) => { - this.setState({ [ name ]: value }) - }; - - writeOption = (e, { name, value }) => { - this.setState({ [ name ]: value }) - this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, { [ name ]: value === 'all' ? null : value }) - } - - render() { - const { data, loading, compare, isTemplate } = this.props; - - return ( -
-
- -
- - -
- - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/SlowestResources/SlowestResources.module.css b/frontend/app/components/Dashboard/Widgets/SlowestResources/SlowestResources.module.css deleted file mode 100644 index 42a6c55a1..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestResources/SlowestResources.module.css +++ /dev/null @@ -1,7 +0,0 @@ -.topActions { - position: absolute; - top: 0px; - right: 50px; - display: flex; - justify-content: flex-end; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/SlowestResources/imageInfo.module.css b/frontend/app/components/Dashboard/Widgets/SlowestResources/imageInfo.module.css deleted file mode 100644 index 1de36b529..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestResources/imageInfo.module.css +++ /dev/null @@ -1,52 +0,0 @@ -.name { - display: flex; - align-items: center; - - & > span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 60%; - } - - & .label { - max-width: 300px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } -} - -.hasPreview { - /* text-decoration: underline; */ - border-bottom: 1px dotted; - cursor: pointer; -} - -.imagePreview { - max-width: 200px; - max-height: 200px; -} - -.imageWrapper { - display: flex; - flex-flow: column; - align-items: center; - width: 40px; - text-align: center; - margin-right: 10px; - & > span { - height: 16px; - } - & .label { - font-size: 9px; - color: $gray-light; - } -} - -.popup { - background-color: #f5f5f5 !important; - &:before { - background-color: #f5f5f5 !important; - } -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/SlowestResources/index.js b/frontend/app/components/Dashboard/Widgets/SlowestResources/index.js deleted file mode 100644 index 22fd02391..000000000 --- a/frontend/app/components/Dashboard/Widgets/SlowestResources/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SlowestResources'; diff --git a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Bar.js b/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Bar.js deleted file mode 100644 index 8702869e9..000000000 --- a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Bar.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import stl from './Bar.module.css' - -const Bar = ({ className = '', width = 0, avg, domain, color }) => { - return ( -
-
-
-
{avg}
-
-
{domain}
-
- ) -} - -export default Bar diff --git a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Bar.module.css b/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Bar.module.css deleted file mode 100644 index d3d399918..000000000 --- a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Bar.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.bar { - height: 10px; - background-color: red; - width: 100%; - border-radius: 3px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Scale.js b/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Scale.js deleted file mode 100644 index 6ee56f2b3..000000000 --- a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/Scale.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react' -import { Styles } from '../common'; -import cn from 'classnames'; -import stl from './scale.module.css'; - -function Scale({ colors }) { - const lastIndex = (Styles.colors.length - 1) - return ( -
- {colors.map((c, i) => ( -
- { i === 0 &&
Slow
} - { i === lastIndex &&
Fast
} -
- ))} -
- ) -} - -export default Scale diff --git a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/SpeedIndexLocation.js b/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/SpeedIndexLocation.js deleted file mode 100644 index 9e1a23213..000000000 --- a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/SpeedIndexLocation.js +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import { widgetHOC, AvgLabel, Styles } from '../common'; -import * as DataMap from "datamaps"; -import { threeLetter } from 'App/constants/countries'; -import Scale from './Scale'; -import { numberWithCommas } from 'App/utils'; -import stl from './speedIndexLocation.module.css'; -import { colorScale } from 'App/utils'; - -@widgetHOC('speedLocation', { fitContent: false }) -export default class SpeedIndexLocation extends React.PureComponent { - wrapper = React.createRef() - map = null; - - getSeries = data => { - const series = []; - data.chart.forEach(item => { - const d = [threeLetter[item.userCountry], Math.round(item.avg)] - series.push(d) - }) - - return series; - } - - componentDidUpdate(prevProps) { - if (this.map) { - this.map.updateChoropleth(this.getDataset(), { reset: true}); - } - } - - getDataset = () => { - const { data, compare } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - - var dataset = {}; - const series = this.getSeries(data); - var onlyValues = series.map(function(obj){ return obj[1]; }); - const paletteScale = colorScale(onlyValues, [...colors].reverse()); - - // fill dataset in appropriate format - series.forEach(function(item){ - var iso = item[0], value = item[1]; - dataset[iso] = { numberOfThings: value, fillColor: paletteScale(value) }; - }); - return dataset; - } - - render() { - const { data, loading, compare = false } = this.props; - - - if (this.wrapper.current && !this.map && data.chart.length > 0) { - const dataset = this.getDataset(); - this.map = new DataMap({ - element: this.wrapper.current, - fills: { defaultFill: '#E8E8E8' }, - data: dataset, - // responsive: true, - // height: null, //if not null, datamaps will grab the height of 'element' - // width: null, //if not null, datamaps will grab the width of 'element' - geographyConfig: { - borderColor: '#FFFFFF', - borderWidth: 0.5, - highlightBorderWidth: 1, - popupOnHover: true, - // don't change color on mouse hover - highlightFillColor: function(geo) { - return '#999999'; - // return geo['fillColor'] || '#F5F5F5'; - }, - // only change border - highlightBorderColor: '#B7B7B7', - // show desired information in tooltip - popupTemplate: function(geo, data) { - // don't show tooltip if country don't present in dataset - if (!data) { return ; } - // tooltip content - return ['
', - '', geo.properties.name, '', - 'Avg: ', numberWithCommas(data.numberOfThings), '', - '
'].join(''); - } - } - }); - } - - return ( - <> -
- -
- -
- - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/index.js b/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/index.js deleted file mode 100644 index d62ce8efc..000000000 --- a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SpeedIndexLocation'; diff --git a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/scale.module.css b/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/scale.module.css deleted file mode 100644 index 5aa34f966..000000000 --- a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/scale.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.bars { - & div:first-child { - border-top-left-radius: 3px; - border-top-right-radius: 3px; - } - - & div:last-child { - border-bottom-left-radius: 3px; - border-bottom-right-radius: 3px; - } -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/speedIndexLocation.module.css b/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/speedIndexLocation.module.css deleted file mode 100644 index 1a433dc85..000000000 --- a/frontend/app/components/Dashboard/Widgets/SpeedIndexLocation/speedIndexLocation.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.mapWrapper { - height: 220px; - width: 90%; - margin: 0 auto; - display: flex; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/TimeToRender/TimeToRender.js b/frontend/app/components/Dashboard/Widgets/TimeToRender/TimeToRender.js deleted file mode 100644 index 1ac489588..000000000 --- a/frontend/app/components/Dashboard/Widgets/TimeToRender/TimeToRender.js +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles, AvgLabel } from '../common'; -import { withRequest } from 'HOCs'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; -import WidgetAutoComplete from 'Shared/WidgetAutoComplete'; -import { toUnderscore } from 'App/utils'; -import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; - -const WIDGET_KEY = 'timeToRender'; -const customParams = rangeName => { - const params = { density: 70 } - - if (rangeName === LAST_24_HOURS) params.density = 70 - if (rangeName === LAST_30_MINUTES) params.density = 70 - if (rangeName === YESTERDAY) params.density = 70 - if (rangeName === LAST_7_DAYS) params.density = 70 - - return params -} - -@withRequest({ - dataName: "options", - initialData: [], - dataWrapper: data => data, - loadingName: "optionsLoading", - requestName: "fetchOptions", - endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', - method: 'GET' -}) -@widgetHOC(WIDGET_KEY, { customParams }) -export default class TimeToRender extends React.PureComponent { - onSelect = (params) => { - const _params = customParams(this.props.period.rangeName) - this.props.fetchWidget(WIDGET_KEY, this.props.period, this.props.platform, { ..._params, url: params.value }, this.props.filters) - } - - render() { - const { data, loading, optionsLoading, period, compare = false, showSync = false } = this.props; - const colors = compare ? Styles.compareColors : Styles.colors; - const params = customParams(period.rangeName) - const gradientDef = Styles.gradientDef(); - - return ( - -
- -
- -
-
- - - - - {gradientDef} - - - Styles.tickFormatter(val)} - /> - - - - - - -
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/TimeToRender/index.js b/frontend/app/components/Dashboard/Widgets/TimeToRender/index.js deleted file mode 100644 index 9c4c77c12..000000000 --- a/frontend/app/components/Dashboard/Widgets/TimeToRender/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './TimeToRender'; diff --git a/frontend/app/components/Dashboard/Widgets/TopDomains/TopDomains.js b/frontend/app/components/Dashboard/Widgets/TopDomains/TopDomains.js deleted file mode 100644 index 23537e0f7..000000000 --- a/frontend/app/components/Dashboard/Widgets/TopDomains/TopDomains.js +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; -import { Loader, NoContent } from 'UI'; -import { widgetHOC, Styles } from '../common'; -import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, - LineChart, Line, Legend, Tooltip -} from 'recharts'; - -@widgetHOC('domainsErrors', { fitContent: true }) -export default class TopDomains extends React.PureComponent { - render() { - const { data, loading, key = '4xx' } = this.props; - - const namesMap = data.chart[key] - .map(i => Object.keys(i)) - .flat() - .filter(i => i !== 'time' && i !== 'timestamp') - .reduce( - (unique, item) => (unique.includes(item) ? unique : [...unique, item]), - [] - ); - - return ( - - - - - - - - - - - - - - - - { namesMap.map((key, index) => ( - - ))} - - - - - ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/TopDomains/index.js b/frontend/app/components/Dashboard/Widgets/TopDomains/index.js deleted file mode 100644 index 33cefa725..000000000 --- a/frontend/app/components/Dashboard/Widgets/TopDomains/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './TopDomains'; diff --git a/frontend/app/components/Dashboard/Widgets/TopMetrics.js b/frontend/app/components/Dashboard/Widgets/TopMetrics.js deleted file mode 100644 index bac8d5d41..000000000 --- a/frontend/app/components/Dashboard/Widgets/TopMetrics.js +++ /dev/null @@ -1,56 +0,0 @@ -import React from 'react'; -import { Loader } from 'UI'; -import { msToSec } from 'App/date'; -import { CountBadge, Divider, widgetHOC } from './common'; - -@widgetHOC('topMetrics') -export default class TopMetrics extends React.PureComponent { - render() { - const { data, loading } = this.props; - return ( -
- -
- - - - {/* */} -
-
- - - -
-
-
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/TrendChart/TrendChart.js b/frontend/app/components/Dashboard/Widgets/TrendChart/TrendChart.js deleted file mode 100644 index b32f4171a..000000000 --- a/frontend/app/components/Dashboard/Widgets/TrendChart/TrendChart.js +++ /dev/null @@ -1,107 +0,0 @@ -import React from 'react'; -import { Styles, CountBadge } from '../common'; -import { CloseButton, Loader } from 'UI'; -import { ResponsiveContainer, AreaChart, XAxis, YAxis, Area, Tooltip } from 'recharts'; -import { numberWithCommas } from 'App/utils'; -import cn from 'classnames'; -import stl from './trendChart.module.css'; - -const loadChart = (data, loading, unit, syncId, compare, tooltipLael) => { - const gradientDef = Styles.gradientDef(); - return ( -
- - - - {gradientDef} - - - - - - - -
- ) -} - -const countView = (avg, unit) => { - if (unit === 'mb') { - if (!avg) return 0; - const count = Math.trunc(avg / 1024 / 1024); - return numberWithCommas(count); - } - if (unit === 'min') { - if (!avg) return 0; - const count = Math.trunc(avg); - return numberWithCommas(count > 1000 ? count +'k' : count); - } - return avg ? numberWithCommas(avg): 0; -} - -function TrendChart({ - loading = true, - title, - avg, - progress, - unit = false, - subtext, - data, - handleRemove, - compare = false, - comparing = false, - syncId = '', - tooltipLael = '', - textClass ='', - prefix = '', - canRemove = true -}) { - return ( -
- { canRemove && ( - - )} -
-
- {comparing &&
} -
{ title }
-
-
- {prefix} - {/*
*/} - {/*
*/} - {/*
*/} - -
-
- { loadChart(data, loading, unit, syncId, compare, tooltipLael) } -
- ) -} - -export default TrendChart diff --git a/frontend/app/components/Dashboard/Widgets/TrendChart/index.js b/frontend/app/components/Dashboard/Widgets/TrendChart/index.js deleted file mode 100644 index bdf7f32b2..000000000 --- a/frontend/app/components/Dashboard/Widgets/TrendChart/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default as TrendChart } from './TrendChart'; diff --git a/frontend/app/components/Dashboard/Widgets/TrendChart/trendChart.module.css b/frontend/app/components/Dashboard/Widgets/TrendChart/trendChart.module.css deleted file mode 100644 index e69fb4878..000000000 --- a/frontend/app/components/Dashboard/Widgets/TrendChart/trendChart.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.circle { - width: 8px; - height: 8px; - border-radius: 50%; - margin-right: 5px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/UserActivity.js b/frontend/app/components/Dashboard/Widgets/UserActivity.js deleted file mode 100644 index 282e305d9..000000000 --- a/frontend/app/components/Dashboard/Widgets/UserActivity.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { msToMin } from 'App/date'; -import { Loader } from 'UI'; -import { CountBadge, Divider, widgetHOC } from './common'; - -@widgetHOC('userActivity') -export default class UserActivity extends React.PureComponent { - render() { - const { data, loading } = this.props; - return ( -
- - - - - -
- ); - } -} diff --git a/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/CustomMetricWidgetHoc.module.css b/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/CustomMetricWidgetHoc.module.css deleted file mode 100644 index 1d1ef3ee4..000000000 --- a/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/CustomMetricWidgetHoc.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.wrapper { - background-color: white; - /* border: solid thin $gray-medium; */ - border-radius: 3px; - padding: 10px; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/CustomMetricWidgetHoc.tsx b/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/CustomMetricWidgetHoc.tsx deleted file mode 100644 index 4df0d6fbb..000000000 --- a/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/CustomMetricWidgetHoc.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import stl from './CustomMetricWidgetHoc.module.css'; -import { Icon } from 'UI'; - -interface Props { -} -const CustomMetricWidgetHoc = ({ ...rest }: Props) => BaseComponent => { - return ( -
-
-
Widget Name
-
-
- -
-
-
- {/* */} -
- ); -} - -export default CustomMetricWidgetHoc; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/index.ts b/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/index.ts deleted file mode 100644 index 0be8a5be5..000000000 --- a/frontend/app/components/Dashboard/Widgets/common/CustomMetricWidgetHoc/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './CustomMetricWidgetHoc'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/common/index.js b/frontend/app/components/Dashboard/Widgets/common/index.js index 52ca30580..21ef76ea0 100644 --- a/frontend/app/components/Dashboard/Widgets/common/index.js +++ b/frontend/app/components/Dashboard/Widgets/common/index.js @@ -1,9 +1,5 @@ export { default as Title } from './Title'; -export { default as CountBadge } from './CountBadge'; export { default as Table } from './Table'; -export { default as Divider } from './Divider'; export { default as domain } from './domain'; -export { default as widgetHOC } from './widgetHOC'; -export { default as SessionLine } from './SessionLine'; export { default as Styles } from './Styles'; export { default as AvgLabel } from './AvgLabel'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/common/widgetHOC.js b/frontend/app/components/Dashboard/Widgets/common/widgetHOC.js deleted file mode 100644 index 341d52245..000000000 --- a/frontend/app/components/Dashboard/Widgets/common/widgetHOC.js +++ /dev/null @@ -1,149 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import cn from 'classnames'; -import { CloseButton } from 'UI'; -import { fetchWidget } from 'Duck/dashboard'; -// import { updateAppearance } from 'Duck/user'; -import { WIDGET_MAP } from 'Types/dashboard'; -import Title from './Title'; -import stl from './widgetHOC.module.css'; - -export default (widgetKey, panelProps = {}, wrapped = true, allowedFilters = []) => - (BaseComponent) => { - @connect( - (state, props) => { - const compare = props && props.compare; - const key = compare ? '_' + widgetKey : widgetKey; - - return { - loading: state.getIn(['dashboard', 'fetchWidget', key, 'loading']), - data: state.getIn(['dashboard', key]), - comparing: state.getIn(['dashboard', 'comparing']), - filtersSize: state.getIn(['dashboard', 'filters']).size, - filters: state.getIn(['dashboard', compare ? 'filtersCompare' : 'filters']), - period: state.getIn(['dashboard', compare ? 'periodCompare' : 'period']), //TODO: filters - platform: state.getIn(['dashboard', 'platform']), - // appearance: state.getIn([ 'user', 'account', 'appearance' ]), - - dataCompare: state.getIn(['dashboard', '_' + widgetKey]), // only for overview - loadingCompare: state.getIn(['dashboard', 'fetchWidget', '_' + widgetKey, 'loading']), - filtersCompare: state.getIn(['dashboard', 'filtersCompare']), - periodCompare: state.getIn(['dashboard', 'periodCompare']), //TODO: filters - }; - }, - { - fetchWidget, - // updateAppearance, - } - ) - class WidgetWrapper extends React.PureComponent { - constructor(props) { - super(props); - const params = panelProps.customParams - ? panelProps.customParams(this.props.period.rangeName) - : {}; - if (props.testId) { - params.testId = parseInt(props.testId); - } - params.compare = this.props.compare; - const filters = - allowedFilters.length > 0 - ? props.filters.filter((f) => allowedFilters.includes(f.key)) - : props.filters; - props.fetchWidget(widgetKey, props.period, props.platform, params, filters); - } - - componentDidUpdate(prevProps) { - if ( - prevProps.period !== this.props.period || - prevProps.platform !== this.props.platform || - prevProps.filters.size !== this.props.filters.size - ) { - const params = panelProps.customParams - ? panelProps.customParams(this.props.period.rangeName) - : {}; - if (this.props.testId) { - params.testId = parseInt(this.props.testId); - } - params.compare = this.props.compare; - const filters = - allowedFilters.length > 0 - ? this.props.filters.filter((f) => allowedFilters.includes(f.key)) - : this.props.filters; - this.props.fetchWidget( - widgetKey, - this.props.period, - this.props.platform, - params, - filters - ); - } - - // handling overview widgets - if ( - (!prevProps.comparing || - prevProps.periodCompare !== this.props.periodCompare || - prevProps.filtersCompare.size !== this.props.filtersCompare.size) && - this.props.comparing && - this.props.isOverview - ) { - const params = panelProps.customParams - ? panelProps.customParams(this.props.period.rangeName) - : {}; - params.compare = true; - const filtersCompare = - allowedFilters.length > 0 - ? this.props.filtersCompare.filter((f) => allowedFilters.includes(f.key)) - : this.props.filtersCompare; - this.props.fetchWidget( - widgetKey, - this.props.periodCompare, - this.props.platform, - params, - filtersCompare - ); - } - } - - handleRemove = () => { - // const { appearance } = this.props; - // this.props.updateAppearance(appearance.setIn([ 'dashboard', widgetKey ], false)); - }; - - render() { - const { comparing, compare } = this.props; - - return wrapped ? ( -
-
-
- {comparing && ( -
- )} - - { - <CloseButton - className={cn(stl.closeButton, 'ml-auto')} - onClick={this.handleRemove} - size="17" - /> - } - </div> - <div className="flex-1 flex flex-col"> - <BaseComponent {...this.props} /> - </div> - </div> - </div> - ) : ( - <BaseComponent {...this.props} /> - ); - } - } - return WidgetWrapper; - }; diff --git a/frontend/app/components/Dashboard/Widgets/index.js b/frontend/app/components/Dashboard/Widgets/index.js deleted file mode 100644 index e5dbd3758..000000000 --- a/frontend/app/components/Dashboard/Widgets/index.js +++ /dev/null @@ -1,39 +0,0 @@ -// export { default as ApplicationActivity } from './ApplicationActivity'; -// export { default as ProcessedSessions } from './ProcessedSessions'; -// export { default as Errors } from './Errors'; -// export { default as UserActivity } from './UserActivity'; -// export { default as Performance } from './Performance'; -// export { default as SlowestImages } from './SlowestImages'; -// export { default as PageMetrics } from './PageMetrics'; -// export { default as LastFrustrations } from './LastFrustrations'; -// export { default as MissingResources } from './MissingResources'; -// export { default as ResourceLoadingTime } from './ResourceLoadingTime'; -// export { default as SlowestResources } from './SlowestResources'; -// export { default as DomBuildingTime } from './DomBuildingTime'; -// export { default as BusiestTimeOfTheDay } from './BusiestTimeOfTheDay'; -// export { default as ResponseTime } from './ResponseTime'; -// export { default as ResponseTimeDistribution } from './ResponseTimeDistribution'; -// export { default as TimeToRender } from './TimeToRender'; -// export { default as SessionsImpactedBySlowRequests } from './SessionsImpactedBySlowRequests'; -// export { default as MemoryConsumption } from './MemoryConsumption'; -// export { default as FPS } from './FPS'; -// export { default as CpuLoad } from './CpuLoad'; -// export { default as Crashes } from './Crashes'; -// export { default as TopDomains } from './TopDomains'; -// export { default as SlowestDomains } from './SlowestDomains'; -// export { default as ErrorsPerDomain } from './ErrorsPerDomain'; -// export { default as CallWithErrors } from './CallWithErrors'; -// export { default as ErrorsByType } from './ErrorsByType'; -// export { default as ErrorsByOrigin } from './ErrorsByOrigin'; -// export { default as ResourceLoadedVsResponseEnd } from './ResourceLoadedVsResponseEnd'; -// export { default as ResourceLoadedVsVisuallyComplete } from './ResourceLoadedVsVisuallyComplete'; -// export { default as SessionsAffectedByJSErrors } from './SessionsAffectedByJSErrors'; -// export { default as BreakdownOfLoadedResources } from './BreakdownOfLoadedResources'; -// export { default as TopMetrics } from './TopMetrics'; -// export { default as SpeedIndexLocation } from './SpeedIndexLocation'; -// export { default as SessionsPerBrowser } from './SessionsPerBrowser'; -// export { default as CallsErrors5xx } from './CallsErrors5xx'; -// export { default as CallsErrors4xx } from './CallsErrors4xx'; -// export { default as TrendChart } from './TrendChart'; - -// TODO remove all the references to the old widgets \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/AddCardModal/AddCardModal.tsx b/frontend/app/components/Dashboard/components/AddCardModal/AddCardModal.tsx new file mode 100644 index 000000000..227b485f6 --- /dev/null +++ b/frontend/app/components/Dashboard/components/AddCardModal/AddCardModal.tsx @@ -0,0 +1,20 @@ +import Modal from 'App/components/Modal/Modal'; +import React from 'react'; +import MetricTypeList from '../MetricTypeList'; + +interface Props { + siteId: string; + dashboardId: string; +} +function AddCardModal(props: Props) { + return ( + <> + <Modal.Header title="Add Card" /> + <Modal.Content className="px-3 pb-6"> + <MetricTypeList siteId={props.siteId} dashboardId={parseInt(props.dashboardId)} /> + </Modal.Content> + </> + ); +} + +export default AddCardModal; diff --git a/frontend/app/components/Dashboard/components/AddCardModal/index.ts b/frontend/app/components/Dashboard/components/AddCardModal/index.ts new file mode 100644 index 000000000..c7864e849 --- /dev/null +++ b/frontend/app/components/Dashboard/components/AddCardModal/index.ts @@ -0,0 +1 @@ +export { default } from './AddCardModal'; diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertForm/Condition.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertForm/Condition.tsx index 3fa9a9a43..80a900895 100644 --- a/frontend/app/components/Dashboard/components/Alerts/AlertForm/Condition.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/AlertForm/Condition.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Input } from 'UI'; import Select from 'Shared/Select'; import { alertConditions as conditions } from 'App/constants'; +import Alert from 'Types/alert' const thresholdOptions = [ { label: '15 minutes', value: 15 }, @@ -25,6 +26,7 @@ interface ICondition { writeQuery: (data: any) => void; writeQueryOption: (e: any, data: any) => void; unit: any; + changeUnit: (value: string) => void; } function Condition({ @@ -35,6 +37,7 @@ function Condition({ writeQueryOption, writeQuery, unit, + changeUnit, }: ICondition) { return ( <div> @@ -47,7 +50,7 @@ function Condition({ options={changeOptions} name="change" defaultValue={instance.change} - onChange={({ value }) => writeOption(null, { name: 'change', value })} + onChange={({ value }) => changeUnit(value)} id="change-dropdown" /> </div> @@ -100,6 +103,7 @@ function Condition({ value={instance.query.right} onChange={writeQuery} placeholder="Specify Value" + type={"number"} /> )} </div> diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertForm/NotifyHooks.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertForm/NotifyHooks.tsx index 921c7ba9b..95efaaaf3 100644 --- a/frontend/app/components/Dashboard/components/Alerts/AlertForm/NotifyHooks.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/AlertForm/NotifyHooks.tsx @@ -1,11 +1,13 @@ import React from 'react'; import { Checkbox } from 'UI'; import DropdownChips from '../DropdownChips'; +import { observer } from 'mobx-react-lite' interface INotifyHooks { instance: Alert; onChangeCheck: (e: React.ChangeEvent<HTMLInputElement>) => void; slackChannels: Array<any>; + msTeamsChannels: Array<any>; validateEmail: (value: string) => boolean; edit: (data: any) => void; hooks: Array<any>; @@ -16,20 +18,33 @@ function NotifyHooks({ onChangeCheck, slackChannels, validateEmail, + msTeamsChannels, hooks, edit, }: INotifyHooks) { return ( <div className="flex flex-col"> <div className="flex items-center my-4"> - <Checkbox - name="slack" - className="mr-8" - type="checkbox" - checked={instance.slack} - onClick={onChangeCheck} - label="Slack" - /> + {slackChannels.length > 0 && ( + <Checkbox + name="slack" + className="mr-8" + type="checkbox" + checked={instance.slack} + onClick={onChangeCheck} + label="Slack" + /> + )} + {msTeamsChannels.length > 0 && ( + <Checkbox + name="msteams" + className="mr-8" + type="checkbox" + checked={instance.msteams} + onClick={onChangeCheck} + label="MS Teams" + /> + )} <Checkbox name="email" type="checkbox" @@ -49,7 +64,7 @@ function NotifyHooks({ {instance.slack && ( <div className="flex items-start my-4"> - <label className="w-1/6 flex-shrink-0 font-normal pt-2">{'Slack'}</label> + <label className="w-1/6 flex-shrink-0 font-normal pt-2">Slack</label> <div className="w-2/6"> <DropdownChips fluid @@ -63,9 +78,25 @@ function NotifyHooks({ </div> )} + {instance.msteams && ( + <div className="flex items-start my-4"> + <label className="w-1/6 flex-shrink-0 font-normal pt-2">MS Teams</label> + <div className="w-2/6"> + <DropdownChips + fluid + selected={instance.msteamsInput} + options={msTeamsChannels} + placeholder="Select Channel" + // @ts-ignore + onChange={(selected) => edit({ msteamsInput: selected })} + /> + </div> + </div> + )} + {instance.email && ( <div className="flex items-start my-4"> - <label className="w-1/6 flex-shrink-0 font-normal pt-2">{'Email'}</label> + <label className="w-1/6 flex-shrink-0 font-normal pt-2">Email</label> <div className="w-2/6"> <DropdownChips textFiled @@ -81,7 +112,7 @@ function NotifyHooks({ {instance.webhook && ( <div className="flex items-start my-4"> - <label className="w-1/6 flex-shrink-0 font-normal pt-2">{'Webhook'}</label> + <label className="w-1/6 flex-shrink-0 font-normal pt-2">Webhook</label> <div className="w-2/6"> <DropdownChips fluid @@ -98,4 +129,4 @@ function NotifyHooks({ ); } -export default NotifyHooks; +export default observer(NotifyHooks); diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertListItem.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertListItem.tsx index 3e8a68f11..8137b7750 100644 --- a/frontend/app/components/Dashboard/components/Alerts/AlertListItem.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/AlertListItem.tsx @@ -7,6 +7,8 @@ import { numberWithCommas } from 'App/utils'; import { DateTime } from 'luxon'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import cn from 'classnames'; +import Alert from 'Types/alert'; +import { observer } from 'mobx-react-lite' const getThreshold = (threshold: number) => { if (threshold === 15) return '15 Minutes'; @@ -29,17 +31,37 @@ const getNotifyChannel = (alert: Record<string, any>, webhooks: Array<any>) => { .map((channelId: number) => { return ( '#' + - webhooks.find((hook) => hook.webhookId === channelId && hook.type === 'slack').name + webhooks.find((hook) => hook.webhookId === channelId && hook.type === 'slack')?.name ); }) .join(', ') + ')' ); }; + const getMsTeamsChannels = () => { + return ( + ' (' + + alert.msteamsInput + .map((channelId: number) => { + return webhooks.find((hook) => hook.webhookId === channelId && hook.type === 'msteams') + ?.name; + }) + .join(', ') + + ')' + ); + }; let str = ''; if (alert.slack) { str = 'Slack'; - str += alert.slackInput.length > 0 ? getSlackChannels() : ''; + if (alert.slackInput.length > 0) { + str += getSlackChannels(); + } + } + if (alert.msteams) { + str += (str === '' ? '' : ' and ') + 'MS Teams'; + if (alert.msteamsInput.length > 0) { + str += getMsTeamsChannels(); + } } if (alert.email) { str += (str === '' ? '' : ' and ') + (alert.emailInput.length > 1 ? 'Emails' : 'Email'); @@ -54,13 +76,14 @@ const getNotifyChannel = (alert: Record<string, any>, webhooks: Array<any>) => { interface Props extends RouteComponentProps { alert: Alert; siteId: string; - init: (alert?: Alert) => void; + init: (alert: Alert) => void; demo?: boolean; webhooks: Array<any>; + triggerOptions: Record<string, any>; } function AlertListItem(props: Props) { - const { alert, siteId, history, init, demo, webhooks } = props; + const { alert, siteId, history, init, demo, webhooks, triggerOptions } = props; if (!alert) { return null; @@ -69,10 +92,15 @@ function AlertListItem(props: Props) { const onItemClick = () => { if (demo) return; const path = withSiteId(alertEdit(alert.alertId), siteId); - init(alert); + init(alert || {}); history.push(path); }; + const formTriggerName = () => + Number.isInteger(alert.query.left) && triggerOptions + ? triggerOptions.find((opt: { value: any, label: string }) => opt.value === alert.query.left).label + : alert.query.left; + return ( <div className={cn('px-6', !demo ? 'hover:bg-active-blue cursor-pointer border-t' : '')} @@ -103,20 +131,29 @@ function AlertListItem(props: Props) { </div> <div className="color-gray-medium px-2 pb-2"> {'When the '} - <span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas' }}>{alert.detectionMethod}</span> + <span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas' }}> + {alert.detectionMethod} + </span> {' of '} - <span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas' }}>{alert.query.left}</span> + <span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas' }}> + {triggerOptions ? formTriggerName() : alert.seriesName} + </span> {' is '} <span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas' }}> {alert.query.operator} - {numberWithCommas(alert.query.right)} {alert.metric.unit} + {numberWithCommas(alert.query.right)} + {alert.change === 'percent' ? '%' : alert.metric?.unit} </span> {' over the past '} - <span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas' }}>{getThreshold(alert.currentPeriod)}</span> + <span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas' }}> + {getThreshold(alert.currentPeriod)} + </span> {alert.detectionMethod === 'change' ? ( <> {' compared to the previous '} - <span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas ' }}>{getThreshold(alert.previousPeriod)}</span> + <span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas ' }}> + {getThreshold(alert.previousPeriod)} + </span> </> ) : null} {', notify me on '} @@ -129,4 +166,4 @@ function AlertListItem(props: Props) { ); } -export default withRouter(AlertListItem); +export default withRouter(observer(AlertListItem)); diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx index 54cdf0a4f..d1d4c84ef 100644 --- a/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx @@ -1,41 +1,36 @@ import React from 'react'; -import { NoContent, Pagination, Icon } from 'UI'; +import { NoContent, Pagination } from 'UI'; import { filterList } from 'App/utils'; import { sliceListPerPage } from 'App/utils'; -import { fetchList } from 'Duck/alerts'; -import { connect } from 'react-redux'; -import { fetchList as fetchWebhooks } from 'Duck/webhook'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import AlertListItem from './AlertListItem' +import { useStore } from 'App/mstore' +import { observer } from 'mobx-react-lite' const pageSize = 10; interface Props { - fetchList: () => void; - list: any; - alertsSearch: any; siteId: string; - webhooks: Array<any>; - init: (instance?: Alert) => void - fetchWebhooks: () => void; } -function AlertsList({ fetchList, list: alertsList, alertsSearch, siteId, init, fetchWebhooks, webhooks }: Props) { - React.useEffect(() => { fetchList(); fetchWebhooks() }, []); +function AlertsList({ siteId }: Props) { + const { alertsStore, settingsStore } = useStore(); + const { fetchWebhooks, webhooks } = settingsStore + const { alerts: alertsList, alertsSearch, fetchList, init } = alertsStore + const page = alertsStore.page; - const alertsArray = alertsList.toJS(); - const [page, setPage] = React.useState(1); + React.useEffect(() => { fetchList(); fetchWebhooks() }, []); + const alertsArray = alertsList const filteredAlerts = filterList(alertsArray, alertsSearch, ['name'], (item, query) => query.test(item.query.left)) const list = alertsSearch !== '' ? filteredAlerts : alertsArray; - const lenth = list.length; return ( <NoContent - show={lenth === 0} + show={list.length === 0} title={ <div className="flex flex-col items-center justify-center"> - <AnimatedSVG name={ICONS.NO_ALERTS} size={80} /> + <AnimatedSVG name={ICONS.NO_ALERTS} size={180} /> <div className="text-center text-gray-600 my-4"> {alertsSearch !== '' ? 'No matching results' : "You haven't created any alerts yet"} </div> @@ -63,8 +58,8 @@ function AlertsList({ fetchList, list: alertsList, alertsSearch, siteId, init, f </div> <Pagination page={page} - totalPages={Math.ceil(lenth / pageSize)} - onPageChange={(page) => setPage(page)} + totalPages={Math.ceil(list.length / pageSize)} + onPageChange={(page) => alertsStore.updateKey('page', page)} limit={pageSize} debounceRequest={100} /> @@ -73,14 +68,4 @@ function AlertsList({ fetchList, list: alertsList, alertsSearch, siteId, init, f ); } -export default connect( - (state) => ({ - // @ts-ignore - list: state.getIn(['alerts', 'list']).sort((a, b) => b.createdAt - a.createdAt), - // @ts-ignore - alertsSearch: state.getIn(['alerts', 'alertsSearch']), - // @ts-ignore - webhooks: state.getIn(['webhooks', 'list']), - }), - { fetchList, fetchWebhooks } -)(AlertsList); +export default observer(AlertsList); diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertsSearch.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertsSearch.tsx index 0e4ffc5ef..0928f3a46 100644 --- a/frontend/app/components/Dashboard/components/Alerts/AlertsSearch.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/AlertsSearch.tsx @@ -1,20 +1,17 @@ import React, { useEffect, useState } from 'react'; import { Icon } from 'UI'; import { debounce } from 'App/utils'; -import { changeSearch } from 'Duck/alerts'; -import { connect } from 'react-redux'; +import { useStore } from 'App/mstore' +import { observer } from 'mobx-react-lite' let debounceUpdate: any = () => {}; -interface Props { - changeSearch: (value: string) => void; -} - -function AlertsSearch({ changeSearch }: Props) { - const [inputValue, setInputValue] = useState(''); +function AlertsSearch() { + const { alertsStore } = useStore(); + const [inputValue, setInputValue] = useState(alertsStore.alertsSearch); useEffect(() => { - debounceUpdate = debounce((value: string) => changeSearch(value), 500); + debounceUpdate = debounce((value: string) => alertsStore.changeSearch(value), 500); }, []); const write = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => { @@ -36,10 +33,4 @@ function AlertsSearch({ changeSearch }: Props) { ); } -export default connect( - (state) => ({ - // @ts-ignore - alertsSearch: state.getIn(['alerts', 'alertsSearch']), - }), - { changeSearch } -)(AlertsSearch); +export default observer(AlertsSearch); diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertsView.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertsView.tsx index 07b77961a..544c86f8f 100644 --- a/frontend/app/components/Dashboard/components/Alerts/AlertsView.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/AlertsView.tsx @@ -1,19 +1,30 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { Button, PageTitle, Icon, Link } from 'UI'; import withPageTitle from 'HOCs/withPageTitle'; -import { connect } from 'react-redux'; -import { init } from 'Duck/alerts'; import { withSiteId, alertCreate } from 'App/routes'; import AlertsList from './AlertsList'; import AlertsSearch from './AlertsSearch'; +import { useHistory } from 'react-router'; +import { useStore } from 'App/mstore'; interface IAlertsView { siteId: string; - init: (instance?: Alert) => any; } -function AlertsView({ siteId, init }: IAlertsView) { +function AlertsView({ siteId }: IAlertsView) { + const history = useHistory(); + const { alertsStore } = useStore(); + + + useEffect(() => { + const unmount = history.listen((location) => { + if (!location.pathname.includes('/alert')) { + alertsStore.updateKey('page', 1); + } + }); + return unmount; + }, [history]); return ( <div style={{ maxWidth: '1300px', margin: 'auto'}} className="bg-white rounded py-4 border"> <div className="flex items-center mb-4 justify-between px-6"> @@ -21,7 +32,7 @@ function AlertsView({ siteId, init }: IAlertsView) { <PageTitle title="Alerts" /> </div> <div className="ml-auto flex items-center"> - <Link to={withSiteId(alertCreate(), siteId)}><Button variant="primary" onClick={null}>Create Alert</Button></Link> + <Link to={withSiteId(alertCreate(), siteId)}><Button variant="primary">Create Alert</Button></Link> <div className="ml-4 w-1/4" style={{ minWidth: 300 }}> <AlertsSearch /> </div> @@ -31,12 +42,9 @@ function AlertsView({ siteId, init }: IAlertsView) { <Icon name="info-circle-fill" className="mr-2" size={16} /> Alerts helps your team stay up to date with the activity on your app. </div> - <AlertsList siteId={siteId} init={init} /> + <AlertsList siteId={siteId} /> </div> ); } -// @ts-ignore -const Container = connect(null, { init })(AlertsView); - -export default withPageTitle('Alerts - OpenReplay')(Container); +export default withPageTitle('Alerts - OpenReplay')(AlertsView); diff --git a/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx b/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx index 6027646f7..4d1d247b0 100644 --- a/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx @@ -1,16 +1,16 @@ import React, { useEffect } from 'react'; -import { Form, SegmentSelection, Icon } from 'UI'; +import { Form, SegmentSelection } from 'UI'; import { connect } from 'react-redux'; import { validateEmail } from 'App/validate'; -import { fetchTriggerOptions, init, edit, save, remove, fetchList } from 'Duck/alerts'; import { confirm } from 'UI'; import { toast } from 'react-toastify'; -import { SLACK, WEBHOOK } from 'App/constants/schedule'; -import { fetchList as fetchWebhooks } from 'Duck/webhook'; +import { SLACK, WEBHOOK, TEAMS } from 'App/constants/schedule'; import Breadcrumb from 'Shared/Breadcrumb'; import { withSiteId, alerts } from 'App/routes'; import { withRouter, RouteComponentProps } from 'react-router-dom'; - +import { useStore } from 'App/mstore' +import { observer } from 'mobx-react-lite' +import Alert from 'Types/alert' import cn from 'classnames'; import WidgetName from '../WidgetName'; import BottomButtons from './AlertForm/BottomButtons'; @@ -48,69 +48,68 @@ const Section = ({ index, title, description, content }: ISection) => ( </div> ); +interface Select { + label: string; + value: string | number +} + interface IProps extends RouteComponentProps { siteId: string; - instance: Alert; slackChannels: any[]; - webhooks: any[]; loading: boolean; deleting: boolean; triggerOptions: any[]; list: any; - fetchTriggerOptions: () => void; - edit: (query: any) => void; - init: (alert?: Alert) => any; - save: (alert: Alert) => Promise<any>; - remove: (alertId: string) => Promise<any>; onSubmit: (instance: Alert) => void; - fetchWebhooks: () => void; - fetchList: () => void; } const NewAlert = (props: IProps) => { + const { alertsStore, settingsStore } = useStore(); const { - instance, - siteId, - webhooks, - loading, - deleting, - triggerOptions, + fetchTriggerOptions, init, edit, save, remove, - fetchWebhooks, fetchList, - list, + instance, + alerts: list, + triggerOptions, + loading, + } = alertsStore + const deleting = loading + const webhooks = settingsStore.webhooks + const fetchWebhooks = settingsStore.fetchWebhooks + const { + siteId, } = props; useEffect(() => { init({}); - if (list.size === 0) fetchList(); - props.fetchTriggerOptions(); - fetchWebhooks(); + if (list.length === 0) fetchList(); + fetchTriggerOptions(); + void fetchWebhooks(); }, []); useEffect(() => { - if (list.size > 0) { + if (list.length > 0) { const alertId = location.pathname.split('/').pop(); - const currentAlert = list - .toJS() - .find((alert: Alert) => alert.alertId === parseInt(alertId, 10)); - init(currentAlert); + const currentAlert = list.find((alert: Alert) => alert.alertId === String(alertId)); + if (currentAlert) { + init(currentAlert) + } } }, [list]); const write = ({ target: { value, name } }: React.ChangeEvent<HTMLInputElement>) => - props.edit({ [name]: value }); + edit({ [name]: value }); const writeOption = ( _: React.ChangeEvent, { name, value }: { name: string; value: Record<string, any> } - ) => props.edit({ [name]: value.value }); + ) => edit({ [name]: value.value }); - const onChangeCheck = ({ target: { checked, name } }: React.ChangeEvent<HTMLInputElement>) => - props.edit({ [name]: checked }); + const onChangeCheck = ({ target: { checked, name } }: React.ChangeEvent<HTMLInputElement>) => edit({ [name]: checked }); const onDelete = async (instance: Alert) => { if ( @@ -143,29 +142,38 @@ const NewAlert = (props: IProps) => { }); }; - const slackChannels = webhooks - .filter((hook) => hook.type === SLACK) - .map(({ webhookId, name }) => ({ value: webhookId, label: name })) - // @ts-ignore - .toJS(); + const slackChannels: Select[] = [] + const hooks: Select[] = [] + const msTeamsChannels: Select[] = [] - const hooks = webhooks - .filter((hook) => hook.type === WEBHOOK) - .map(({ webhookId, name }) => ({ value: webhookId, label: name })) - // @ts-ignore - .toJS(); + webhooks.forEach((hook) => { + const option = { value: hook.webhookId, label: hook.name } + if (hook.type === SLACK) { + slackChannels.push(option) + } + if (hook.type === WEBHOOK) { + hooks.push(option) + } + if (hook.type === TEAMS) { + msTeamsChannels.push(option) + } + }) const writeQueryOption = ( e: React.ChangeEvent, { name, value }: { name: string; value: string } ) => { const { query } = instance; - props.edit({ query: { ...query, [name]: value } }); + edit({ query: { ...query, [name]: value } }); }; + const changeUnit = (value: string) => { + alertsStore.changeUnit(value) + } + const writeQuery = ({ target: { value, name } }: React.ChangeEvent<HTMLInputElement>) => { const { query } = instance; - props.edit({ query: { ...query, [name]: value } }); + edit({ query: { ...query, [name]: value } }); }; const metric = @@ -212,7 +220,7 @@ const NewAlert = (props: IProps) => { outline name="detectionMethod" className="my-3 w-1/4" - onSelect={(e: any, { name, value }: any) => props.edit({ [name]: value })} + onSelect={(e: any, { name, value }: any) => edit({ [name]: value })} value={{ value: instance.detectionMethod }} list={[ { name: 'Threshold', value: 'threshold' }, @@ -239,6 +247,7 @@ const NewAlert = (props: IProps) => { instance={instance} triggerOptions={triggerOptions} writeQueryOption={writeQueryOption} + changeUnit={changeUnit} writeQuery={writeQuery} unit={unit} /> @@ -253,6 +262,7 @@ const NewAlert = (props: IProps) => { instance={instance} onChangeCheck={onChangeCheck} slackChannels={slackChannels} + msTeamsChannels={msTeamsChannels} validateEmail={validateEmail} hooks={hooks} edit={edit} @@ -273,30 +283,17 @@ const NewAlert = (props: IProps) => { <div className="bg-white mt-4 border rounded mb-10"> {instance && ( - <AlertListItem alert={instance} demo siteId="" init={() => null} webhooks={webhooks} /> + <AlertListItem + alert={instance} + triggerOptions={triggerOptions} + demo + siteId="" + init={() => null} + webhooks={webhooks} /> )} </div> </> ); }; -export default withRouter( - connect( - (state) => ({ - // @ts-ignore - instance: state.getIn(['alerts', 'instance']), - //@ts-ignore - list: state.getIn(['alerts', 'list']), - // @ts-ignore - triggerOptions: state.getIn(['alerts', 'triggerOptions']), - // @ts-ignore - loading: state.getIn(['alerts', 'saveRequest', 'loading']), - // @ts-ignore - deleting: state.getIn(['alerts', 'removeRequest', 'loading']), - // @ts-ignore - webhooks: state.getIn(['webhooks', 'list']), - }), - { fetchTriggerOptions, init, edit, save, remove, fetchWebhooks, fetchList } - // @ts-ignore - )(NewAlert) -); +export default withRouter(observer(NewAlert)) diff --git a/frontend/app/components/Dashboard/components/Alerts/type.d.ts b/frontend/app/components/Dashboard/components/Alerts/type.d.ts deleted file mode 100644 index 6ac1a8f34..000000000 --- a/frontend/app/components/Dashboard/components/Alerts/type.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -// TODO burn the immutable and make typing this possible -type Alert = Record<string, any> diff --git a/frontend/app/components/Dashboard/components/ClickMapRagePicker/ClickMapRagePicker.tsx b/frontend/app/components/Dashboard/components/ClickMapRagePicker/ClickMapRagePicker.tsx new file mode 100644 index 000000000..f13a21682 --- /dev/null +++ b/frontend/app/components/Dashboard/components/ClickMapRagePicker/ClickMapRagePicker.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Checkbox} from "UI"; +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite'; + +function ClickMapRagePicker() { + const { metricStore } = useStore(); + + const onToggle = (e: React.ChangeEvent<HTMLInputElement>) => { + metricStore.setClickMaps(e.target.checked) + } + + React.useEffect(() => { + return () => { + metricStore.setClickMaps(false) + } + }, []) + + return ( + <div className="mr-4 flex items-center cursor-pointer"> + <Checkbox + onChange={onToggle} + label="Include rage clicks" + /> + </div> + ); +} + +export default observer(ClickMapRagePicker); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/ClickMapRagePicker/index.ts b/frontend/app/components/Dashboard/components/ClickMapRagePicker/index.ts new file mode 100644 index 000000000..208ab0919 --- /dev/null +++ b/frontend/app/components/Dashboard/components/ClickMapRagePicker/index.ts @@ -0,0 +1 @@ +export { default } from './ClickMapRagePicker' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/DashbaordListModal/DashbaordListModal.tsx b/frontend/app/components/Dashboard/components/DashbaordListModal/DashbaordListModal.tsx index 9b93b9942..236602493 100644 --- a/frontend/app/components/Dashboard/components/DashbaordListModal/DashbaordListModal.tsx +++ b/frontend/app/components/Dashboard/components/DashbaordListModal/DashbaordListModal.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useStore } from 'App/mstore'; -import { SideMenuitem, SideMenuHeader, Icon, Button } from 'UI'; -import { withSiteId, dashboardSelected, metrics } from 'App/routes'; +import { SideMenuitem, Icon } from 'UI'; +import { withSiteId, dashboardSelected } from 'App/routes'; import { withRouter } from 'react-router-dom'; import { useModal } from 'App/components/Modal'; diff --git a/frontend/app/components/Dashboard/components/DashboardHeader/DashboardHeader.tsx b/frontend/app/components/Dashboard/components/DashboardHeader/DashboardHeader.tsx new file mode 100644 index 000000000..5f312d124 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardHeader/DashboardHeader.tsx @@ -0,0 +1,134 @@ +import React from 'react'; +import Breadcrumb from 'Shared/Breadcrumb'; +import { withSiteId } from 'App/routes'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { Button, PageTitle, confirm, Tooltip } from 'UI'; +import SelectDateRange from 'Shared/SelectDateRange'; +import { useStore } from 'App/mstore'; +import { useModal } from 'App/components/Modal'; +import DashboardOptions from '../DashboardOptions'; +import withModal from 'App/components/Modal/withModal'; +import { observer } from 'mobx-react-lite'; +import DashboardEditModal from '../DashboardEditModal'; +import AddCardModal from '../AddCardModal'; + +interface IProps { + dashboardId: string; + siteId: string; + renderReport?: any; +} + +type Props = IProps & RouteComponentProps; +const MAX_CARDS = 30 +function DashboardHeader(props: Props) { + const { siteId, dashboardId } = props; + const { dashboardStore } = useStore(); + const { showModal } = useModal(); + const [focusTitle, setFocusedInput] = React.useState(true); + const [showEditModal, setShowEditModal] = React.useState(false); + const period = dashboardStore.period; + + const dashboard: any = dashboardStore.selectedDashboard; + const canAddMore: boolean = dashboard?.widgets?.length <= MAX_CARDS; + + const onEdit = (isTitle: boolean) => { + dashboardStore.initDashboard(dashboard); + setFocusedInput(isTitle); + setShowEditModal(true); + }; + + const onDelete = async () => { + if ( + await confirm({ + header: 'Confirm', + confirmButton: 'Yes, delete', + confirmation: `Are you sure you want to permanently delete this Dashboard?`, + }) + ) { + dashboardStore.deleteDashboard(dashboard).then(() => { + props.history.push(withSiteId(`/dashboard`, siteId)); + }); + } + }; + return ( + <div> + <DashboardEditModal + show={showEditModal} + closeHandler={() => setShowEditModal(false)} + focusTitle={focusTitle} + /> + <Breadcrumb + items={[ + { + label: 'Dashboards', + to: withSiteId('/dashboard', siteId), + }, + { label: (dashboard && dashboard.name) || '' }, + ]} + /> + <div className="flex items-center mb-2 justify-between"> + <div className="flex items-center" style={{ flex: 3 }}> + <PageTitle + title={ + // @ts-ignore + <Tooltip delay={100} arrow title="Double click to rename"> + {dashboard?.name} + </Tooltip> + } + onDoubleClick={() => onEdit(true)} + className="mr-3 select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer" + /> + </div> + <div className="flex items-center" style={{ flex: 1, justifyContent: 'end' }}> + <Tooltip delay={0} disabled={canAddMore} title="The number of cards in one dashboard is limited to 30."> + <Button + disabled={!canAddMore} + variant="primary" + onClick={() => + showModal(<AddCardModal dashboardId={dashboardId} siteId={siteId} />, { right: true }) + } + icon="plus" + iconSize={24} + > + Add Card + </Button> + </Tooltip> + <div className="mx-4"></div> + <div + className="flex items-center flex-shrink-0 justify-end" + style={{ width: 'fit-content' }} + > + <SelectDateRange + style={{ width: '300px' }} + period={period} + onChange={(period: any) => dashboardStore.setPeriod(period)} + right={true} + /> + </div> + <div className="mx-4" /> + <div className="flex items-center flex-shrink-0"> + <DashboardOptions + editHandler={onEdit} + deleteHandler={onDelete} + renderReport={props.renderReport} + isTitlePresent={!!dashboard?.description} + /> + </div> + </div> + </div> + <div className="pb-4"> + {/* @ts-ignore */} + <Tooltip delay={100} arrow title="Double click to rename" className="w-fit !block"> + <h2 + className="my-2 font-normal w-fit text-disabled-text border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer" + onDoubleClick={() => onEdit(false)} + > + {dashboard?.description || 'Describe the purpose of this dashboard'} + </h2> + </Tooltip> + </div> + </div> + ); +} + +export default withRouter(withModal(observer(DashboardHeader))); diff --git a/frontend/app/components/Dashboard/components/DashboardHeader/index.ts b/frontend/app/components/Dashboard/components/DashboardHeader/index.ts new file mode 100644 index 000000000..4bd864695 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardHeader/index.ts @@ -0,0 +1 @@ +export { default } from './DashboardHeader'; diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx index 7bb716733..18c96f12a 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx @@ -1,22 +1,15 @@ import { observer } from 'mobx-react-lite'; import React from 'react'; -import { NoContent, Pagination, Icon } from 'UI'; +import { NoContent, Pagination } from 'UI'; import { useStore } from 'App/mstore'; -import { filterList } from 'App/utils'; import { sliceListPerPage } from 'App/utils'; import DashboardListItem from './DashboardListItem'; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; function DashboardList() { const { dashboardStore } = useStore(); - const [shownDashboards, setDashboards] = React.useState([]); - const dashboards = dashboardStore.sortedDashboards; + const list = dashboardStore.filteredList; const dashboardsSearch = dashboardStore.dashboardsSearch; - - React.useEffect(() => { - setDashboards(filterList(dashboards, dashboardsSearch, ['name', 'owner', 'description'])); - }, [dashboardsSearch]); - - const list = dashboardsSearch !== '' ? shownDashboards : dashboards; const lenth = list.length; return ( @@ -24,12 +17,19 @@ function DashboardList() { show={lenth === 0} title={ <div className="flex flex-col items-center justify-center"> - <Icon name="no-dashboard" size={80} color="figmaColors-accent-secondary" /> - <div className="text-center text-gray-600 my-4"> - {dashboardsSearch !== '' - ? 'No matching results' - : "You haven't created any dashboards yet"} + <div className="text-center my-4"> + {dashboardsSearch !== '' ? ( + 'No matching results' + ) : ( + <div> + <div>Create your first Dashboard</div> + <div className="text-sm color-gray-medium font-normal"> + A dashboard lets you visualize trends and insights of data captured by OpenReplay. + </div> + </div> + )} </div> + <AnimatedSVG name={ICONS.NO_DASHBOARDS} size={180} /> </div> } > @@ -37,7 +37,7 @@ function DashboardList() { <div className="grid grid-cols-12 py-2 font-medium px-6"> <div className="col-span-8">Title</div> <div className="col-span-2">Visibility</div> - <div className="col-span-2 text-right">Created</div> + <div className="col-span-2 text-right">Creation Date</div> </div> {sliceListPerPage(list, dashboardStore.page - 1, dashboardStore.pageSize).map( diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx index a3cb6e436..e076dd6d5 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx @@ -32,7 +32,6 @@ function DashboardListItem(props: Props) { <div className="link capitalize-first">{dashboard.name}</div> </div> </div> - {/* <div><Label className="capitalize">{metric.metricType}</Label></div> */} <div className="col-span-2"> <div className="flex items-center"> <Icon name={dashboard.isPublic ? 'user-friends' : 'person-fill'} className="mr-2" /> diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx index a3b13f1d3..d60a6886c 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx @@ -4,33 +4,37 @@ import { useStore } from 'App/mstore'; import { Icon } from 'UI'; import { debounce } from 'App/utils'; -let debounceUpdate: any = () => {} +let debounceUpdate: any = () => {}; function DashboardSearch() { - const { dashboardStore } = useStore(); - const [query, setQuery] = useState(dashboardStore.dashboardsSearch); - useEffect(() => { - debounceUpdate = debounce((key: string, value: any) => dashboardStore.updateKey(key, value), 500); - }, []) - - // @ts-ignore - const write = ({ target: { value } }) => { - setQuery(value); - debounceUpdate('dashboardsSearch', value); - } - - return ( - <div className="relative"> - <Icon name="search" className="absolute top-0 bottom-0 ml-2 m-auto" size="16" /> - <input - value={query} - name="dashboardsSearch" - className="bg-white p-2 border border-borderColor-gray-light-shade rounded w-full pl-10" - placeholder="Filter by title or description" - onChange={write} - /> - </div> + const { dashboardStore } = useStore(); + const [query, setQuery] = useState(dashboardStore.dashboardsSearch); + useEffect(() => { + debounceUpdate = debounce( + (key: string, value: any) => + dashboardStore.updateKey('filter', { ...dashboardStore.filter, query: value }), + 500 ); + }, []); + + // @ts-ignore + const write = ({ target: { value } }) => { + setQuery(value); + debounceUpdate('dashboardsSearch', value); + }; + + return ( + <div className="relative"> + <Icon name="search" className="absolute top-0 bottom-0 ml-2 m-auto" size="16" /> + <input + value={query} + name="dashboardsSearch" + className="bg-white p-2 border border-borderColor-gray-light-shade rounded w-full pl-10" + placeholder="Filter by title or description" + onChange={write} + /> + </div> + ); } export default observer(DashboardSearch); diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx index 7378e88f8..f0c8b46ff 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx @@ -1,45 +1,15 @@ import React from 'react'; -import { Button, PageTitle, Icon } from 'UI'; import withPageTitle from 'HOCs/withPageTitle'; -import { useStore } from 'App/mstore'; -import { withSiteId } from 'App/routes'; - import DashboardList from './DashboardList'; -import DashboardSearch from './DashboardSearch'; +import Header from './Header'; -function DashboardsView({ history, siteId }: { history: any, siteId: string }) { - const { dashboardStore } = useStore(); - - const onAddDashboardClick = () => { - dashboardStore.initDashboard(); - dashboardStore - .save(dashboardStore.dashboardInstance) - .then(async (syncedDashboard) => { - dashboardStore.selectDashboardById(syncedDashboard.dashboardId); - history.push(withSiteId(`/dashboard/${syncedDashboard.dashboardId}`, siteId)) - }) - } - - return ( - <div style={{ maxWidth: '1300px', margin: 'auto'}} className="bg-white rounded py-4 border"> - <div className="flex items-center mb-4 justify-between px-6"> - <div className="flex items-baseline mr-3"> - <PageTitle title="Dashboards" /> - </div> - <div className="ml-auto flex items-center"> - <Button variant="primary" onClick={onAddDashboardClick}>Create Dashboard</Button> - <div className="ml-4 w-1/4" style={{ minWidth: 300 }}> - <DashboardSearch /> - </div> - </div> - </div> - <div className="text-base text-disabled-text flex items-center px-6"> - <Icon name="info-circle-fill" className="mr-2" size={16} /> - A Dashboard is a collection of Metrics that can be shared across teams. - </div> - <DashboardList /> - </div> - ); +function DashboardsView({ history, siteId }: { history: any; siteId: string }) { + return ( + <div style={{ maxWidth: '1300px', margin: 'auto' }} className="bg-white rounded py-4 border"> + <Header history={history} siteId={siteId} /> + <DashboardList /> + </div> + ); } export default withPageTitle('Dashboards - OpenReplay')(DashboardsView); diff --git a/frontend/app/components/Dashboard/components/DashboardList/Header.tsx b/frontend/app/components/Dashboard/components/DashboardList/Header.tsx new file mode 100644 index 000000000..9a8e9de22 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardList/Header.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { Button, PageTitle, Toggler, Icon } from 'UI'; +import Select from 'Shared/Select'; +import DashboardSearch from './DashboardSearch'; +import { useStore } from 'App/mstore'; +import { observer, useObserver } from 'mobx-react-lite'; +import { withSiteId } from 'App/routes'; + +function Header({ history, siteId }: { history: any; siteId: string }) { + const { dashboardStore } = useStore(); + const sort = useObserver(() => dashboardStore.sort); + + const onAddDashboardClick = () => { + dashboardStore.initDashboard(); + dashboardStore.save(dashboardStore.dashboardInstance).then(async (syncedDashboard) => { + dashboardStore.selectDashboardById(syncedDashboard.dashboardId); + history.push(withSiteId(`/dashboard/${syncedDashboard.dashboardId}`, siteId)); + }); + }; + + return ( + <> + <div className="flex items-center justify-between px-6"> + <div className="flex items-baseline mr-3"> + <PageTitle title="Dashboards" /> + </div> + <div className="ml-auto flex items-center"> + <Button variant="primary" onClick={onAddDashboardClick}> + New Dashboard + </Button> + <div className="mx-2"></div> + <div className="w-1/4" style={{ minWidth: 300 }}> + <DashboardSearch /> + </div> + </div> + </div> + <div className="text-base text-disabled-text flex items-center px-6"> + <Icon name="info-circle-fill" className="mr-2" size={16} /> + A Dashboard is a collection of Cards that can be shared across teams. + </div> + <div className="border-y px-3 py-1 mt-2 flex items-center w-full justify-end gap-4"> + <Toggler + label="Private Dashboards" + checked={dashboardStore.filter.showMine} + name="test" + className="font-medium mr-2" + onChange={() => + dashboardStore.updateKey('filter', { + ...dashboardStore.filter, + showMine: !dashboardStore.filter.showMine, + }) + } + /> + {/* <Select + options={[ + { label: 'Visibility - All', value: 'all' }, + { label: 'Visibility - Private', value: 'private' }, + { label: 'Visibility - Team', value: 'team' }, + ]} + defaultValue={'all'} + plain + onChange={({ value }) => + dashboardStore.updateKey('filter', { + ...dashboardStore.filter, + visibility: value.value, + }) + } + /> */} + + <Select + options={[ + { label: 'Newest', value: 'desc' }, + { label: 'Oldest', value: 'asc' }, + ]} + defaultValue={sort.by} + plain + onChange={({ value }) => dashboardStore.updateKey('sort', { by: value.value })} + /> + </div> + </> + ); +} + +export default observer(Header); diff --git a/frontend/app/components/Dashboard/components/DashboardOptions/DashboardOptions.tsx b/frontend/app/components/Dashboard/components/DashboardOptions/DashboardOptions.tsx index b600efb69..63b1c3f35 100644 --- a/frontend/app/components/Dashboard/components/DashboardOptions/DashboardOptions.tsx +++ b/frontend/app/components/Dashboard/components/DashboardOptions/DashboardOptions.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { ItemMenu } from 'UI'; import { connect } from 'react-redux'; +import { ENTERPRISE_REQUEIRED } from 'App/constants'; interface Props { editHandler: (isTitle: boolean) => void; @@ -16,10 +17,8 @@ function DashboardOptions(props: Props) { { icon: 'text-paragraph', text: `${!isTitlePresent ? 'Add' : 'Edit'} Description`, onClick: () => editHandler(false) }, { icon: 'users', text: 'Visibility & Access', onClick: editHandler }, { icon: 'trash', text: 'Delete', onClick: deleteHandler }, + { icon: 'pdf-download', text: 'Download Report', onClick: renderReport, disabled: !isEnterprise, tooltipTitle: ENTERPRISE_REQUEIRED } ] - if (isEnterprise) { - menuItems.unshift({ icon: 'pdf-download', text: 'Download Report', onClick: renderReport }); - } return ( <ItemMenu diff --git a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx index 42129f699..d2451b68e 100644 --- a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx +++ b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { SideMenuitem, SideMenuHeader } from 'UI'; +import { SideMenuitem } from 'UI'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import { withSiteId, metrics, dashboard, alerts } from 'App/routes'; import { connect } from 'react-redux'; @@ -38,8 +38,8 @@ function DashboardSideMenu(props: Props) { <SideMenuitem active={isMetric} id="menu-manage-alerts" - title="Metrics" - iconName="bar-chart-line" + title="Cards" + iconName="card-text" onClick={() => redirect(withSiteId(metrics(), siteId))} /> </div> diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index e610d040c..0cde69517 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -1,23 +1,17 @@ import React, { useEffect } from 'react'; import { observer } from 'mobx-react-lite'; import { useStore } from 'App/mstore'; -import { Button, PageTitle, Loader, Tooltip, Popover } from 'UI'; +import { Loader } from 'UI'; import { withSiteId } from 'App/routes'; import withModal from 'App/components/Modal/withModal'; import DashboardWidgetGrid from '../DashboardWidgetGrid'; -import { confirm } from 'UI'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import { useModal } from 'App/components/Modal'; import DashboardModal from '../DashboardModal'; -import DashboardEditModal from '../DashboardEditModal'; import AlertFormModal from 'App/components/Alerts/AlertFormModal'; import withPageTitle from 'HOCs/withPageTitle'; import withReport from 'App/components/hocs/withReport'; -import DashboardOptions from '../DashboardOptions'; -import SelectDateRange from 'Shared/SelectDateRange'; -import Breadcrumb from 'Shared/Breadcrumb'; -import AddMetricContainer from '../DashboardWidgetGrid/AddMetricContainer'; -import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; +import DashboardHeader from '../DashboardHeader'; interface IProps { siteId: string; @@ -32,14 +26,9 @@ function DashboardView(props: Props) { const { dashboardStore } = useStore(); const { showModal } = useModal(); - const [showTooltip, setShowTooltip] = React.useState(false); - const [focusTitle, setFocusedInput] = React.useState(true); - const [showEditModal, setShowEditModal] = React.useState(false); - const showAlertModal = dashboardStore.showAlertModal; const loading = dashboardStore.fetchingDashboard; const dashboard: any = dashboardStore.selectedDashboard; - const period = dashboardStore.period; const queryParams = new URLSearchParams(props.location.search); @@ -50,6 +39,7 @@ function DashboardView(props: Props) { search: queryParams.toString(), }); }; + const pushQuery = () => { if (!queryParams.has('modal')) props.history.push('?modal=addMetric'); }; @@ -81,117 +71,14 @@ function DashboardView(props: Props) { ); }; - const onEdit = (isTitle: boolean) => { - dashboardStore.initDashboard(dashboard); - setFocusedInput(isTitle); - setShowEditModal(true); - }; - - const onDelete = async () => { - if ( - await confirm({ - header: 'Confirm', - confirmButton: 'Yes, delete', - confirmation: `Are you sure you want to permanently delete this Dashboard?`, - }) - ) { - dashboardStore.deleteDashboard(dashboard).then(() => { - props.history.push(withSiteId(`/dashboard`, siteId)); - }); - } - }; - if (!dashboard) return null; return ( <Loader loading={loading}> <div style={{ maxWidth: '1300px', margin: 'auto' }}> - <DashboardEditModal - show={showEditModal} - closeHandler={() => setShowEditModal(false)} - focusTitle={focusTitle} - /> - <Breadcrumb - items={[ - { - label: 'Dashboards', - to: withSiteId('/dashboard', siteId), - }, - { label: (dashboard && dashboard.name) || '' }, - ]} - /> - <div className="flex items-center mb-2 justify-between"> - <div className="flex items-center" style={{ flex: 3 }}> - <PageTitle - title={ - // @ts-ignore - <Tooltip title="Double click to rename"> - {dashboard?.name} - </Tooltip> - } - onDoubleClick={() => onEdit(true)} - className="mr-3 select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer" - actionButton={ - // <OutsideClickDetectingDiv onClickOutside={() => setShowTooltip(false)}> - <Popover - // open={showTooltip} - // interactive - // useContext - // @ts-ignore - // theme="nopadding" - // hideDelay={0} - // duration={0} - // distance={20} - placement="left" - render={() => showTooltip && ( - <div style={{ padding: 0 }}> - <AddMetricContainer - onAction={() => setShowTooltip(false)} - isPopup - siteId={siteId} - /> - </div> - )} - > - <Button variant="primary" onClick={() => setShowTooltip(true)}> - Add Metric - </Button> - </Popover> - // </OutsideClickDetectingDiv> - } - /> - </div> - <div className="flex items-center" style={{ flex: 1, justifyContent: 'end' }}> - <div className="flex items-center flex-shrink-0 justify-end" style={{ width: '300px' }}> - <SelectDateRange - style={{ width: '300px' }} - period={period} - onChange={(period: any) => dashboardStore.setPeriod(period)} - right={true} - /> - </div> - <div className="mx-4" /> - <div className="flex items-center flex-shrink-0"> - <DashboardOptions - editHandler={onEdit} - deleteHandler={onDelete} - renderReport={props.renderReport} - isTitlePresent={!!dashboard?.description} - /> - </div> - </div> - </div> - <div className="pb-4"> - {/* @ts-ignore */} - <Tooltip delay={100} arrow title="Double click to rename" className="w-fit !block"> - <h2 - className="my-2 font-normal w-fit text-disabled-text border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer" - onDoubleClick={() => onEdit(false)} - > - {dashboard?.description || 'Describe the purpose of this dashboard'} - </h2> - </Tooltip> - </div> + {/* @ts-ignore */} + <DashboardHeader renderReport={props.renderReport} siteId={siteId} dashboardId={dashboardId} /> + <DashboardWidgetGrid siteId={siteId} dashboardId={dashboardId} @@ -200,7 +87,7 @@ function DashboardView(props: Props) { /> <AlertFormModal showModal={showAlertModal} - onClose={() => dashboardStore.updateKey('showAlertModal', false)} + onClose={() => dashboardStore.toggleAlertModal(false)} /> </div> </Loader> diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx index b45b51625..0064d98b8 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx @@ -3,8 +3,8 @@ import { useStore } from 'App/mstore'; import WidgetWrapper from '../WidgetWrapper'; import { NoContent, Loader, Icon } from 'UI'; import { useObserver } from 'mobx-react-lite'; -import AddMetricContainer from './AddMetricContainer'; import Widget from 'App/mstore/types/widget'; +import MetricTypeList from '../MetricTypeList'; interface Props { siteId: string; @@ -38,23 +38,28 @@ function DashboardWidgetGrid(props: Props) { show={list.length === 0} icon="no-metrics-chart" title={ - <span className="text-2xl capitalize-first text-figmaColors-text-primary"> - Build your dashboard - </span> - } - subtext={ - <div className="w-4/5 m-auto mt-4"> - <AddMetricContainer siteId={siteId} /> + <div className="bg-white rounded"> + <div className="border-b p-5"> + <div className="text-2xl font-normal color-gray-darkest"> + There are no cards in this dashboard + </div> + <div className="text-base font-normal"> + Create a card from any of the below types or pick an existing one from your library. + </div> + </div> + <div className="grid grid-cols-4 p-8 gap-2"> + <MetricTypeList dashboardId={parseInt(dashboardId)} siteId={siteId} /> + </div> </div> } > - <div className="grid gap-4 grid-cols-4 items-start pb-10" id={props.id}> - {smallWidgets.length > 0 ? ( - <> - <div className="font-semibold text-xl py-4 flex items-center gap-2 col-span-4"> - <Icon name="grid-horizontal" size={26} /> - Web Vitals - </div> + <div className="grid gap-4 grid-cols-4 items-start pb-10" id={props.id}>{smallWidgets.length > 0 ? ( + <> + <div className="font-semibold text-xl py-4 flex items-center gap-2 col-span-4"> + <Icon name="grid-horizontal" size={26} /> + Web Vitals + </div> + {smallWidgets && smallWidgets.map((item: any, index: any) => ( <React.Fragment key={item.widgetId}> @@ -63,23 +68,24 @@ function DashboardWidgetGrid(props: Props) { widget={item} moveListItem={(dragIndex: any, hoverIndex: any) => dashboard.swapWidgetPosition(dragIndex, hoverIndex) - } - dashboardId={dashboardId} + + }dashboardId={dashboardId} siteId={siteId} isWidget={true} grid="vitals" /> </React.Fragment> ))} - </> - ) : null} - {smallWidgets.length > 0 && regularWidgets.length > 0 ? ( - <div className="font-semibold text-xl py-4 flex items-center gap-2 col-span-4"> - <Icon name="grid-horizontal" size={26} /> - All Metrics - </div> - ) : null} + </> + ) : null} + + {smallWidgets.length > 0 && regularWidgets.length > 0 ? ( + <div className="font-semibold text-xl py-4 flex items-center gap-2 col-span-4"> + <Icon name="grid-horizontal" size={26} /> + All Cards + </div> + ) : null} {regularWidgets && regularWidgets.map((item: any, index: any) => ( @@ -97,10 +103,6 @@ function DashboardWidgetGrid(props: Props) { /> </React.Fragment> ))} - - <div className="col-span-2" id="no-print"> - <AddMetricContainer siteId={siteId} /> - </div> </div> </NoContent> </Loader> diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorDetails.tsx b/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorDetails.tsx index a45226fb3..3a102a9c4 100644 --- a/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorDetails.tsx +++ b/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorDetails.tsx @@ -1,8 +1,6 @@ import React, { useState } from 'react' import ErrorFrame from './ErrorFrame' -import cn from 'classnames'; import { IconButton, Icon } from 'UI'; -import { connect } from 'react-redux'; const docLink = 'https://docs.openreplay.com/installation/upload-sourcemaps'; diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorDetailsModal/ErrorDetailsModal.tsx b/frontend/app/components/Dashboard/components/Errors/ErrorDetailsModal/ErrorDetailsModal.tsx index 38f86af66..b8513580b 100644 --- a/frontend/app/components/Dashboard/components/Errors/ErrorDetailsModal/ErrorDetailsModal.tsx +++ b/frontend/app/components/Dashboard/components/Errors/ErrorDetailsModal/ErrorDetailsModal.tsx @@ -7,7 +7,7 @@ interface Props { function ErrorDetailsModal(props: Props) { return ( <div - style={{ width: '85vw', maxWidth: '1200px' }} + // style={{ width: '85vw', maxWidth: '1200px' }} className="bg-white h-screen p-4 overflow-y-auto" > <ErrorInfo errorId={props.errorId} /> diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx b/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx index 2d76241b3..145a215ab 100644 --- a/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx +++ b/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx @@ -1,16 +1,12 @@ import React from 'react'; import cn from 'classnames'; import moment from 'moment'; -import { error as errorRoute } from 'App/routes'; import { IGNORED, RESOLVED } from 'Types/errorInfo'; -import { Link, Label } from 'UI'; import ErrorName from '../ErrorName'; import ErrorLabel from '../ErrorLabel'; import { BarChart, Bar, YAxis, Tooltip, XAxis } from 'recharts'; import { Styles } from '../../../Widgets/common'; import { diffFromNowString } from 'App/date'; -import { useModal } from '../../../../Modal'; -import ErrorDetailsModal from '../ErrorDetailsModal'; interface Props { error: any; diff --git a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx index ccb269cbd..49f3659da 100644 --- a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx +++ b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx @@ -1,14 +1,5 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import FilterList from 'Shared/Filters/FilterList'; -import { - edit, - updateSeries, - addSeriesFilterFilter, - removeSeriesFilterFilter, - editSeriesFilterFilter, - editSeriesFilter, -} from 'Duck/customMetrics'; -import { connect } from 'react-redux'; import { Button, Icon } from 'UI'; import FilterSelection from 'Shared/Filters/FilterSelection'; import SeriesName from './SeriesName'; @@ -18,21 +9,25 @@ import { observer } from 'mobx-react-lite'; interface Props { seriesIndex: number; series: any; - edit: typeof edit; - updateSeries: typeof updateSeries; onRemoveSeries: (seriesIndex: any) => void; - canDelete?: boolean; - addSeriesFilterFilter: typeof addSeriesFilterFilter; - editSeriesFilterFilter: typeof editSeriesFilterFilter; - editSeriesFilter: typeof editSeriesFilter; - removeSeriesFilterFilter: typeof removeSeriesFilterFilter; + canDelete?: boolean; + supportsEmpty?: boolean; hideHeader?: boolean; emptyMessage?: any; observeChanges?: () => void; + excludeFilterKeys?: Array<string> } function FilterSeries(props: Props) { - const { observeChanges = () => {}, canDelete, hideHeader = false, emptyMessage = 'Add user event or filter to define the series by clicking Add Step.' } = props; + const { + observeChanges = () => { + }, + canDelete, + hideHeader = false, + emptyMessage = 'Add user event or filter to define the series by clicking Add Step.', + supportsEmpty = true, + excludeFilterKeys = [] + } = props; const [expanded, setExpanded] = useState(true) const { series, seriesIndex } = props; @@ -46,7 +41,7 @@ function FilterSeries(props: Props) { observeChanges() } - const onChangeEventsOrder = (e, { name, value }: any) => { + const onChangeEventsOrder = (_: any, { name, value }: any) => { series.filter.updateKey(name, value) observeChanges() } @@ -60,11 +55,11 @@ function FilterSeries(props: Props) { <div className="border rounded bg-white"> <div className={cn("border-b px-5 h-12 flex items-center relative", { 'hidden': hideHeader })}> <div className="mr-auto"> - <SeriesName seriesIndex={seriesIndex} name={series.name} onUpdate={(name) => series.update('name', name) } /> + <SeriesName seriesIndex={seriesIndex} name={series.name} onUpdate={(name) => series.update('name', name)} /> </div> - + <div className="flex items-center cursor-pointer"> - <div onClick={props.onRemoveSeries} className={cn("ml-3", {'disabled': !canDelete})}> + <div onClick={props.onRemoveSeries} className={cn("ml-3", { 'disabled': !canDelete })}> <Icon name="trash" size="16" /> </div> @@ -73,17 +68,19 @@ function FilterSeries(props: Props) { </div> </div> </div> - { expanded && ( + {expanded && ( <> <div className="p-5"> - { series.filter.filters.length > 0 ? ( + {series.filter.filters.length > 0 ? ( <FilterList filter={series.filter} onUpdateFilter={onUpdateFilter} onRemoveFilter={onRemoveFilter} onChangeEventsOrder={onChangeEventsOrder} + supportsEmpty={supportsEmpty} + excludeFilterKeys={excludeFilterKeys} /> - ): ( + ) : ( <div className="color-gray-medium">{emptyMessage}</div> )} </div> @@ -92,6 +89,7 @@ function FilterSeries(props: Props) { <FilterSelection filter={undefined} onFilterClick={onAddFilter} + excludeFilterKeys={excludeFilterKeys} > <Button variant="text-primary" icon="plus">ADD STEP</Button> </FilterSelection> @@ -103,11 +101,4 @@ function FilterSeries(props: Props) { ); } -export default connect(null, { - edit, - updateSeries, - addSeriesFilterFilter, - editSeriesFilterFilter, - editSeriesFilter, - removeSeriesFilterFilter, -})(observer(FilterSeries)); \ No newline at end of file +export default observer(FilterSeries); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.tsx index 1ae1fe528..16bd8abd0 100644 --- a/frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.tsx +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.tsx @@ -19,7 +19,7 @@ function FunnelIssueDetails(props: Props) { useEffect(() => { setLoading(true); - const _filters = { ...filter, series: widget.data.stages ? widget.toJsonDrilldown().map((item: any) => { + const _filters = { ...filter, series: widget.data.stages ? widget.series.map((item: any) => { return { ...item, filter: { @@ -27,7 +27,7 @@ function FunnelIssueDetails(props: Props) { filters: item.filter.filters.filter((filter: any, index: any) => { const stage = widget.data.funnel.stages[index]; return stage &&stage.isActive - }) + }).map((f: any) => f.toJson()) } } }) : [], }; diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssueModal/FunnelIssueModal.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssueModal/FunnelIssueModal.tsx index f1fa3469d..d5b6de307 100644 --- a/frontend/app/components/Dashboard/components/Funnels/FunnelIssueModal/FunnelIssueModal.tsx +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssueModal/FunnelIssueModal.tsx @@ -7,13 +7,11 @@ interface Props { function FunnelIssueModal(props: Props) { const { issueId } = props; return ( - <div style={{ width: '85vw', maxWidth: '1200px' }}> - <div - className="border-r shadow p-4 h-screen" - style={{ backgroundColor: '#FAFAFA', zIndex: 999, width: '100%' }} - > - <FunnelIssueDetails issueId={issueId} /> - </div> + <div + className="border-r shadow p-4 h-screen" + style={{ backgroundColor: '#FAFAFA', zIndex: 999, width: '100%' }} + > + <FunnelIssueDetails issueId={issueId} /> </div> ); } diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx index 826e9a133..6c636f901 100644 --- a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx @@ -31,7 +31,7 @@ function FunnelIssues() { filters: item.filter.filters.filter((filter: any, index: any) => { const stage = widget.data.funnel.stages[index]; return stage &&stage.isActive - }) + }).map((f: any) => f.toJson()) } } }), @@ -49,7 +49,7 @@ function FunnelIssues() { const depsString = JSON.stringify(widget.series); useEffect(() => { - debounceRequest({ ...filter, series: widget.toJsonDrilldown(), page: metricStore.sessionsPage, limit: metricStore.sessionsPageSize }); + debounceRequest({ ...filter, series: widget.series, page: metricStore.sessionsPage, limit: metricStore.sessionsPageSize }); }, [stages.length, drillDownPeriod, filter.filters, depsString, metricStore.sessionsPage]); return useObserver(() => ( diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesList/FunnelIssuesList.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesList/FunnelIssuesList.tsx index 3894f4671..a59202043 100644 --- a/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesList/FunnelIssuesList.tsx +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesList/FunnelIssuesList.tsx @@ -29,7 +29,7 @@ function FunnelIssuesList(props: RouteComponentProps<Props>) { useEffect(() => { if (!issueId) return; - showModal(<FunnelIssueModal issueId={issueId} />, { right: true, onClose: () => { + showModal(<FunnelIssueModal issueId={issueId} />, { right: true, width: 1000, onClose: () => { if (props.history.location.pathname.includes("/metric")) { props.history.replace({search: ""}); } diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesListItem/FunnelIssuesListItem.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesListItem/FunnelIssuesListItem.tsx index 89aa09be3..b692ca30b 100644 --- a/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesListItem/FunnelIssuesListItem.tsx +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesListItem/FunnelIssuesListItem.tsx @@ -2,8 +2,6 @@ import React from 'react'; import cn from 'classnames'; import { Icon, TextEllipsis } from 'UI'; import FunnelIssueGraph from '../FunnelIssueGraph'; -import { useModal } from 'App/components/Modal'; -import FunnelIssueModal from '../FunnelIssueModal'; interface Props { issue: any; diff --git a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx index 38b30af7b..795337cb9 100644 --- a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx +++ b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx @@ -1,66 +1,86 @@ -import React from 'react'; -import { Icon, Tooltip } from 'UI'; +import React, { useEffect, useState } from 'react'; +import { Icon, Checkbox, Tooltip } from 'UI'; import { checkForRecent } from 'App/date'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import { withSiteId } from 'App/routes'; +import { TYPES } from 'App/constants/card'; +import cn from 'classnames'; interface Props extends RouteComponentProps { - metric: any; - siteId: string; + metric: any; + siteId: string; + selected?: boolean; + toggleSelection?: any; + disableSelection?: boolean; } function MetricTypeIcon({ type }: any) { - const getIcon = () => { - switch (type) { - case 'funnel': - return 'filter'; - case 'table': - return 'list-alt'; - case 'timeseries': - return 'bar-chart-line'; - } - } + const [card, setCard] = useState<any>(''); + useEffect(() => { + const t = TYPES.find((i) => i.slug === type); + setCard(t); + }, [type]); - return ( - <Tooltip - title={<div className="capitalize">{type}</div>} - > - <div className="w-9 h-9 rounded-full bg-tealx-lightest flex items-center justify-center mr-2"> - <Icon name={getIcon()} size="16" color="tealx" /> - </div> - </Tooltip> - ) + return ( + <Tooltip delay={0} title={<div className="capitalize">{card.title}</div>}> + <div className="w-9 h-9 rounded-full bg-tealx-lightest flex items-center justify-center mr-2"> + {card.icon && <Icon name={card.icon} size="16" color="tealx" />} + </div> + </Tooltip> + ); } - - function MetricListItem(props: Props) { - const { metric, history, siteId } = props; + const { + metric, + history, + siteId, + selected, + toggleSelection = () => {}, + disableSelection = false, + } = props; - const onItemClick = () => { - const path = withSiteId(`/metrics/${metric.metricId}`, siteId); - history.push(path); - }; - return ( - <div className="grid grid-cols-12 py-4 border-t select-none items-center hover:bg-active-blue cursor-pointer px-6" onClick={onItemClick}> - <div className="col-span-4 flex items-start"> - <div className="flex items-center"> - <MetricTypeIcon type={metric.metricType} /> - <div className="link capitalize-first"> - {metric.name} - </div> - </div> - </div> - <div className="col-span-4">{metric.owner}</div> - <div className="col-span-2"> - <div className="flex items-center"> - <Icon name={metric.isPublic ? "user-friends" : "person-fill"} className="mr-2" /> - <span>{metric.isPublic ? 'Team' : 'Private'}</span> - </div> - </div> - <div className="col-span-2 text-right">{metric.lastModified && checkForRecent(metric.lastModified, 'LLL dd, yyyy, hh:mm a')}</div> + const onItemClick = (e: React.MouseEvent) => { + if (!disableSelection) { + return toggleSelection(e); + } + const path = withSiteId(`/metrics/${metric.metricId}`, siteId); + history.push(path); + }; + + return ( + <div + className="grid grid-cols-12 py-4 border-t select-none items-center hover:bg-active-blue cursor-pointer px-6" + onClick={onItemClick} + > + <div className="col-span-4 flex items-center"> + {!disableSelection && ( + <Checkbox + name="slack" + className="mr-4" + type="checkbox" + checked={selected} + onClick={toggleSelection} + /> + )} + + <div className="flex items-center"> + <MetricTypeIcon type={metric.metricType} /> + <div className={cn('capitalize-first', { link: disableSelection })}>{metric.name}</div> </div> - ); + </div> + <div className="col-span-4">{metric.owner}</div> + <div className="col-span-2"> + <div className="flex items-center"> + <Icon name={metric.isPublic ? 'user-friends' : 'person-fill'} className="mr-2" /> + <span>{metric.isPublic ? 'Team' : 'Private'}</span> + </div> + </div> + <div className="col-span-2 text-right"> + {metric.lastModified && checkForRecent(metric.lastModified, 'LLL dd, yyyy, hh:mm a')} + </div> + </div> + ); } export default withRouter(MetricListItem); diff --git a/frontend/app/components/Dashboard/components/MetricTypeItem/MetricTypeItem.tsx b/frontend/app/components/Dashboard/components/MetricTypeItem/MetricTypeItem.tsx new file mode 100644 index 000000000..c28389c4a --- /dev/null +++ b/frontend/app/components/Dashboard/components/MetricTypeItem/MetricTypeItem.tsx @@ -0,0 +1,48 @@ +import { IconNames } from 'App/components/ui/SVG'; +import React from 'react'; +import { Icon, Tooltip } from 'UI'; +import cn from 'classnames'; +import { ENTERPRISE_REQUEIRED } from 'App/constants'; + +export interface MetricType { + title: string; + icon?: IconNames; + description: string; + slug: string; + disabled?: boolean; + tooltipTitle?: string; +} + +interface Props { + metric: MetricType; + onClick?: any; +} + +function MetricTypeItem(props: Props) { + const { + metric: { title, icon, description, slug, disabled }, + onClick = () => {}, + } = props; + return ( + <Tooltip disabled={!disabled} title={ENTERPRISE_REQUEIRED} delay={0}> + <div + className={cn( + 'rounded color-gray-darkest flex items-start border border-transparent p-4 hover:bg-active-blue cursor-pointer group hover-color-teal', + { 'opacity-30 pointer-events-none': disabled } + )} + onClick={onClick} + > + <div className="pr-4 pt-1"> + {/* @ts-ignore */} + <Icon name={icon} size="20" color="gray-dark" /> + </div> + <div className="flex flex-col items-start text-left"> + <div className="text-base">{title}</div> + <div className="text-sm color-gray-medium font-normal">{description}</div> + </div> + </div> + </Tooltip> + ); +} + +export default MetricTypeItem; diff --git a/frontend/app/components/Dashboard/components/MetricTypeItem/index.ts b/frontend/app/components/Dashboard/components/MetricTypeItem/index.ts new file mode 100644 index 000000000..e9b95b5a8 --- /dev/null +++ b/frontend/app/components/Dashboard/components/MetricTypeItem/index.ts @@ -0,0 +1 @@ +export { default } from './MetricTypeItem'; diff --git a/frontend/app/components/Dashboard/components/MetricTypeList/MetricTypeList.tsx b/frontend/app/components/Dashboard/components/MetricTypeList/MetricTypeList.tsx new file mode 100644 index 000000000..12d6e88d1 --- /dev/null +++ b/frontend/app/components/Dashboard/components/MetricTypeList/MetricTypeList.tsx @@ -0,0 +1,66 @@ +import { useModal } from 'App/components/Modal'; +import React from 'react'; +import MetricsLibraryModal from '../MetricsLibraryModal'; +import MetricTypeItem, { MetricType } from '../MetricTypeItem/MetricTypeItem'; +import { TYPES, LIBRARY, INSIGHTS } from 'App/constants/card'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { dashboardMetricCreate, withSiteId } from 'App/routes'; +import { useStore } from 'App/mstore'; +import { connect } from 'react-redux'; +import { ENTERPRISE_REQUEIRED } from 'App/constants'; + +interface Props extends RouteComponentProps { + dashboardId: number; + siteId: string; + isEnterprise: boolean; +} +function MetricTypeList(props: Props) { + const { dashboardId, siteId, history, isEnterprise } = props; + const { metricStore } = useStore(); + const { hideModal } = useModal(); + + const list = React.useMemo(() => { + return TYPES.map((metric: MetricType) => { + const disabled = metric.slug === INSIGHTS && !isEnterprise; + return { + ...metric, + disabled: metric.slug === INSIGHTS && !isEnterprise, + tooltipTitle: disabled ? ENTERPRISE_REQUEIRED : '', + }; + }); + }, []); + + const { showModal } = useModal(); + const onClick = ({ slug }: MetricType) => { + hideModal(); + if (slug === LIBRARY) { + return showModal(<MetricsLibraryModal siteId={siteId} dashboardId={dashboardId} />, { + right: true, + width: 800, + onClose: () => { + metricStore.updateKey('metricsSearch', ''); + }, + }); + } + + // TODO redirect to card builder with metricType query param + const path = withSiteId(dashboardMetricCreate(dashboardId + ''), siteId); + const queryString = new URLSearchParams({ type: slug }).toString(); + history.push({ + pathname: path, + search: `?${queryString}`, + }); + }; + + return ( + <> + {list.map((metric: MetricType) => ( + <MetricTypeItem metric={metric} onClick={() => onClick(metric)} /> + ))} + </> + ); +} + +export default connect((state: any) => ({ + isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee', +}))(withRouter(MetricTypeList)); diff --git a/frontend/app/components/Dashboard/components/MetricTypeList/index.ts b/frontend/app/components/Dashboard/components/MetricTypeList/index.ts new file mode 100644 index 000000000..35cf1cc61 --- /dev/null +++ b/frontend/app/components/Dashboard/components/MetricTypeList/index.ts @@ -0,0 +1 @@ +export { default } from './MetricTypeList'; diff --git a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx new file mode 100644 index 000000000..081dd21e5 --- /dev/null +++ b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx @@ -0,0 +1,123 @@ +import React from 'react'; +import { Icon, PageTitle, Button, Link, Toggler } from 'UI'; +import MetricsSearch from '../MetricsSearch'; +import Select from 'Shared/Select'; +import { useStore } from 'App/mstore'; +import { observer, useObserver } from 'mobx-react-lite'; +import { DROPDOWN_OPTIONS } from 'App/constants/card'; + +function MetricViewHeader() { + const { metricStore } = useStore(); + const filter = metricStore.filter; + + return ( + <div> + <div className="flex items-center justify-between px-6"> + <div className="flex items-baseline mr-3"> + <PageTitle title="Cards" className="" /> + </div> + <div className="ml-auto flex items-center"> + <Link to={'/metrics/create'}> + <Button variant="primary">New Card</Button> + </Link> + <div className="ml-4 w-1/4" style={{ minWidth: 300 }}> + <MetricsSearch /> + </div> + </div> + </div> + <div className="text-base text-disabled-text flex items-center px-6"> + <Icon name="info-circle-fill" className="mr-2" size={16} /> + Create custom Cards to capture key interactions and track KPIs. + </div> + <div className="border-y px-3 py-1 mt-2 flex items-center w-full justify-between"> + <ListViewToggler /> + + <div className="items-center flex gap-4"> + <Toggler + label="My Cards" + checked={filter.showMine} + name="test" + className="font-medium mr-2" + onChange={() => + metricStore.updateKey('filter', { ...filter, showMine: !filter.showMine }) + } + /> + <Select + options={[{ label: 'All Types', value: 'all' }, ...DROPDOWN_OPTIONS]} + name="type" + defaultValue={filter.type} + onChange={({ value }) => + metricStore.updateKey('filter', { ...filter, type: value.value }) + } + plain={true} + isSearchable={true} + /> + + <Select + options={[ + { label: 'Newest', value: 'desc' }, + { label: 'Oldest', value: 'asc' }, + ]} + name="sort" + defaultValue={metricStore.sort.by} + onChange={({ value }) => metricStore.updateKey('sort', { by: value.value })} + plain={true} + /> + + <DashboardDropdown + plain={true} + onChange={(value: any) => + metricStore.updateKey('filter', { ...filter, dashboard: value }) + } + /> + </div> + </div> + </div> + ); +} + +export default observer(MetricViewHeader); + +function DashboardDropdown({ onChange, plain = false }: { plain?: boolean; onChange: any }) { + const { dashboardStore, metricStore } = useStore(); + const dashboardOptions = dashboardStore.dashboards.map((i: any) => ({ + key: i.id, + label: i.name, + value: i.dashboardId, + })); + + return ( + <Select + isSearchable={true} + placeholder="Select Dashboard" + plain={plain} + options={dashboardOptions} + value={metricStore.filter.dashboard} + onChange={({ value }: any) => onChange(value)} + isMulti={true} + /> + ); +} + +function ListViewToggler({}) { + const { metricStore } = useStore(); + const listView = useObserver(() => metricStore.listView); + return ( + <div className="flex items-center"> + <Button + icon="list-alt" + variant={listView ? 'text-primary' : 'text'} + onClick={() => metricStore.updateKey('listView', true)} + > + List + </Button> + <Button + icon="grid" + variant={!listView ? 'text-primary' : 'text'} + onClick={() => metricStore.updateKey('listView', false)} + > + Grid + </Button> + </div> + ); +} diff --git a/frontend/app/components/Dashboard/components/MetricViewHeader/index.ts b/frontend/app/components/Dashboard/components/MetricViewHeader/index.ts new file mode 100644 index 000000000..fd6048fc9 --- /dev/null +++ b/frontend/app/components/Dashboard/components/MetricViewHeader/index.ts @@ -0,0 +1 @@ +export { default } from './MetricViewHeader'; diff --git a/frontend/app/components/Dashboard/components/MetricsGrid/MetricsGrid.tsx b/frontend/app/components/Dashboard/components/MetricsGrid/MetricsGrid.tsx new file mode 100644 index 000000000..a98a972f5 --- /dev/null +++ b/frontend/app/components/Dashboard/components/MetricsGrid/MetricsGrid.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +interface Props { + +} +function MetricsGrid(props: Props) { + return ( + <div className="grid grid-cols-3 gap-4"> + + </div> + ); +} + +export default MetricsGrid; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/MetricsGrid/index.ts b/frontend/app/components/Dashboard/components/MetricsGrid/index.ts new file mode 100644 index 000000000..6e16e72d9 --- /dev/null +++ b/frontend/app/components/Dashboard/components/MetricsGrid/index.ts @@ -0,0 +1 @@ +export { default } from './MetricsGrid' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/MetricsLibraryModal/FooterContent.tsx b/frontend/app/components/Dashboard/components/MetricsLibraryModal/FooterContent.tsx new file mode 100644 index 000000000..61a6f0b7c --- /dev/null +++ b/frontend/app/components/Dashboard/components/MetricsLibraryModal/FooterContent.tsx @@ -0,0 +1,42 @@ +import { useModal } from 'App/components/Modal'; +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite'; +import React, { useMemo } from 'react'; +import { Button } from 'UI'; + +function FooterContent({ dashboardId, selected }: any) { + const { hideModal } = useModal(); + const { metricStore, dashboardStore } = useStore(); + const dashboard = useMemo(() => dashboardStore.getDashboard(dashboardId), [dashboardId]); + + const existingCardIds = useMemo(() => dashboard?.widgets?.map(i => parseInt(i.metricId)), [dashboard]); + const total = useMemo(() => metricStore.filteredCards.filter(i => !existingCardIds?.includes(parseInt(i.metricId))).length, [metricStore.filteredCards]); + + const addSelectedToDashboard = () => { + if (!dashboard || !dashboard.dashboardId) return; + dashboardStore.addWidgetToDashboard(dashboard, selected).then(() => { + hideModal(); + dashboardStore.fetch(dashboard.dashboardId!); + }); + }; + + return ( + <div className="flex items-center rounded border bg-gray-light-shade justify-between p-3"> + <div> + Selected <span className="font-medium">{selected.length}</span> of{' '} + <span className="font-medium">{total}</span> + </div> + <div className="flex items-center"> + <Button variant="text-primary" className="mr-2" onClick={hideModal}> + Cancel + </Button> + <Button disabled={selected.length === 0} variant="primary" onClick={addSelectedToDashboard}> + Add Selected to Dashboard + </Button> + </div> + </div> + ); + } + + export default observer(FooterContent); + \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/MetricsLibraryModal/MetricsLibraryModal.tsx b/frontend/app/components/Dashboard/components/MetricsLibraryModal/MetricsLibraryModal.tsx new file mode 100644 index 000000000..0683dce60 --- /dev/null +++ b/frontend/app/components/Dashboard/components/MetricsLibraryModal/MetricsLibraryModal.tsx @@ -0,0 +1,73 @@ +import Modal from 'App/components/Modal/Modal'; +import React, { useEffect, useState } from 'react'; +import MetricsList from '../MetricsList'; +import { Icon } from 'UI'; +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite'; +import FooterContent from './FooterContent'; + +interface Props { + dashboardId: number; + siteId: string; +} +function MetricsLibraryModal(props: Props) { + const { metricStore } = useStore(); + const { siteId, dashboardId } = props; + const [selectedList, setSelectedList] = useState([]); + + useEffect(() => { + metricStore.updateKey('page', 1) + metricStore.updateKey('listView', true); + + return () => { + metricStore.updateKey('filter', { ...metricStore.filter, query: '' }) + } + }, []); + + const onSelectionChange = (list: any) => { + setSelectedList(list); + }; + + const onChange = ({ target: { value } }: any) => { + metricStore.updateKey('filter', { ...metricStore.filter, query: value }) + }; + + return ( + <> + <Modal.Header title="Cards Library"> + <div className="flex items-center justify-between px-4 pt-4"> + <div className="text-lg flex items-center font-medium"> + <div>Cards Library</div> + </div> + <div> + <MetricSearch onChange={onChange} /> + </div> + </div> + </Modal.Header> + <Modal.Content className="p-4 pb-20"> + <div className="border"> + <MetricsList siteId={siteId} onSelectionChange={onSelectionChange} /> + </div> + </Modal.Content> + <Modal.Footer> + <FooterContent dashboardId={dashboardId} selected={selectedList} /> + </Modal.Footer> + </> + ); +} + +export default observer(MetricsLibraryModal); + +function MetricSearch({ onChange }: any) { + return ( + <div className="relative"> + <Icon name="search" className="absolute top-0 bottom-0 ml-2 m-auto" size="16" /> + <input + name="dashboardsSearch" + className="bg-white p-2 border border-borderColor-gray-light-shade rounded w-full pl-10" + placeholder="Filter by title or owner" + onChange={onChange} + /> + </div> + ); +} diff --git a/frontend/app/components/Dashboard/components/MetricsLibraryModal/index.ts b/frontend/app/components/Dashboard/components/MetricsLibraryModal/index.ts new file mode 100644 index 000000000..f217fc7c3 --- /dev/null +++ b/frontend/app/components/Dashboard/components/MetricsLibraryModal/index.ts @@ -0,0 +1 @@ +export { default } from './MetricsLibraryModal'; diff --git a/frontend/app/components/Dashboard/components/MetricsList/GridView.tsx b/frontend/app/components/Dashboard/components/MetricsList/GridView.tsx new file mode 100644 index 000000000..ab3f770f0 --- /dev/null +++ b/frontend/app/components/Dashboard/components/MetricsList/GridView.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import WidgetWrapper from 'App/components/Dashboard/components/WidgetWrapper'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { withSiteId } from 'App/routes'; +interface Props extends RouteComponentProps { + list: any; + siteId: any; + selectedList: any; +} +function GridView(props: Props) { + const { siteId, list, selectedList, history } = props; + + const onItemClick = (metricId: number) => { + const path = withSiteId(`/metrics/${metricId}`, siteId); + history.push(path); + }; + + return ( + <div className="grid grid-cols-4 gap-4 m-4 items-start"> + {list.map((metric: any) => ( + <React.Fragment key={metric.metricId}> + <WidgetWrapper + key={metric.metricId} + widget={metric} + isGridView={true} + active={selectedList.includes(metric.metricId)} + isWidget={true} + onClick={() => onItemClick(parseInt(metric.metricId))} + /> + </React.Fragment> + ))} + </div> + ); +} + +export default withRouter(GridView); diff --git a/frontend/app/components/Dashboard/components/MetricsList/ListView.tsx b/frontend/app/components/Dashboard/components/MetricsList/ListView.tsx new file mode 100644 index 000000000..446ffa431 --- /dev/null +++ b/frontend/app/components/Dashboard/components/MetricsList/ListView.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import MetricListItem from '../MetricListItem'; +import { Checkbox } from 'UI'; + +interface Props { + list: any; + siteId: any; + selectedList: any; + toggleSelection?: (metricId: any) => void; + toggleAll?: (e: any) => void; + disableSelection?: boolean; + allSelected?: boolean + existingCardIds?: number[]; +} +function ListView(props: Props) { + const { siteId, list, selectedList, toggleSelection, disableSelection = false, allSelected = false } = props; + return ( + <div> + <div className="grid grid-cols-12 py-2 font-medium px-6"> + <div className="col-span-4 flex items-center"> + {!disableSelection && ( + <Checkbox + name="slack" + className="mr-4" + type="checkbox" + checked={allSelected} + // onClick={() => selectedList(list.map((i: any) => i.metricId))} + onClick={props.toggleAll} + /> + )} + <span>Title</span> + </div> + + <div className="col-span-4">Owner</div> + <div className="col-span-2">Visibility</div> + <div className="col-span-2 text-right">Last Modified</div> + </div> + {list.map((metric: any) => ( + <MetricListItem + disableSelection={disableSelection} + metric={metric} + siteId={siteId} + selected={selectedList.includes(parseInt(metric.metricId))} + toggleSelection={(e: any) => { + e.stopPropagation(); + toggleSelection && toggleSelection(parseInt(metric.metricId)); + }} + /> + ))} + </div> + ); +} + +export default ListView; diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx index 2869f5240..1b9f7dfc9 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx @@ -1,26 +1,48 @@ -import { observer } from 'mobx-react-lite'; -import React, { useEffect } from 'react'; -import { NoContent, Pagination, Icon } from 'UI'; +import { observer, useObserver } from 'mobx-react-lite'; +import React, { useEffect, useMemo, useState } from 'react'; +import { NoContent, Pagination } from 'UI'; import { useStore } from 'App/mstore'; -import { filterList } from 'App/utils'; -import MetricListItem from '../MetricListItem'; import { sliceListPerPage } from 'App/utils'; -import Widget from 'App/mstore/types/widget'; +import GridView from './GridView'; +import ListView from './ListView'; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; -function MetricsList({ siteId }: { siteId: string }) { - const { metricStore } = useStore(); - const metrics = metricStore.sortedWidgets; +function MetricsList({ + siteId, + onSelectionChange, +}: { + siteId: string; + onSelectionChange?: (selected: any[]) => void; +}) { + const { metricStore, dashboardStore } = useStore(); const metricsSearch = metricStore.metricsSearch; + const listView = useObserver(() => metricStore.listView); + const [selectedMetrics, setSelectedMetrics] = useState<any>([]); + + const dashboard = dashboardStore.selectedDashboard; + const existingCardIds = useMemo(() => dashboard?.widgets?.map(i => parseInt(i.metricId)), [dashboard]); + const cards = useMemo(() => !!onSelectionChange ? metricStore.filteredCards.filter(i => !existingCardIds?.includes(parseInt(i.metricId))) : metricStore.filteredCards, [metricStore.filteredCards]); - const filterByDashboard = (item: Widget, searchRE: RegExp) => { - const dashboardsStr = item.dashboards.map((d: any) => d.name).join(' '); - return searchRE.test(dashboardsStr); + useEffect(() => { + metricStore.fetchList(); + }, []); + + useEffect(() => { + if (!onSelectionChange) { + return; + } + onSelectionChange(selectedMetrics); + }, [selectedMetrics]); + + const toggleMetricSelection = (id: any) => { + if (selectedMetrics.includes(id)) { + setSelectedMetrics(selectedMetrics.filter((i: number) => i !== id)); + } else { + setSelectedMetrics([...selectedMetrics, id]); + } }; - const list = - metricsSearch !== '' - ? filterList(metrics, metricsSearch, ['name', 'metricType', 'owner'], filterByDashboard) - : metrics; - const lenth = list.length; + + const lenth = cards.length; useEffect(() => { metricStore.updateKey('sessionsPage', 1); @@ -31,33 +53,40 @@ function MetricsList({ siteId }: { siteId: string }) { show={lenth === 0} title={ <div className="flex flex-col items-center justify-center"> - <Icon name="no-metrics" size={80} color="figmaColors-accent-secondary" /> + <AnimatedSVG name={ICONS.NO_CARDS} size={180} /> <div className="text-center text-gray-600 my-4"> - {metricsSearch !== '' ? 'No matching results' : "You haven't created any metrics yet"} + {metricsSearch !== '' ? 'No matching results' : "You haven't created any cards yet"} </div> </div> } > - <div className="mt-3 border-b rounded bg-white"> - <div className="grid grid-cols-12 py-2 font-medium px-6"> - <div className="col-span-4">Title</div> - <div className="col-span-4">Owner</div> - <div className="col-span-2">Visibility</div> - <div className="col-span-2 text-right">Last Modified</div> - </div> + {listView ? ( + <ListView + disableSelection={!onSelectionChange} + siteId={siteId} + list={sliceListPerPage(cards, metricStore.page - 1, metricStore.pageSize)} + selectedList={selectedMetrics} + existingCardIds={existingCardIds} + toggleSelection={toggleMetricSelection} + allSelected={cards.length === selectedMetrics.length} + toggleAll={({ target: { checked, name } }) => + setSelectedMetrics(checked ? cards.map((i: any) => i.metricId) : []) + } + /> + ) : ( + <GridView + siteId={siteId} + list={sliceListPerPage(cards, metricStore.page - 1, metricStore.pageSize)} + selectedList={selectedMetrics} + toggleSelection={toggleMetricSelection} + /> + )} - {sliceListPerPage(list, metricStore.page - 1, metricStore.pageSize).map((metric: any) => ( - <React.Fragment key={metric.metricId}> - <MetricListItem metric={metric} siteId={siteId} /> - </React.Fragment> - ))} - </div> - - <div className="w-full flex items-center justify-between pt-4 px-6"> + <div className="w-full flex items-center justify-between py-4 px-6 border-t"> <div className="text-disabled-text"> Showing{' '} - <span className="font-semibold">{Math.min(list.length, metricStore.pageSize)}</span> out - of <span className="font-semibold">{list.length}</span> metrics + <span className="font-semibold">{Math.min(cards.length, metricStore.pageSize)}</span> out + of <span className="font-semibold">{cards.length}</span> cards </div> <Pagination page={metricStore.page} diff --git a/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx b/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx index cf27661d9..368816ce2 100644 --- a/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx +++ b/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx @@ -4,31 +4,34 @@ import { useStore } from 'App/mstore'; import { Icon } from 'UI'; import { debounce } from 'App/utils'; -let debounceUpdate: any = () => {} -function MetricsSearch(props) { - const { metricStore } = useStore(); - const [query, setQuery] = useState(metricStore.metricsSearch); - useEffect(() => { - debounceUpdate = debounce((key, value) => metricStore.updateKey(key, value), 500); - }, []) +let debounceUpdate: any = () => {}; +function MetricsSearch() { + const { metricStore } = useStore(); + const [query, setQuery] = useState(metricStore.filter.query); + useEffect(() => { + debounceUpdate = debounce( + (key: any, value: any) => metricStore.updateKey('filter', { ...metricStore.filter, query: value }), + 500 + ); + }, []); - const write = ({ target: { value } }) => { - setQuery(value); - debounceUpdate('metricsSearch', value); - } - - return useObserver(() => ( - <div className="relative"> - <Icon name="search" className="absolute top-0 bottom-0 ml-2 m-auto" size="16" /> - <input - value={query} - name="metricsSearch" - className="bg-white p-2 border border-borderColor-gray-light-shade rounded w-full pl-10" - placeholder="Filter by title, type, dashboard and owner" - onChange={write} - /> - </div> - )); + const write = ({ target: { value } }: any) => { + setQuery(value); + debounceUpdate('metricsSearch', value); + }; + + return useObserver(() => ( + <div className="relative"> + <Icon name="search" className="absolute top-0 bottom-0 ml-2 m-auto" size="16" /> + <input + value={query} + name="metricsSearch" + className="bg-white p-2 border border-borderColor-gray-light-shade rounded w-full pl-10" + placeholder="Filter by title and owner" + onChange={write} + /> + </div> + )); } export default MetricsSearch; diff --git a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx index 6c39114cd..a3621291b 100644 --- a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx +++ b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx @@ -1,40 +1,19 @@ import React from 'react'; -import { Button, PageTitle, Icon, Link } from 'UI'; import withPageTitle from 'HOCs/withPageTitle'; import MetricsList from '../MetricsList'; -import MetricsSearch from '../MetricsSearch'; -import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; +import MetricViewHeader from '../MetricViewHeader'; interface Props { - siteId: string; + siteId: string; } function MetricsView({ siteId }: Props) { - const { metricStore } = useStore(); - - React.useEffect(() => { - metricStore.fetchList(); - }, []); - return useObserver(() => ( - <div style={{ maxWidth: '1300px', margin: 'auto'}} className="bg-white rounded py-4 border"> - <div className="flex items-center mb-4 justify-between px-6"> - <div className="flex items-baseline mr-3"> - <PageTitle title="Metrics" className="" /> - </div> - <div className="ml-auto flex items-center"> - <Link to={'/metrics/create'}><Button variant="primary">Create Metric</Button></Link> - <div className="ml-4 w-1/4" style={{ minWidth: 300 }}> - <MetricsSearch /> - </div> - </div> - </div> - <div className="text-base text-disabled-text flex items-center px-6"> - <Icon name="info-circle-fill" className="mr-2" size={16} /> - Create custom Metrics to capture user frustrations, monitor your app's performance and track other KPIs. - </div> - <MetricsList siteId={siteId} /> - </div> - )); + return useObserver(() => ( + <div style={{ maxWidth: '1300px', margin: 'auto' }} className="bg-white rounded pt-4 border"> + <MetricViewHeader /> + <MetricsList siteId={siteId} /> + </div> + )); } -export default withPageTitle('Metrics - OpenReplay')(MetricsView); +export default withPageTitle('Cards - OpenReplay')(MetricsView); diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index 67247a2d2..cf44102d4 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -1,10 +1,10 @@ -import React, { useState, useRef, useEffect, useCallback } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import CustomMetriLineChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart'; import CustomMetricPercentage from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage'; import CustomMetricTable from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable'; import CustomMetricPieChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart'; import { Styles } from 'App/components/Dashboard/Widgets/common'; -import { observer, useObserver } from 'mobx-react-lite'; +import { observer } from 'mobx-react-lite'; import { Loader } from 'UI'; import { useStore } from 'App/mstore'; import WidgetPredefinedChart from '../WidgetPredefinedChart'; @@ -13,27 +13,31 @@ import { getStartAndEndTimestampsByDensity } from 'Types/dashboard/helper'; import { debounce } from 'App/utils'; import useIsMounted from 'App/hooks/useIsMounted' import { FilterKey } from 'Types/filter/filterType'; - +import { TIMESERIES, TABLE, CLICKMAP, FUNNEL, ERRORS, PERFORMANCE, RESOURCE_MONITORING, WEB_VITALS, INSIGHTS } from 'App/constants/card'; import FunnelWidget from 'App/components/Funnels/FunnelWidget'; -import ErrorsWidget from '../Errors/ErrorsWidget'; import SessionWidget from '../Sessions/SessionWidget'; import CustomMetricTableSessions from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions'; import CustomMetricTableErrors from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableErrors'; +import ClickMapCard from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/ClickMapCard' +import InsightsCard from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard'; + interface Props { metric: any; isWidget?: boolean; isTemplate?: boolean; + isPreview?: boolean; } + function WidgetChart(props: Props) { const { isWidget = false, metric, isTemplate } = props; - const { dashboardStore, metricStore } = useStore(); + const { dashboardStore, metricStore, sessionStore } = useStore(); const _metric: any = metricStore.instance; - const period = useObserver(() => dashboardStore.period); - const drillDownPeriod = useObserver(() => dashboardStore.drillDownPeriod); + const period = dashboardStore.period; + const drillDownPeriod = dashboardStore.drillDownPeriod; const drillDownFilter = dashboardStore.drillDownFilter; const colors = Styles.customMetricColors; const [loading, setLoading] = useState(true) - const isOverviewWidget = metric.metricType === 'predefined' && metric.viewType === 'overview'; + const isOverviewWidget = metric.metricType === WEB_VITALS; const params = { density: isOverviewWidget ? 7 : 70 } const metricParams = { ...params } const prevMetricRef = useRef<any>(); @@ -43,10 +47,16 @@ function WidgetChart(props: Props) { const isTableWidget = metric.metricType === 'table' && metric.viewType === 'table'; const isPieChart = metric.metricType === 'table' && metric.viewType === 'pieChart'; + useEffect(() => { + return () => { + dashboardStore.resetDrillDownFilter(); + } + }, []) + const onChartClick = (event: any) => { if (event) { if (isTableWidget || isPieChart) { // get the filter of clicked row - const periodTimestamps = period.toTimestamps() + const periodTimestamps = drillDownPeriod.toTimestamps() drillDownFilter.merge({ filters: event, startTimestamp: periodTimestamps.startTimestamp, @@ -77,43 +87,42 @@ function WidgetChart(props: Props) { } const debounceRequest: any = React.useCallback(debounce(fetchMetricChartData, 500), []); - useEffect(() => { + const loadPage = () => { if (prevMetricRef.current && prevMetricRef.current.name !== metric.name) { prevMetricRef.current = metric; return - }; + } prevMetricRef.current = metric; const timestmaps = drillDownPeriod.toTimestamps(); const payload = isWidget ? { ...params } : { ...metricParams, ...timestmaps, ...metric.toJson() }; debounceRequest(metric, payload, isWidget, !isWidget ? drillDownPeriod : period); - }, [drillDownPeriod, period, depsString, _metric.page, metric.metricType, metric.metricOf, metric.viewType]); + } + useEffect(() => { + _metric.updateKey('page', 1) + loadPage(); + }, [drillDownPeriod, period, depsString, metric.metricType, metric.metricOf, metric.viewType, metric.metricValue]); + useEffect(loadPage, [_metric.page]); const renderChart = () => { const { metricType, viewType, metricOf } = metric; - const metricWithData = { ...metric, data }; - if (metricType === 'sessions') { - return <SessionWidget metric={metric} data={data} /> - } - if (metricType === 'errors') { - return <ErrorsWidget metric={metric} data={data} /> - } - - if (metricType === 'funnel') { + if (metricType === FUNNEL) { return <FunnelWidget metric={metric} data={data} isWidget={isWidget || isTemplate} /> } - if (metricType === 'predefined') { - const defaultMetric = metric.data.chart.length === 0 ? metricWithData : metric + if (metricType === 'predefined' || metricType === ERRORS || metricType === PERFORMANCE || metricType === RESOURCE_MONITORING || metricType === WEB_VITALS) { + const defaultMetric = metric.data.chart && metric.data.chart.length === 0 ? metricWithData : metric if (isOverviewWidget) { return <CustomMetricOverviewChart data={data} /> } - return <WidgetPredefinedChart isTemplate={isTemplate} metric={defaultMetric} data={data} predefinedKey={metric.predefinedKey} /> + return <WidgetPredefinedChart isTemplate={isTemplate} metric={defaultMetric} data={data} predefinedKey={metric.metricOf} /> } - if (metricType === 'timeseries') { + // TODO add USER_PATH, RETENTION, FEATUER_ADOPTION + + if (metricType === TIMESERIES) { if (viewType === 'lineChart') { return ( <CustomMetriLineChart @@ -134,7 +143,7 @@ function WidgetChart(props: Props) { } } - if (metricType === 'table') { + if (metricType === TABLE) { if (metricOf === FilterKey.SESSIONS) { return ( <CustomMetricTableSessions @@ -155,7 +164,7 @@ function WidgetChart(props: Props) { /> ) } - if (viewType === 'table') { + if (viewType === TABLE) { return ( <CustomMetricTable metric={metric} data={data[0]} @@ -175,8 +184,24 @@ function WidgetChart(props: Props) { ) } } + if (metricType === CLICKMAP) { + if (!props.isPreview) { + return ( + <div style={{ height: '229px', overflow:'hidden', marginBottom: '10px'}}> + <img src={metric.thumbnail} alt="clickmap thumbnail" /> + </div> + ) + } + return ( + <ClickMapCard /> + ) + } - return <div>Unknown</div>; + if (metricType === INSIGHTS) { + return <InsightsCard data={data} /> + } + + return <div>Unknown metric type</div>; } return ( <Loader loading={loading} style={{ height: `${isOverviewWidget ? 100 : 240}px` }}> diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx index bf0ccda20..59a1b619a 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx @@ -1,228 +1,261 @@ -import React from 'react'; -import { metricTypes, metricOf, issueOptions } from 'App/constants/filterOptions'; +import React, { useEffect, useState } from 'react'; +import { metricOf, issueOptions, issueCategories } from 'App/constants/filterOptions'; import { FilterKey } from 'Types/filter/filterType'; import { useStore } from 'App/mstore'; -import { useObserver } from 'mobx-react-lite'; -import { Button, Icon, SegmentSelection } from 'UI' +import { observer } from 'mobx-react-lite'; +import { Button, Icon, confirm, Tooltip } from 'UI'; import FilterSeries from '../FilterSeries'; -import { confirm, Tooltip } from 'UI'; -import Select from 'Shared/Select' -import { withSiteId, dashboardMetricDetails, metricDetails } from 'App/routes' - +import Select from 'Shared/Select'; +import { withSiteId, dashboardMetricDetails, metricDetails } from 'App/routes'; +import MetricTypeDropdown from './components/MetricTypeDropdown'; +import MetricSubtypeDropdown from './components/MetricSubtypeDropdown'; +import { + TIMESERIES, + TABLE, + CLICKMAP, + FUNNEL, + ERRORS, + RESOURCE_MONITORING, + PERFORMANCE, + WEB_VITALS, + INSIGHTS, +} from 'App/constants/card'; +import { eventKeys } from 'App/types/filter/newFilter'; +import { renderClickmapThumbnail } from './renderMap'; +import Widget from 'App/mstore/types/widget'; interface Props { - history: any; - match: any; - onDelete: () => void; -} - -const metricIcons = { - timeseries: 'graph-up', - table: 'table', - funnel: 'funnel', + history: any; + match: any; + onDelete: () => void; } function WidgetForm(props: Props) { + const { + history, + match: { + params: { siteId, dashboardId }, + }, + } = props; + const { metricStore, dashboardStore } = useStore(); + const isSaving = metricStore.isSaving; + const metric: any = metricStore.instance; + const [initialInstance, setInitialInstance] = useState(); - const { history, match: { params: { siteId, dashboardId } } } = props; - const { metricStore, dashboardStore } = useStore(); - const dashboards = dashboardStore.dashboards; - const isSaving = useObserver(() => metricStore.isSaving); - const metric: any = useObserver(() => metricStore.instance) + const timeseriesOptions = metricOf.filter((i) => i.type === 'timeseries'); + const tableOptions = metricOf.filter((i) => i.type === 'table'); + const isTable = metric.metricType === TABLE; + const isClickmap = metric.metricType === CLICKMAP; + const isFunnel = metric.metricType === FUNNEL; + const isInsights = metric.metricType === INSIGHTS; + const canAddSeries = metric.series.length < 3; + const eventsLength = metric.series[0].filter.filters.filter((i: any) => i.isEvent).length; + const cannotSaveFunnel = isFunnel && (!metric.series[0] || eventsLength <= 1); - const timeseriesOptions = metricOf.filter(i => i.type === 'timeseries'); - const tableOptions = metricOf.filter(i => i.type === 'table'); - const isTable = metric.metricType === 'table'; - const isFunnel = metric.metricType === 'funnel'; - const canAddToDashboard = metric.exists() && dashboards.length > 0; - const canAddSeries = metric.series.length < 3; - const eventsLength = useObserver(() => metric.series[0].filter.filters.filter((i: any) => i.isEvent).length) - const cannotSaveFunnel = isFunnel && (!metric.series[0] || eventsLength <= 1); + const isPredefined = [ERRORS, PERFORMANCE, RESOURCE_MONITORING, WEB_VITALS].includes( + metric.metricType + ); - const writeOption = ({ value, name }: any) => { - value = Array.isArray(value) ? value : value.value - const obj: any = { [ name ]: value }; + const excludeFilterKeys = isClickmap ? eventKeys : []; - if (name === 'metricValue') { - obj['metricValue'] = value; + useEffect(() => { + if (!!metric && !initialInstance) { + setInitialInstance(metric.toJson()); + } + }, [metric]); - // handle issues (remove all when other option is selected) - if (Array.isArray(obj['metricValue']) && obj['metricValue'].length > 1) { - obj['metricValue'] = obj['metricValue'].filter(i => i.value !== 'all'); - } - } + const writeOption = ({ value, name }: { value: any; name: any }) => { + value = Array.isArray(value) ? value : value.value; + const obj: any = { [name]: value }; - if (name === 'metricOf') { - // if (value === FilterKey.ISSUE) { - // obj['metricValue'] = [{ value: 'all', label: 'All' }]; - // } - } - - if (name === 'metricType') { - if (value === 'timeseries') { - obj['metricOf'] = timeseriesOptions[0].value; - obj['viewType'] = 'lineChart'; - } else if (value === 'table') { - obj['metricOf'] = tableOptions[0].value; - obj['viewType'] = 'table'; - } - } - - metricStore.merge(obj); - }; - - const onSelect = (_: any, option: Record<string, any>) => writeOption({ value: { value: option.value }, name: option.name}) - - const onSave = () => { - const wasCreating = !metric.exists() - metricStore.save(metric, dashboardId) - .then((metric: any) => { - if (wasCreating) { - if (parseInt(dashboardId) > 0) { - history.replace(withSiteId(dashboardMetricDetails(dashboardId, metric.metricId), siteId)); - const dashboard = dashboardStore.getDashboard(parseInt(dashboardId)) - dashboardStore.addWidgetToDashboard(dashboard, [metric.metricId]) - } else { - history.replace(withSiteId(metricDetails(metric.metricId), siteId)); - } - } - }); + if (name === 'metricType') { + switch (value) { + case TIMESERIES: + obj.metricOf = timeseriesOptions[0].value; + break; + case TABLE: + obj.metricOf = tableOptions[0].value; + break; + } } - const onDelete = async () => { - if (await confirm({ - header: 'Confirm', - confirmButton: 'Yes, delete', - confirmation: `Are you sure you want to permanently delete this metric?` - })) { - metricStore.delete(metric).then(props.onDelete); - } + metricStore.merge(obj); + }; + + const onSave = async () => { + const wasCreating = !metric.exists(); + if (isClickmap) { + try { + metric.thumbnail = await renderClickmapThumbnail(); + } catch (e) { + console.error(e); + } } + const savedMetric = await metricStore.save(metric); + setInitialInstance(metric.toJson()) + if (wasCreating) { + if (parseInt(dashboardId, 10) > 0) { + history.replace( + withSiteId(dashboardMetricDetails(dashboardId, savedMetric.metricId), siteId) + ); + dashboardStore.addWidgetToDashboard( + dashboardStore.getDashboard(parseInt(dashboardId, 10))!, + [savedMetric.metricId] + ); + } else { + history.replace(withSiteId(metricDetails(savedMetric.metricId), siteId)); + } + } + }; - return useObserver(() => ( - <div className="p-6"> - <div className="form-group"> - <label className="font-medium">Metric Type</label> - <div className="flex items-center"> - <SegmentSelection - icons - outline - name="metricType" - className="my-3" - onSelect={ onSelect } - value={metricTypes.find((i) => i.value === metric.metricType) || metricTypes[0]} - // @ts-ignore - list={metricTypes.map((i) => ({ value: i.value, name: i.label, icon: metricIcons[i.value] }))} - /> + const onDelete = async () => { + if ( + await confirm({ + header: 'Confirm', + confirmButton: 'Yes, delete', + confirmation: `Are you sure you want to permanently delete this card?`, + }) + ) { + metricStore.delete(metric).then(props.onDelete); + } + }; - {metric.metricType === 'timeseries' && ( - <> - <span className="mx-3">of</span> - <Select - name="metricOf" - options={timeseriesOptions} - defaultValue={metric.metricOf} - onChange={ writeOption } - /> - </> - )} + const undoChnages = () => { + const w = new Widget(); + metricStore.merge(w.fromJson(initialInstance), false); + }; - {metric.metricType === 'table' && ( - <> - <span className="mx-3">of</span> - <Select - name="metricOf" - options={tableOptions} - defaultValue={metric.metricOf} - onChange={ writeOption } - /> - </> - )} + return ( + <div className="p-6"> + <div className="form-group"> + <div className="flex items-center"> + <span className="mr-2">Card showing</span> + <MetricTypeDropdown onSelect={writeOption} /> + <MetricSubtypeDropdown onSelect={writeOption} /> - {metric.metricOf === FilterKey.ISSUE && ( - <> - <span className="mx-3">issue type</span> - <Select - name="metricValue" - options={issueOptions} - value={metric.metricValue} - onChange={ writeOption } - isMulti={true} - placeholder="All Issues" - /> - </> - )} + {metric.metricOf === FilterKey.ISSUE && metric.metricType === TABLE && ( + <> + <span className="mx-3">issue type</span> + <Select + name="metricValue" + options={issueOptions} + value={metric.metricValue} + onChange={writeOption} + isMulti={true} + placeholder="All Issues" + /> + </> + )} - {metric.metricType === 'table' && !(metric.metricOf === FilterKey.ERRORS || metric.metricOf === FilterKey.SESSIONS) && ( - <> - <span className="mx-3">showing</span> - <Select - name="metricFormat" - options={[ - { value: 'sessionCount', label: 'Session Count' }, - ]} - defaultValue={ metric.metricFormat } - onChange={ writeOption } - /> - </> - )} - </div> - </div> + {metric.metricType === INSIGHTS && ( + <> + <span className="mx-3">of</span> + <Select + name="metricValue" + options={issueCategories} + value={metric.metricValue} + onChange={writeOption} + isMulti={true} + placeholder="All Categories" + /> + </> + )} - <div className="form-group"> - <div className="flex items-center font-medium py-2"> - {`${(isTable || isFunnel) ? 'Filter by' : 'Chart Series'}`} - {!isTable && !isFunnel && ( - <Button - className="ml-2" - variant="text-primary" - onClick={() => metric.addSeries()} - disabled={!canAddSeries} - >Add Series</Button> - )} - </div> - - {metric.series.length > 0 && metric.series.slice(0, (isTable || isFunnel) ? 1 : metric.series.length).map((series: any, index: number) => ( - <div className="mb-2" key={series.name}> - <FilterSeries - observeChanges={() => metric.updateKey('hasChanged', true)} - hideHeader={ isTable } - seriesIndex={index} - series={series} - onRemoveSeries={() => metric.removeSeries(index)} - canDelete={metric.series.length > 1} - emptyMessage={isTable ? - 'Filter data using any event or attribute. Use Add Step button below to do so.' : - 'Add user event or filter to define the series by clicking Add Step.' - } - /> - </div> - ))} - </div> - - <div className="form-groups flex items-center justify-between"> - <Tooltip - title="Cannot save funnel metric without at least 2 events" - disabled={!cannotSaveFunnel} - > - <Button - variant="primary" - onClick={onSave} - disabled={isSaving || cannotSaveFunnel} - > - {metric.exists() ? 'Update' : 'Create'} - </Button> - </Tooltip> - <div className="flex items-center"> - {metric.exists() && ( - <Button variant="text-primary" onClick={onDelete}> - <Icon name="trash" size="14" className="mr-2" color="teal"/> - Delete - </Button> - )} - </div> - </div> + {metric.metricType === 'table' && + !(metric.metricOf === FilterKey.ERRORS || metric.metricOf === FilterKey.SESSIONS) && ( + <> + <span className="mx-3">showing</span> + <Select + name="metricFormat" + options={[{ value: 'sessionCount', label: 'Session Count' }]} + defaultValue={metric.metricFormat} + onChange={writeOption} + /> + </> + )} </div> - )); + </div> + + {isPredefined && ( + <div className="flex items-center my-6 justify-center"> + <Icon name="info-circle" size="18" color="gray-medium" /> + <div className="ml-2"> + Filtering and drill-downs will be supported soon for this card type. + </div> + </div> + )} + + {!isPredefined && ( + <div className="form-group"> + <div className="flex items-center font-medium py-2"> + {`${isTable || isFunnel || isClickmap || isInsights ? 'Filter by' : 'Chart Series'}`} + {!isTable && !isFunnel && !isClickmap && !isInsights && ( + <Button + className="ml-2" + variant="text-primary" + onClick={() => metric.addSeries()} + disabled={!canAddSeries} + > + ADD + </Button> + )} + </div> + + {metric.series.length > 0 && + metric.series + .slice(0, isTable || isFunnel || isClickmap || isInsights ? 1 : metric.series.length) + .map((series: any, index: number) => ( + <div className="mb-2" key={series.name}> + <FilterSeries + supportsEmpty={!isClickmap} + excludeFilterKeys={excludeFilterKeys} + observeChanges={() => metric.updateKey('hasChanged', true)} + hideHeader={isTable || isClickmap || isInsights} + seriesIndex={index} + series={series} + onRemoveSeries={() => metric.removeSeries(index)} + canDelete={metric.series.length > 1} + emptyMessage={ + isTable + ? 'Filter data using any event or attribute. Use Add Step button below to do so.' + : 'Add user event or filter to define the series by clicking Add Step.' + } + /> + </div> + ))} + </div> + )} + + <div className="form-groups flex items-center justify-between"> + <Tooltip + title="Cannot save funnel metric without at least 2 events" + disabled={!cannotSaveFunnel} + > + <div className="flex items-center"> + <Button variant="primary" onClick={onSave} disabled={isSaving || cannotSaveFunnel}> + {metric.exists() + ? 'Update' + : parseInt(dashboardId) > 0 + ? 'Create & Add to Dashboard' + : 'Create'} + </Button> + {metric.exists() && metric.hasChanged && ( + <Button onClick={undoChnages} variant="text" icon="arrow-counterclockwise" className="ml-2"> + Undo + </Button> + )} + </div> + </Tooltip> + <div className="flex items-center"> + {metric.exists() && ( + <Button variant="text-primary" onClick={onDelete}> + <Icon name="trash" size="14" className="mr-2" color="teal" /> + Delete + </Button> + )} + </div> + </div> + </div> + ); } -export default WidgetForm; +export default observer(WidgetForm); diff --git a/frontend/app/components/Dashboard/components/WidgetForm/components/MetricSubtypeDropdown/MetricSubtypeDropdown.tsx b/frontend/app/components/Dashboard/components/WidgetForm/components/MetricSubtypeDropdown/MetricSubtypeDropdown.tsx new file mode 100644 index 000000000..37e2b46ad --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetForm/components/MetricSubtypeDropdown/MetricSubtypeDropdown.tsx @@ -0,0 +1,66 @@ +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite'; +import { TYPES } from 'App/constants/card'; +import { MetricType } from 'App/components/Dashboard/components/MetricTypeItem/MetricTypeItem'; +import React from 'react'; +import Select from 'Shared/Select'; +import { components } from 'react-select'; +import CustomDropdownOption from 'Shared/CustomDropdownOption'; + +interface Props { + onSelect: any; +} +function MetricSubtypeDropdown(props: Props) { + const { metricStore } = useStore(); + const metric: any = metricStore.instance; + + const options: any = React.useMemo(() => { + const type = TYPES.find((i: MetricType) => i.slug === metric.metricType); + if (type && type.subTypes) { + const options = type.subTypes.map((i: MetricType) => ({ + label: i.title, + icon: i.icon, + value: i.slug, + description: i.description, + })); + return options; + } + return false; + }, [metric.metricType]); + + React.useEffect(() => { + // @ts-ignore + if (options && !options.map(i => i.value).includes(metric.metricOf)) { + setTimeout(() => props.onSelect({ name: 'metricOf', value: { value: options[0].value }}), 0) + } + }, [metric.metricType]) + + return options ? ( + <> + <div className="mx-3">of</div> + <Select + name="metricOf" + placeholder="Select Card Type" + options={options} + value={options.find((i: any) => i.value === metric.metricOf)} + onChange={props.onSelect} + // className="mx-2" + components={{ + MenuList: ({ children, ...props }: any) => { + return ( + <components.MenuList {...props} className="!p-3"> + {children} + </components.MenuList> + ); + }, + Option: ({ children, ...props }: any) => { + const { data } = props; + return <CustomDropdownOption children={children} {...props} {...data} />; + }, + }} + /> + </> + ) : null; +} + +export default observer(MetricSubtypeDropdown); diff --git a/frontend/app/components/Dashboard/components/WidgetForm/components/MetricSubtypeDropdown/index.ts b/frontend/app/components/Dashboard/components/WidgetForm/components/MetricSubtypeDropdown/index.ts new file mode 100644 index 000000000..6ab331224 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetForm/components/MetricSubtypeDropdown/index.ts @@ -0,0 +1 @@ +export { default } from './MetricSubtypeDropdown'; diff --git a/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/MetricTypeDropdown.stories.tsx b/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/MetricTypeDropdown.stories.tsx new file mode 100644 index 000000000..4280ed4cc --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/MetricTypeDropdown.stories.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import MetricTypeDropdown from './'; + +export default { + title: 'Dashboad/Cards/Form/MetricTypeDropdown', + component: MetricTypeDropdown, +}; + +const Template = (args: any) => <MetricTypeDropdown {...args} />; + +export const Simple = Template.bind({}); diff --git a/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/MetricTypeDropdown.tsx b/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/MetricTypeDropdown.tsx new file mode 100644 index 000000000..060c9c580 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/MetricTypeDropdown.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { DROPDOWN_OPTIONS, INSIGHTS, Option } from 'App/constants/card'; +import Select from 'Shared/Select'; +import { components } from 'react-select'; +import CustomDropdownOption from 'Shared/CustomDropdownOption'; +import { observer } from 'mobx-react-lite'; +import { useStore } from 'App/mstore'; +import withLocationHandlers from 'HOCs/withLocationHandlers'; +import { Icon } from 'UI'; +import { connect } from 'react-redux'; + +interface Props { + query: Record<string, (key: string) => any>; + onSelect: (arg: any) => void; + isEnterprise?: boolean; +} +function MetricTypeDropdown(props: Props) { + const { isEnterprise } = props; + const { metricStore } = useStore(); + const metric: any = metricStore.instance; + + const options = React.useMemo(() => { + return DROPDOWN_OPTIONS.map((option: any) => { + return { + ...option, + disabled: !isEnterprise && option.value === INSIGHTS, + }; + }); + }, []); + + React.useEffect(() => { + const queryCardType = props.query.get('type'); + if (queryCardType && options.length > 0 && metric.metricType) { + const type: Option = options.find((i) => i.value === queryCardType) as Option; + if (type.disabled) { + return; + } + setTimeout(() => onChange(type.value), 0); + } + }, []); + + const onChange = (type: string) => { + metricStore.changeType(type); + }; + + return ( + <Select + name="metricType" + placeholder="Select Card Type" + options={options} + isOptionDisabled={(option: Option) => option.disabled} + value={ + DROPDOWN_OPTIONS.find((i: any) => i.value === metric.metricType) || DROPDOWN_OPTIONS[0] + } + onChange={props.onSelect} + components={{ + SingleValue: ({ children, ...props }: any) => { + const { + data: { icon, label }, + } = props; + return ( + <components.SingleValue {...props}> + <div className="flex items-center"> + <Icon name={icon} size="18" color="gray-medium" /> + <div className="ml-2">{label}</div> + </div> + </components.SingleValue> + ); + }, + MenuList: ({ children, ...props }: any) => { + return ( + <components.MenuList {...props} className="!p-3"> + {children} + </components.MenuList> + ); + }, + Option: ({ children, ...props }: any) => { + const { data } = props; + return <CustomDropdownOption children={children} {...props} {...data} />; + }, + }} + /> + ); +} + +export default connect((state: any) => ({ + isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee', +}))(withLocationHandlers()(observer(MetricTypeDropdown))); diff --git a/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/index.ts b/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/index.ts new file mode 100644 index 000000000..ae7bd4cb1 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetForm/components/MetricTypeDropdown/index.ts @@ -0,0 +1 @@ +export { default } from './MetricTypeDropdown'; diff --git a/frontend/app/components/Dashboard/components/WidgetForm/renderMap.ts b/frontend/app/components/Dashboard/components/WidgetForm/renderMap.ts new file mode 100644 index 000000000..05aaff537 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetForm/renderMap.ts @@ -0,0 +1,29 @@ +export const renderClickmapThumbnail = () => { + // @ts-ignore + return import('html2canvas').then(({ default: html2canvas }) => { + // @ts-ignore + window.html2canvas = html2canvas; + const element = document.querySelector<HTMLIFrameElement>('#clickmap-render * iframe').contentDocument.body + if (element) { + const dimensions = element.getBoundingClientRect() + return html2canvas( + element, + { + scale: 1, + // allowTaint: true, + useCORS: true, + foreignObjectRendering: true, + height: dimensions.height > 900 ? 900 : dimensions.height, + width: dimensions.width > 1200 ? 1200 : dimensions.width, + x: 0, + y: 0, + ignoreElements: (e) => e.id.includes('render-ignore'), + } + ).then((canvas) => { + return canvas.toDataURL('img/png'); + }).catch(console.log); + } else { + Promise.reject("can't find clickmap container") + } + }) +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx b/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx index 3f352165a..a560235e9 100644 --- a/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx +++ b/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx @@ -64,12 +64,12 @@ function WidgetName(props: Props) { /> ) : ( // @ts-ignore - <Tooltip title="Double click to rename" disabled={!canEdit}> - <div - onDoubleClick={() => setEditing(true)} + <Tooltip delay={200} title="Double click to rename" disabled={!canEdit}> + <div + onDoubleClick={() => setEditing(true)} className={ cn( - "text-2xl h-8 flex items-center border-transparent", + "text-2xl h-8 flex items-center border-transparent", canEdit && 'cursor-pointer select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium' ) } @@ -77,7 +77,7 @@ function WidgetName(props: Props) { { name } </div> </Tooltip> - + )} { canEdit && <div className="ml-3 cursor-pointer" onClick={() => setEditing(true)}><Icon name="pencil" size="14" /></div> } </div> diff --git a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx index 9f246794e..88da5d888 100644 --- a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx @@ -1,6 +1,4 @@ import React from 'react'; -import { Styles } from 'App/components/Dashboard/Widgets/common'; -import CustomMetricOverviewChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart'; import ErrorsByType from 'App/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType'; import ErrorsByOrigin from 'App/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin'; import ErrorsPerDomain from 'App/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain'; @@ -27,6 +25,7 @@ import CallWithErrors from '../../Widgets/PredefinedWidgets/CallWithErrors'; import SpeedIndexByLocation from '../../Widgets/PredefinedWidgets/SpeedIndexByLocation'; import SlowestResources from '../../Widgets/PredefinedWidgets/SlowestResources'; import ResponseTimeDistribution from '../../Widgets/PredefinedWidgets/ResponseTimeDistribution'; +import { FilterKey } from 'Types/filter/filterType'; interface Props { data: any; @@ -40,59 +39,59 @@ function WidgetPredefinedChart(props: Props) { const renderWidget = () => { switch (predefinedKey) { // ERRORS - case 'errors_per_type': + case FilterKey.ERRORS_PER_TYPE: return <ErrorsByType data={data} metric={metric} /> - case 'errors_per_domains': + case FilterKey.ERRORS_PER_DOMAINS: return <ErrorsPerDomain data={data} metric={metric} /> - case 'resources_by_party': + case FilterKey.RESOURCES_BY_PARTY: return <ErrorsByOrigin data={data} metric={metric} /> - case 'impacted_sessions_by_js_errors': + case FilterKey.IMPACTED_SESSIONS_BY_JS_ERRORS: return <SessionsAffectedByJSErrors data={data} metric={metric} /> - case 'domains_errors_4xx': + case FilterKey.DOMAINS_ERRORS_4XX: return <CallsErrors4xx data={data} metric={metric} /> - case 'domains_errors_5xx': + case FilterKey.DOMAINS_ERRORS_5XX: return <CallsErrors5xx data={data} metric={metric} /> - case 'calls_errors': + case FilterKey.CALLS_ERRORS: return <CallWithErrors isTemplate={isTemplate} data={data} metric={metric} /> // PERFORMANCE - case 'impacted_sessions_by_slow_pages': + case FilterKey.IMPACTED_SESSIONS_BY_SLOW_PAGES: return <SessionsImpactedBySlowRequests data={data} metric={metric} /> - case 'pages_response_time_distribution': + case FilterKey.PAGES_RESPONSE_TIME_DISTRIBUTION: return <ResponseTimeDistribution data={data} metric={metric} /> - case 'speed_location': + case FilterKey.SPEED_LOCATION: return <SpeedIndexByLocation metric={metric} /> - case 'cpu': + case FilterKey.CPU: return <CPULoad data={data} metric={metric} /> - case 'crashes': + case FilterKey.CRASHES: return <Crashes data={data} metric={metric} /> - case 'pages_dom_buildtime': + case FilterKey.PAGES_DOM_BUILD_TIME: return <DomBuildingTime data={data} metric={metric} /> - case 'fps': + case FilterKey.FPS: return <FPS data={data} metric={metric} /> - case 'memory_consumption': + case FilterKey.MEMORY_CONSUMPTION: return <MemoryConsumption data={data} metric={metric} /> - case 'pages_response_time': + case FilterKey.PAGES_RESPONSE_TIME: return <ResponseTime data={data} metric={metric} /> - case 'resources_vs_visually_complete': + case FilterKey.RESOURCES_VS_VISUALLY_COMPLETE: return <ResourceLoadedVsVisuallyComplete data={data} metric={metric} /> - case 'sessions_per_browser': + case FilterKey.SESSIONS_PER_BROWSER: return <SessionsPerBrowser data={data} metric={metric} /> - case 'slowest_domains': + case FilterKey.SLOWEST_DOMAINS: return <SlowestDomains data={data} metric={metric} /> - case 'time_to_render': + case FilterKey.TIME_TO_RENDER: return <TimeToRender data={data} metric={metric} /> // Resources - case 'resources_count_by_type': + case FilterKey.BREAKDOWN_OF_LOADED_RESOURCES: return <BreakdownOfLoadedResources data={data} metric={metric} /> - case 'missing_resources': + case FilterKey.MISSING_RESOURCES: return <MissingResources isTemplate={isTemplate} data={data} metric={metric} /> - case 'resource_type_vs_response_end': + case FilterKey.RESOURCE_TYPE_VS_RESPONSE_END: return <ResourceLoadedVsResponseEnd data={data} metric={metric} /> - case 'resources_loading_time': + case FilterKey.RESOURCES_LOADING_TIME: return <ResourceLoadingTime data={data} metric={metric} /> - case 'slowest_resources': + case FilterKey.SLOWEST_RESOURCES: return <SlowestResources isTemplate={isTemplate} data={data} metric={metric} /> default: diff --git a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx index bea850d11..7c534a8bb 100644 --- a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx @@ -3,44 +3,35 @@ import cn from 'classnames'; import WidgetWrapper from '../WidgetWrapper'; import { useStore } from 'App/mstore'; import { SegmentSelection, Button, Icon } from 'UI'; -import { useObserver } from 'mobx-react-lite'; +import { observer } from 'mobx-react-lite'; import { FilterKey } from 'Types/filter/filterType'; import WidgetDateRange from '../WidgetDateRange/WidgetDateRange'; -// import Period, { LAST_24_HOURS, LAST_30_DAYS } from 'Types/app/period'; +import ClickMapRagePicker from "Components/Dashboard/components/ClickMapRagePicker"; import DashboardSelectionModal from '../DashboardSelectionModal/DashboardSelectionModal'; +import { CLICKMAP, TABLE, TIMESERIES } from "App/constants/card"; interface Props { className?: string; name: string; + isEditing?: boolean; } function WidgetPreview(props: Props) { const [showDashboardSelectionModal, setShowDashboardSelectionModal] = React.useState(false); const { className = '' } = props; const { metricStore, dashboardStore } = useStore(); const dashboards = dashboardStore.dashboards; - const metric: any = useObserver(() => metricStore.instance); - const isTimeSeries = metric.metricType === 'timeseries'; - const isTable = metric.metricType === 'table'; - const drillDownFilter = useObserver(() => dashboardStore.drillDownFilter); - const disableVisualization = useObserver(() => metric.metricOf === FilterKey.SESSIONS || metric.metricOf === FilterKey.ERRORS); - // const period = useObserver(() => dashboardStore.drillDownPeriod); + const metric: any = metricStore.instance; + const isTimeSeries = metric.metricType === TIMESERIES; + const isTable = metric.metricType === TABLE; + const disableVisualization = metric.metricOf === FilterKey.SESSIONS || metric.metricOf === FilterKey.ERRORS; - const chagneViewType = (e, { name, value }: any) => { + const changeViewType = (_, { name, value }: any) => { metric.update({ [ name ]: value }); } - // const onChangePeriod = (period: any) => { - // dashboardStore.setDrillDownPeriod(period); - // const periodTimestamps = period.toTimestamps(); - // drillDownFilter.merge({ - // startTimestamp: periodTimestamps.startTimestamp, - // endTimestamp: periodTimestamps.endTimestamp, - // }) - // } - const canAddToDashboard = metric.exists() && dashboards.length > 0; - return useObserver(() => ( + return ( <> <div className={cn(className, 'bg-white rounded border')}> <div className="flex items-center justify-between px-4 pt-2"> @@ -55,8 +46,8 @@ function WidgetPreview(props: Props) { name="viewType" className="my-3" primary - icons={true} - onSelect={ chagneViewType } + size="small" + onSelect={ changeViewType } value={{ value: metric.viewType }} list={ [ { value: 'lineChart', name: 'Chart', icon: 'graph-up-arrow' }, @@ -73,8 +64,8 @@ function WidgetPreview(props: Props) { name="viewType" className="my-3" primary={true} - icons={true} - onSelect={ chagneViewType } + size="small" + onSelect={ changeViewType } value={{ value: metric.viewType }} list={[ { value: 'table', name: 'Table', icon: 'table' }, @@ -85,6 +76,9 @@ function WidgetPreview(props: Props) { </> )} <div className="mx-4" /> + {metric.metricType === CLICKMAP ? ( + <ClickMapRagePicker /> + ) : null} <WidgetDateRange /> {/* add to dashboard */} {metric.exists() && ( @@ -93,7 +87,7 @@ function WidgetPreview(props: Props) { className="ml-2 p-0" onClick={() => setShowDashboardSelectionModal(true)} disabled={!canAddToDashboard} - > + > <Icon name="columns-gap-filled" size="14" className="mr-2" color="teal"/> Add to Dashboard </Button> @@ -112,7 +106,7 @@ function WidgetPreview(props: Props) { /> )} </> - )); + ); } -export default WidgetPreview; +export default observer(WidgetPreview); diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx index d3a092b49..3720dd94b 100644 --- a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx @@ -1,141 +1,212 @@ import React, { useEffect, useState } from 'react'; -import { NoContent, Loader, Pagination } from 'UI'; +import { NoContent, Loader, Pagination, Button } from 'UI'; import Select from 'Shared/Select'; import cn from 'classnames'; import { useStore } from 'App/mstore'; import SessionItem from 'Shared/SessionItem'; -import { observer, useObserver } from 'mobx-react-lite'; +import { observer } from 'mobx-react-lite'; import { DateTime } from 'luxon'; import { debounce } from 'App/utils'; import useIsMounted from 'App/hooks/useIsMounted'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import { numberWithCommas } from 'App/utils'; +import { CLICKMAP } from 'App/constants/card'; interface Props { - className?: string; + className?: string; } function WidgetSessions(props: Props) { - const { className = '' } = props; - const [activeSeries, setActiveSeries] = useState('all'); - const [data, setData] = useState<any>([]); - const isMounted = useIsMounted(); - const [loading, setLoading] = useState(false); - const filteredSessions = getListSessionsBySeries(data, activeSeries); - const { dashboardStore, metricStore } = useStore(); - const filter = useObserver(() => dashboardStore.drillDownFilter); - const widget: any = useObserver(() => metricStore.instance); - const startTime = DateTime.fromMillis(filter.startTimestamp).toFormat('LLL dd, yyyy HH:mm'); - const endTime = DateTime.fromMillis(filter.endTimestamp).toFormat('LLL dd, yyyy HH:mm'); - const [seriesOptions, setSeriesOptions] = useState([{ label: 'All', value: 'all' }]); + const { className = '' } = props; + const [activeSeries, setActiveSeries] = useState('all'); + const [data, setData] = useState<any>([]); + const isMounted = useIsMounted(); + const [loading, setLoading] = useState(false); + const filteredSessions = getListSessionsBySeries(data, activeSeries); + const { dashboardStore, metricStore, sessionStore } = useStore(); + const filter = dashboardStore.drillDownFilter; + const widget = metricStore.instance; + const startTime = DateTime.fromMillis(filter.startTimestamp).toFormat('LLL dd, yyyy HH:mm'); + const endTime = DateTime.fromMillis(filter.endTimestamp).toFormat('LLL dd, yyyy HH:mm'); + const [seriesOptions, setSeriesOptions] = useState([{ label: 'All', value: 'all' }]); + const hasFilters = filter.filters.length > 0 || (filter.startTimestamp !== dashboardStore.drillDownPeriod.start || filter.endTimestamp !== dashboardStore.drillDownPeriod.end); - const writeOption = ({ value }: any) => setActiveSeries(value.value); - useEffect(() => { - if (!data) return; - const seriesOptions = data.map((item: any) => ({ - label: item.seriesName, - value: item.seriesId, - })); - setSeriesOptions([{ label: 'All', value: 'all' }, ...seriesOptions]); - }, [data]); + const writeOption = ({ value }: any) => setActiveSeries(value.value); + useEffect(() => { + if (!data) return; + const seriesOptions = data.map((item: any) => ({ + label: item.seriesName, + value: item.seriesId, + })); + setSeriesOptions([{ label: 'All', value: 'all' }, ...seriesOptions]); + }, [data]); - const fetchSessions = (metricId: any, filter: any) => { - if (!isMounted()) return; - setLoading(true); - widget - .fetchSessions(metricId, filter) - .then((res: any) => { - setData(res); - }) - .finally(() => { - setLoading(false); - }); - }; - const debounceRequest: any = React.useCallback(debounce(fetchSessions, 1000), []); + const fetchSessions = (metricId: any, filter: any) => { + if (!isMounted()) return; + setLoading(true); + delete filter.eventsOrderSupport; + widget + .fetchSessions(metricId, filter) + .then((res: any) => { + setData(res); + }) + .finally(() => { + setLoading(false); + }); + }; + const fetchClickmapSessions = (customFilters: Record<string, any>) => { + sessionStore.getSessions(customFilters).then((data) => { + setData([{ ...data, seriesId: 1, seriesName: 'Clicks' }]); + }); + }; + const debounceRequest: any = React.useCallback(debounce(fetchSessions, 1000), []); + const debounceClickMapSearch = React.useCallback(debounce(fetchClickmapSessions, 1000), []); - const depsString = JSON.stringify(widget.series); - useEffect(() => { - debounceRequest(widget.metricId, { - ...filter, - series: widget.toJsonDrilldown(), - page: metricStore.sessionsPage, - limit: metricStore.sessionsPageSize, - }); - }, [filter.startTimestamp, filter.endTimestamp, filter.filters, depsString, metricStore.sessionsPage]); + const depsString = JSON.stringify(widget.series); - return useObserver(() => ( - <div className={cn(className, "bg-white p-3 pb-0 rounded border")}> - <div className="flex items-center justify-between"> - <div className="flex items-baseline"> - <h2 className="text-2xl">Sessions</h2> - <div className="ml-2 color-gray-medium"> - between <span className="font-medium color-gray-darkest">{startTime}</span> and{' '} - <span className="font-medium color-gray-darkest">{endTime}</span>{' '} - </div> - </div> + const loadData = () => { + if (widget.metricType === CLICKMAP && metricStore.clickMapSearch) { + const clickFilter = { + value: [metricStore.clickMapSearch], + type: 'CLICK', + operator: 'onSelector', + isEvent: true, + // @ts-ignore + filters: [], + }; + const timeRange = { + rangeValue: dashboardStore.drillDownPeriod.rangeValue, + startDate: dashboardStore.drillDownPeriod.start, + endDate: dashboardStore.drillDownPeriod.end, + }; + const customFilter = { + ...filter, + ...timeRange, + filters: [...sessionStore.userFilter.filters, clickFilter], + }; + debounceClickMapSearch(customFilter); + } else { + debounceRequest(widget.metricId, { + ...filter, + series: widget.series.map((s) => s.toJson()), + page: metricStore.sessionsPage, + limit: metricStore.sessionsPageSize, + }); + } + }; + useEffect(() => { + metricStore.updateKey('sessionsPage', 1); + loadData(); + }, [ + filter.startTimestamp, + filter.endTimestamp, + filter.filters, + depsString, + metricStore.clickMapSearch, + activeSeries, + ]); + useEffect(loadData, [metricStore.sessionsPage]); - {widget.metricType !== 'table' && ( - <div className="flex items-center ml-6"> - <span className="mr-2 color-gray-medium">Filter by Series</span> - <Select options={seriesOptions} defaultValue={'all'} onChange={writeOption} plain /> - </div> - )} - </div> + const clearFilters = () => { + metricStore.updateKey('sessionsPage', 1); + dashboardStore.resetDrillDownFilter(); + } - <div className="mt-3"> - <Loader loading={loading}> - <NoContent - title={ - <div className="flex items-center justify-center flex-col"> - <AnimatedSVG name={ICONS.NO_SESSIONS} size={170} /> - <div className="mt-2" /> - <div className="text-center text-gray-600">No relevant sessions found for the selected time period.</div> - </div> - } - show={filteredSessions.sessions.length === 0} - > - {filteredSessions.sessions.map((session: any) => ( - <React.Fragment key={session.sessionId}> - <SessionItem session={session} /> - <div className="border-b" /> - </React.Fragment> - ))} - - <div className="flex items-center justify-between p-5"> - <div> - Showing <span className="font-medium">{(metricStore.sessionsPage - 1) * metricStore.sessionsPageSize + 1}</span> to{' '} - <span className="font-medium">{(metricStore.sessionsPage - 1) * metricStore.sessionsPageSize + filteredSessions.sessions.length}</span> of{' '} - <span className="font-medium">{numberWithCommas(filteredSessions.total)}</span> sessions. - </div> - <Pagination - page={metricStore.sessionsPage} - totalPages={Math.ceil(filteredSessions.total / metricStore.sessionsPageSize)} - onPageChange={(page: any) => metricStore.updateKey('sessionsPage', page)} - limit={metricStore.sessionsPageSize} - debounceRequest={500} - /> - </div> - </NoContent> - </Loader> - </div> + return ( + <div className={cn(className, 'bg-white p-3 pb-0 rounded border')}> + <div className="flex items-center justify-between"> + <div className="flex items-baseline"> + <h2 className="text-xl">{metricStore.clickMapSearch ? 'Clicks' : 'Sessions'}</h2> + <div className="ml-2 color-gray-medium"> + {metricStore.clickMapLabel ? `on "${metricStore.clickMapLabel}" ` : null} + between <span className="font-medium color-gray-darkest">{startTime}</span> and{' '} + <span className="font-medium color-gray-darkest">{endTime}</span>{' '} + </div> </div> - )); + + <div className="flex items-center gap-4"> + {hasFilters && <Button variant="text-primary" onClick={clearFilters}>Clear Filters</Button>} + {widget.metricType !== 'table' && widget.metricType !== CLICKMAP && ( + <div className="flex items-center ml-6"> + <span className="mr-2 color-gray-medium">Filter by Series</span> + <Select options={seriesOptions} defaultValue={'all'} onChange={writeOption} plain /> + </div> + )} + </div> + </div> + + <div className="mt-3"> + <Loader loading={loading}> + <NoContent + title={ + <div className="flex items-center justify-center flex-col"> + <AnimatedSVG name={ICONS.NO_SESSIONS} size={170} /> + <div className="mt-2" /> + <div className="text-center text-gray-600"> + No relevant sessions found for the selected time period. + </div> + </div> + } + show={filteredSessions.sessions.length === 0} + > + {filteredSessions.sessions.map((session: any) => ( + <React.Fragment key={session.sessionId}> + <SessionItem session={session} /> + <div className="border-b" /> + </React.Fragment> + ))} + + <div className="flex items-center justify-between p-5"> + <div> + Showing{' '} + <span className="font-medium"> + {(metricStore.sessionsPage - 1) * metricStore.sessionsPageSize + 1} + </span>{' '} + to{' '} + <span className="font-medium"> + {(metricStore.sessionsPage - 1) * metricStore.sessionsPageSize + + filteredSessions.sessions.length} + </span>{' '} + of <span className="font-medium">{numberWithCommas(filteredSessions.total)}</span>{' '} + sessions. + </div> + <Pagination + page={metricStore.sessionsPage} + totalPages={Math.ceil(filteredSessions.total / metricStore.sessionsPageSize)} + onPageChange={(page: any) => metricStore.updateKey('sessionsPage', page)} + limit={metricStore.sessionsPageSize} + debounceRequest={500} + /> + </div> + </NoContent> + </Loader> + </div> + </div> + ); } const getListSessionsBySeries = (data: any, seriesId: any) => { - const arr: any = { sessions: [], total: 0 }; - data.forEach((element: any) => { - if (seriesId === 'all') { - const sessionIds = arr.sessions.map((i: any) => i.sessionId); - arr.sessions.push(...element.sessions.filter((i: any) => !sessionIds.includes(i.sessionId))); - arr.total = element.total; - } else { - if (element.seriesId === seriesId) { - arr.sessions.push(...element.sessions); - arr.total = element.total; - } - } - }); - return arr; + const arr = data.reduce( + (arr: any, element: any) => { + if (seriesId === 'all') { + const sessionIds = arr.sessions.map((i: any) => i.sessionId); + const sessions = element.sessions.filter((i: any) => !sessionIds.includes(i.sessionId)); + arr.sessions.push(...sessions); + } else if (element.seriesId === seriesId) { + const sessionIds = arr.sessions.map((i: any) => i.sessionId); + const sessions = element.sessions.filter((i: any) => !sessionIds.includes(i.sessionId)); + const duplicates = element.sessions.length - sessions.length; + arr.sessions.push(...sessions); + arr.total = element.total - duplicates; + } + return arr; + }, + { sessions: [] } + ); + arr.total = + seriesId === 'all' + ? Math.max(...data.map((i: any) => i.total)) + : data.find((i: any) => i.seriesId === seriesId).total; + return arr; }; export default observer(WidgetSessions); diff --git a/frontend/app/components/Dashboard/components/WidgetSubDetailsView/WidgetSubDetailsView.tsx b/frontend/app/components/Dashboard/components/WidgetSubDetailsView/WidgetSubDetailsView.tsx index 261e77efd..2cbfe12e7 100644 --- a/frontend/app/components/Dashboard/components/WidgetSubDetailsView/WidgetSubDetailsView.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSubDetailsView/WidgetSubDetailsView.tsx @@ -30,7 +30,7 @@ function WidgetSubDetailsView(props: Props) { <div> <Breadcrumb items={[ - { label: dashboardId ? 'Dashboard' : 'Metrics', to: dashboardId ? withSiteId('/dashboard/' + dashboardId, siteId) : withSiteId('/metrics', siteId) }, + { label: dashboardId ? 'Dashboard' : 'Cards', to: dashboardId ? withSiteId('/dashboard/' + dashboardId, siteId) : withSiteId('/metrics', siteId) }, { label: widget.name, to: withSiteId(`/metrics/${widget.metricId}`, siteId) }, { label: issueInstance ? issueInstance.title : 'Sub Details' } ]} diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx index 3b8d8f246..debb909cc 100644 --- a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx +++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx @@ -13,6 +13,13 @@ import Breadcrumb from 'Shared/Breadcrumb'; import { FilterKey } from 'Types/filter/filterType'; import { Prompt } from 'react-router'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +import { + TIMESERIES, + TABLE, + CLICKMAP, + FUNNEL, + INSIGHTS, + } from 'App/constants/card'; interface Props { history: any; @@ -73,7 +80,7 @@ function WidgetView(props: Props) { <Breadcrumb items={[ { - label: dashboardName ? dashboardName : 'Metrics', + label: dashboardName ? dashboardName : 'Cards', to: dashboardId ? withSiteId('/dashboard/' + dashboardId, siteId) : withSiteId('/metrics', siteId), }, { label: widget.name }, @@ -100,7 +107,7 @@ function WidgetView(props: Props) { </h1> <div className="text-gray-600 w-full cursor-pointer" onClick={() => setExpanded(!expanded)}> <div className="flex items-center select-none w-fit ml-auto"> - <span className="mr-2 color-teal">{expanded ? 'Close' : 'Edit'}</span> + <span className="mr-2 color-teal">{expanded ? 'Collapse' : 'Edit'}</span> <Icon name={expanded ? 'chevron-up' : 'chevron-down'} size="16" color="teal" /> </div> </div> @@ -109,11 +116,12 @@ function WidgetView(props: Props) { {expanded && <WidgetForm onDelete={onBackHandler} {...props} />} </div> - <WidgetPreview className="mt-8" name={widget.name} /> + <WidgetPreview className="mt-8" name={widget.name} isEditing={expanded} /> + {widget.metricOf !== FilterKey.SESSIONS && widget.metricOf !== FilterKey.ERRORS && ( <> - {(widget.metricType === 'table' || widget.metricType === 'timeseries') && <WidgetSessions className="mt-8" />} - {widget.metricType === 'funnel' && <FunnelIssues />} + {(widget.metricType === TABLE || widget.metricType === TIMESERIES || widget.metricType === CLICKMAP || widget.metricType === INSIGHTS) && <WidgetSessions className="mt-8" />} + {widget.metricType === FUNNEL && <FunnelIssues />} </> )} </NoContent> diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/AlertButton.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/AlertButton.tsx index 78d858b3e..548bc9570 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/AlertButton.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/AlertButton.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import { connect } from 'react-redux'; import WidgetIcon from './WidgetIcon'; -import { init as initAlert } from 'Duck/alerts'; import { useStore } from 'App/mstore'; interface Props { @@ -9,20 +7,21 @@ interface Props { initAlert: Function; } function AlertButton(props: Props) { - const { seriesId, initAlert } = props; - const { dashboardStore } = useStore(); + const { seriesId } = props; + const { dashboardStore, alertsStore } = useStore(); const onClick = () => { - initAlert({ query: { left: seriesId }}) - dashboardStore.updateKey('showAlertModal', true); + dashboardStore.toggleAlertModal(true); + alertsStore.init({ query: { left: seriesId }}) } return ( + <div onClick={onClick}> <WidgetIcon className="cursor-pointer" icon="bell-plus" tooltip="Set Alert" - onClick={onClick} /> + </div> ); } -export default connect(null, { initAlert })(AlertButton); \ No newline at end of file +export default AlertButton; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetIcon.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetIcon.tsx index bc804b2b8..b9714b33b 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetIcon.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetIcon.tsx @@ -3,15 +3,14 @@ import { Icon, Tooltip } from 'UI'; interface Props { className: string; - onClick: () => void; icon: string; tooltip: string; } function WidgetIcon(props: Props) { - const { className, onClick, icon, tooltip } = props; + const { className, icon, tooltip } = props; return ( <Tooltip title={tooltip}> - <div className={className} onClick={onClick}> + <div className={className}> {/* @ts-ignore */} <Icon name={icon} size="14" /> </div> diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx index 4aac81dd6..cfbf993dd 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx @@ -1,6 +1,6 @@ import React, { useRef } from 'react'; import cn from 'classnames'; -import { ItemMenu, Tooltip } from 'UI'; +import { ItemMenu, TextEllipsis } from 'UI'; import { useDrag, useDrop } from 'react-dnd'; import WidgetChart from '../WidgetChart'; import { observer } from 'mobx-react-lite'; @@ -28,6 +28,7 @@ interface Props { isWidget?: boolean; hideName?: boolean; grid?: string; + isGridView?: boolean; } function WidgetWrapper(props: Props & RouteComponentProps) { const { dashboardStore } = useStore(); @@ -40,6 +41,7 @@ function WidgetWrapper(props: Props & RouteComponentProps) { isTemplate = false, siteId, grid = '', + isGridView = false, } = props; const widget: any = props.widget; const isTimeSeries = widget.metricType === 'timeseries'; @@ -71,7 +73,7 @@ function WidgetWrapper(props: Props & RouteComponentProps) { }); const onDelete = async () => { - dashboardStore.deleteDashboardWidget(dashboard?.dashboardId, widget.widgetId); + dashboardStore.deleteDashboardWidget(dashboard?.dashboardId!, widget.widgetId); }; const onChartClick = () => { @@ -119,53 +121,59 @@ function WidgetWrapper(props: Props & RouteComponentProps) { {'Cannot drill down system provided metrics'} </div> )} - <Tooltip disabled={!isTemplate} title="Click to select"> - {addOverlay && <TemplateOverlay onClick={onChartClick} isTemplate={isTemplate} />} - <div - className={cn('p-3 pb-4 flex items-center justify-between', { - 'cursor-move': !isTemplate && isWidget, - })} - > - {!props.hideName ? ( - <div className="capitalize-first w-full font-medium">{widget.name}</div> - ) : null} - {isWidget && ( - <div className="flex items-center" id="no-print"> - {!isPredefined && isTimeSeries && ( - <> - <AlertButton seriesId={widget.series[0] && widget.series[0].seriesId} /> - <div className="mx-2" /> - </> - )} - {!isTemplate && ( - <ItemMenu - items={[ - { - text: - widget.metricType === 'predefined' - ? 'Cannot edit system generated metrics' - : 'Edit', - onClick: onChartClick, - disabled: widget.metricType === 'predefined', - }, - { - text: 'Hide', - onClick: onDelete, - }, - ]} - /> - )} - </div> - )} - </div> - - <LazyLoad offset={!isTemplate ? 100 : 600}> - <div className="px-4" onClick={onChartClick}> - <WidgetChart metric={widget} isTemplate={isTemplate} isWidget={isWidget} /> + {addOverlay && <TemplateOverlay onClick={onChartClick} isTemplate={isTemplate} />} + <div + className={cn('p-3 pb-4 flex items-center justify-between', { + 'cursor-move': !isTemplate && isWidget, + })} + > + {!props.hideName ? ( + <div className="capitalize-first w-full font-medium"> + <TextEllipsis text={widget.name} /> </div> - </LazyLoad> - </Tooltip> + ) : null} + {isWidget && ( + <div className="flex items-center" id="no-print"> + {!isPredefined && isTimeSeries && !isGridView && ( + <> + <AlertButton seriesId={widget.series[0] && widget.series[0].seriesId} /> + <div className="mx-2" /> + </> + )} + + {!isTemplate && !isGridView && ( + <ItemMenu + items={[ + { + text: + widget.metricType === 'predefined' + ? 'Cannot edit system generated metrics' + : 'Edit', + onClick: onChartClick, + disabled: widget.metricType === 'predefined', + }, + { + text: 'Hide', + onClick: onDelete, + }, + ]} + /> + )} + </div> + )} + </div> + + <LazyLoad offset={!isTemplate ? 100 : 600}> + <div className="px-4" onClick={onChartClick}> + <WidgetChart + isPreview={isPreview} + metric={widget} + isTemplate={isTemplate} + isWidget={isWidget} + /> + </div> + </LazyLoad> </div> ); } diff --git a/frontend/app/components/Errors/Error/MainSection.js b/frontend/app/components/Errors/Error/MainSection.js index d65c884cb..a74755408 100644 --- a/frontend/app/components/Errors/Error/MainSection.js +++ b/frontend/app/components/Errors/Error/MainSection.js @@ -2,17 +2,15 @@ import React from 'react'; import { connect } from 'react-redux'; import cn from 'classnames'; import withSiteIdRouter from 'HOCs/withSiteIdRouter'; -import { ErrorDetails, IconButton, Icon, Loader, Button } from 'UI'; +import { ErrorDetails, Icon, Loader, Button } from 'UI'; import { sessions as sessionsRoute } from 'App/routes'; -import { TYPES as EV_FILER_TYPES } from 'Types/filter/event'; -import { UNRESOLVED, RESOLVED, IGNORED } from 'Types/errorInfo'; +import { RESOLVED } from 'Types/errorInfo'; import { addFilterByKeyAndValue } from 'Duck/search'; import { resolve, unresolve, ignore, toggleFavorite } from 'Duck/errors'; import { resentOrDate } from 'App/date'; import Divider from 'Components/Errors/ui/Divider'; import ErrorName from 'Components/Errors/ui/ErrorName'; import Label from 'Components/Errors/ui/Label'; -import SharePopup from 'Shared/SharePopup'; import { FilterKey } from 'Types/filter/filterType'; import SessionBar from './SessionBar'; diff --git a/frontend/app/components/Errors/List/List.js b/frontend/app/components/Errors/List/List.js index 9f379319a..d41ebd50c 100644 --- a/frontend/app/components/Errors/List/List.js +++ b/frontend/app/components/Errors/List/List.js @@ -1,11 +1,10 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Set, List as ImmutableList } from "immutable"; -import { NoContent, Loader, Checkbox, LoadMoreButton, IconButton, Input, DropdownPlain, Pagination } from 'UI'; +import { Set } from "immutable"; +import { NoContent, Loader, Checkbox, IconButton, Input, Pagination } from 'UI'; import { merge, resolve, unresolve, ignore, updateCurrentPage, editOptions } from "Duck/errors"; import { applyFilter } from 'Duck/filters'; -import { IGNORED, RESOLVED, UNRESOLVED } from 'Types/errorInfo'; -import SortDropdown from 'Components/BugFinder/Filters/SortDropdown'; +import { IGNORED, UNRESOLVED } from 'Types/errorInfo'; import Divider from 'Components/Errors/ui/Divider'; import ListItem from './ListItem/ListItem'; import { debounce } from 'App/utils'; @@ -25,7 +24,7 @@ const sortOptions = Object.entries(sortOptionsMap) @connect(state => ({ loading: state.getIn([ "errors", "loading" ]), - resolveToggleLoading: state.getIn(["errors", "resolve", "loading"]) || + resolveToggleLoading: state.getIn(["errors", "resolve", "loading"]) || state.getIn(["errors", "unresolve", "loading"]), ignoreLoading: state.getIn([ "errors", "ignore", "loading" ]), mergeLoading: state.getIn([ "errors", "merge", "loading" ]), @@ -54,19 +53,19 @@ export default class List extends React.PureComponent { } this.debounceFetch = debounce(this.props.editOptions, 1000); } - + componentDidMount() { this.props.applyFilter({ }); } check = ({ errorId }) => { const { checkedIds } = this.state; - const newCheckedIds = checkedIds.contains(errorId) - ? checkedIds.remove(errorId) + const newCheckedIds = checkedIds.contains(errorId) + ? checkedIds.remove(errorId) : checkedIds.add(errorId); this.setState({ checkedAll: newCheckedIds.size === this.props.list.size, - checkedIds: newCheckedIds + checkedIds: newCheckedIds }); } @@ -184,7 +183,7 @@ export default class List extends React.PureComponent { onClick={ this.unresolve } disabled={ someLoading || currentCheckedIds.size === 0} /> - } + } { status !== IGNORED && <IconButton outline @@ -196,10 +195,10 @@ export default class List extends React.PureComponent { onClick={ this.ignore } disabled={ someLoading || currentCheckedIds.size === 0} /> - } + } </div> <div className="flex items-center ml-6"> - <span className="mr-2 color-gray-medium">Sort By</span> + <span className="mr-2 color-gray-medium">Sort By</span> <Select defaultValue={ `${sort}-${order}` } name="sort" @@ -212,7 +211,6 @@ export default class List extends React.PureComponent { wrapperClassName="ml-3" placeholder="Filter by name or message" icon="search" - iconPosition="left" name="filter" onChange={ this.onQueryChange } value={query} @@ -258,4 +256,4 @@ export default class List extends React.PureComponent { </div> ); } -} \ No newline at end of file +} diff --git a/frontend/app/components/Errors/List/ListItem/ListItem.js b/frontend/app/components/Errors/List/ListItem/ListItem.js index 1dde46e59..51e416b81 100644 --- a/frontend/app/components/Errors/List/ListItem/ListItem.js +++ b/frontend/app/components/Errors/List/ListItem/ListItem.js @@ -5,8 +5,7 @@ import moment from 'moment'; import { diffFromNowString } from 'App/date'; import { error as errorRoute } from 'App/routes'; import { IGNORED, RESOLVED } from 'Types/errorInfo'; -import { diffFromNowShortString } from 'App/date'; -import { Checkbox, Link } from 'UI'; +import { Checkbox, Link } from 'UI'; import ErrorName from 'Components/Errors/ui/ErrorName'; import Label from 'Components/Errors/ui/Label'; import stl from './listItem.module.css'; diff --git a/frontend/app/components/ForgotPassword/ForgotPassword.js b/frontend/app/components/ForgotPassword/ForgotPassword.js index e931677bf..1cd40c679 100644 --- a/frontend/app/components/ForgotPassword/ForgotPassword.js +++ b/frontend/app/components/ForgotPassword/ForgotPassword.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import ReCAPTCHA from 'react-google-recaptcha'; import withPageTitle from 'HOCs/withPageTitle'; import { Form, Input, Loader, Button, Link, Icon, Message } from 'UI'; -import { requestResetPassword, resetPassword } from 'Duck/user'; +import { requestResetPassword, resetPassword, resetErrors } from 'Duck/user'; import { login as loginRoute } from 'App/routes'; import { withRouter } from 'react-router-dom'; import { validateEmail } from 'App/validate'; @@ -26,7 +26,7 @@ const checkDontMatch = (newPassword, newPasswordRepeat) => loading: state.getIn([ 'user', 'requestResetPassowrd', 'loading' ]) || state.getIn([ 'user', 'resetPassword', 'loading' ]), params: new URLSearchParams(props.location.search) }), - { requestResetPassword, resetPassword }, + { requestResetPassword, resetPassword, resetErrors }, ) @withPageTitle("Password Reset - OpenReplay") @withRouter @@ -42,7 +42,7 @@ export default class ForgotPassword extends React.PureComponent { }; handleSubmit = (token) => { - const { email, requested, code, password } = this.state; + const { email, password } = this.state; const { params } = this.props; const pass = params.get('pass') @@ -89,6 +89,10 @@ export default class ForgotPassword extends React.PureComponent { } } + componentWillUnmount() { + this.props.resetErrors() + } + render() { const { CAPTCHA_ENABLED } = this.state; const { errors, loading, params } = this.props; @@ -148,7 +152,7 @@ export default class ForgotPassword extends React.PureComponent { } { - requested && ( + requested && !errors && ( <div>Reset password link has been sent to your email.</div> ) } diff --git a/frontend/app/components/Funnels/FunnelGraph/FunnelGraph.js b/frontend/app/components/Funnels/FunnelGraph/FunnelGraph.js index 8f3cbb5ea..221a59e8f 100644 --- a/frontend/app/components/Funnels/FunnelGraph/FunnelGraph.js +++ b/frontend/app/components/Funnels/FunnelGraph/FunnelGraph.js @@ -9,9 +9,8 @@ import { YAxis, CartesianGrid, Tooltip, - Legend, LabelList, - Label, + } from 'recharts'; import { connect } from 'react-redux'; import { setActiveStages } from 'Duck/funnels'; diff --git a/frontend/app/components/Funnels/FunnelIssues/FunnelIssues.js b/frontend/app/components/Funnels/FunnelIssues/FunnelIssues.js index 4a96c76c0..0175008a8 100644 --- a/frontend/app/components/Funnels/FunnelIssues/FunnelIssues.js +++ b/frontend/app/components/Funnels/FunnelIssues/FunnelIssues.js @@ -1,7 +1,7 @@ import React, { useState } from 'react' import { connect } from 'react-redux' import { fetchIssues, fetchIssuesFiltered } from 'Duck/funnels' -import { LoadMoreButton, NoContent, Loader } from 'UI' +import { LoadMoreButton, NoContent } from 'UI' import FunnelIssuesHeader from '../FunnelIssuesHeader' import IssueItem from '../IssueItem'; import { funnelIssue as funnelIssueRoute, withSiteId } from 'App/routes' diff --git a/frontend/app/components/Funnels/FunnelSessionList/FunnelSessionList.js b/frontend/app/components/Funnels/FunnelSessionList/FunnelSessionList.js index c16a98407..f51538696 100644 --- a/frontend/app/components/Funnels/FunnelSessionList/FunnelSessionList.js +++ b/frontend/app/components/Funnels/FunnelSessionList/FunnelSessionList.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux' import SessionItem from 'Shared/SessionItem' import { fetchSessions, fetchSessionsFiltered } from 'Duck/funnels' import { setFunnelPage } from 'Duck/sessions' -import { LoadMoreButton, NoContent, Loader } from 'UI' +import { LoadMoreButton, NoContent } from 'UI' import FunnelSessionsHeader from '../FunnelSessionsHeader' import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx index b3e1e2f89..4e738935d 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx @@ -1,6 +1,6 @@ import React from 'react'; import FunnelStepText from './FunnelStepText'; -import { Icon, Tooltip } from 'UI'; +import { Icon } from 'UI'; interface Props { filter: any; diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx index 7090bb36e..146b90159 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx @@ -30,7 +30,16 @@ function FunnelWidget(props: Props) { }, []); return useObserver(() => ( - <NoContent show={!stages || stages.length === 0} title="No recordings found"> + <NoContent + style={{ minHeight: 220 }} + title={ + <div className="flex items-center"> + <Icon name="info-circle" className="mr-2" size="18" /> + No data for selected period. + </div> + } + show={!stages || stages.length === 0} + > <div className="w-full"> { !isWidget && ( stages.map((filter: any, index: any) => ( diff --git a/frontend/app/components/Funnels/funnels.stories.js b/frontend/app/components/Funnels/funnels.stories.js index 67849e0fc..58197bbcc 100644 --- a/frontend/app/components/Funnels/funnels.stories.js +++ b/frontend/app/components/Funnels/funnels.stories.js @@ -1,5 +1,4 @@ import { storiesOf } from '@storybook/react'; -import { List } from 'immutable'; import Funnel from 'Types/funnel' import FunnelIssue from 'Types/funnelIssue' import FunnelList from './FunnelList'; diff --git a/frontend/app/components/Header/DefaultMenuView/DefaultMenuView.tsx b/frontend/app/components/Header/DefaultMenuView/DefaultMenuView.tsx index 9b5c74879..8e6a68e58 100644 --- a/frontend/app/components/Header/DefaultMenuView/DefaultMenuView.tsx +++ b/frontend/app/components/Header/DefaultMenuView/DefaultMenuView.tsx @@ -1,13 +1,12 @@ import React from 'react'; -import { NavLink, withRouter } from 'react-router-dom'; +import { NavLink } from 'react-router-dom'; import { sessions, metrics, assist, - client, dashboard, withSiteId, - CLIENT_DEFAULT_TAB, + } from 'App/routes'; import SiteDropdown from '../SiteDropdown'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; diff --git a/frontend/app/components/Header/Header.js b/frontend/app/components/Header/Header.js index b00ebb93b..7ef0028c9 100644 --- a/frontend/app/components/Header/Header.js +++ b/frontend/app/components/Header/Header.js @@ -12,7 +12,6 @@ import { init as initSite } from 'Duck/site'; import { getInitials } from 'App/utils'; import ErrorGenPanel from 'App/dev/components'; -import Alerts from '../Alerts/Alerts'; import { fetchListActive as fetchMetadata } from 'Duck/customField'; import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; @@ -91,8 +90,6 @@ const Header = (props) => { {<ErrorGenPanel />} </div> - - {showAlerts && <Alerts />} </div> ); }; @@ -103,7 +100,6 @@ export default withRouter( account: state.getIn(['user', 'account']), siteId: state.getIn(['site', 'siteId']), sites: state.getIn(['site', 'list']), - showAlerts: state.getIn(['dashboard', 'showAlerts']), boardingCompletion: state.getIn(['dashboard', 'boardingCompletion']), }), { onLogoutClick: logout, initSite, fetchMetadata } diff --git a/frontend/app/components/Header/NewProjectButton/NewProjectButton.tsx b/frontend/app/components/Header/NewProjectButton/NewProjectButton.tsx index a0dd30244..2ac9e95ae 100644 --- a/frontend/app/components/Header/NewProjectButton/NewProjectButton.tsx +++ b/frontend/app/components/Header/NewProjectButton/NewProjectButton.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { Icon } from 'UI'; -import cn from 'classnames'; import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; import { useModal } from 'App/components/Modal'; diff --git a/frontend/app/components/Header/OnboardingExplore/FeatureItem.js b/frontend/app/components/Header/OnboardingExplore/FeatureItem.js index d30ce85a1..c5841b19b 100644 --- a/frontend/app/components/Header/OnboardingExplore/FeatureItem.js +++ b/frontend/app/components/Header/OnboardingExplore/FeatureItem.js @@ -1,5 +1,5 @@ import React from 'react'; -import { Checkbox, Icon } from 'UI'; +import { Icon } from 'UI'; import cn from 'classnames'; import stl from './featureItem.module.css'; diff --git a/frontend/app/components/Header/PreferencesView/PreferencesView.tsx b/frontend/app/components/Header/PreferencesView/PreferencesView.tsx index a5282aea8..f9da1b933 100644 --- a/frontend/app/components/Header/PreferencesView/PreferencesView.tsx +++ b/frontend/app/components/Header/PreferencesView/PreferencesView.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Icon } from 'UI'; import { withRouter } from 'react-router-dom'; -import ProjectCodeSnippet from 'App/components/Onboarding/components/OnboardingTabs/ProjectCodeSnippet'; interface Props { history: any; diff --git a/frontend/app/components/Header/SettingsMenu/SettingsMenu.tsx b/frontend/app/components/Header/SettingsMenu/SettingsMenu.tsx index 5d443a842..2faf5e128 100644 --- a/frontend/app/components/Header/SettingsMenu/SettingsMenu.tsx +++ b/frontend/app/components/Header/SettingsMenu/SettingsMenu.tsx @@ -12,6 +12,7 @@ interface Props { function SettingsMenu(props: RouteComponentProps<Props>) { const { history, account, className }: any = props; const isAdmin = account.admin || account.superAdmin; + const isEnterprise = account.edition === 'ee'; const navigateTo = (path: any) => { switch (path) { case 'projects': @@ -26,12 +27,16 @@ function SettingsMenu(props: RouteComponentProps<Props>) { return history.push(clientRoute(CLIENT_TABS.INTEGRATIONS)); case 'notifications': return history.push(clientRoute(CLIENT_TABS.NOTIFICATIONS)); + case 'roles': + return history.push(clientRoute(CLIENT_TABS.MANAGE_ROLES)); + case 'audit': + return history.push(clientRoute(CLIENT_TABS.AUDIT)); } }; return ( <div - style={{ width: '150px', marginTop: '35px' }} - className={cn(className, 'absolute right-0 top-0 bg-white border p-2')} + style={{ width: '160px', marginTop: '35px' }} + className={cn(className, 'absolute -right-4 top-0 bg-white border p-2 text-left')} > {isAdmin && ( <> @@ -42,6 +47,12 @@ function SettingsMenu(props: RouteComponentProps<Props>) { <MenuItem onClick={() => navigateTo('metadata')} label="Metadata" icon="tags" /> <MenuItem onClick={() => navigateTo('webhooks')} label="Webhooks" icon="link-45deg" /> <MenuItem onClick={() => navigateTo('integrations')} label="Integrations" icon="puzzle" /> + {isEnterprise && isAdmin && ( + <> + <MenuItem onClick={() => navigateTo('roles')} label="Roles & Access" icon="diagram-3" /> + <MenuItem onClick={() => navigateTo('audit')} label="Audit" icon="list-ul" /> + </> + )} <MenuItem onClick={() => navigateTo('notifications')} label="Notifications" diff --git a/frontend/app/components/Login/Login.js b/frontend/app/components/Login/Login.js index c04e83f12..da2206bdf 100644 --- a/frontend/app/components/Login/Login.js +++ b/frontend/app/components/Login/Login.js @@ -1,31 +1,34 @@ import React from 'react'; import { connect } from 'react-redux'; import withPageTitle from 'HOCs/withPageTitle'; -import { Icon, Loader, Button, Link, Input, Form } from 'UI'; +import { Icon, Loader, Button, Link, Input, Form, Popover, Tooltip } from 'UI'; import { login } from 'Duck/user'; import { forgotPassword, signup } from 'App/routes'; import ReCAPTCHA from 'react-google-recaptcha'; import { withRouter } from 'react-router-dom'; import stl from './login.module.css'; import cn from 'classnames'; -import { setJwt } from 'Duck/jwt'; +import { setJwt } from 'Duck/user'; +import LoginBg from '../../svg/login-illustration.svg'; +import { ENTERPRISE_REQUEIRED } from 'App/constants'; const FORGOT_PASSWORD = forgotPassword(); const SIGNUP_ROUTE = signup(); const recaptchaRef = React.createRef(); +export default @connect( (state, props) => ({ - errors: state.getIn([ 'user', 'loginRequest', 'errors' ]), - loading: state.getIn([ 'user', 'loginRequest', 'loading' ]), + errors: state.getIn(['user', 'loginRequest', 'errors']), + loading: state.getIn(['user', 'loginRequest', 'loading']), authDetails: state.getIn(['user', 'authDetails']), - params: new URLSearchParams(props.location.search) + params: new URLSearchParams(props.location.search), }), - { login, setJwt }, + { login, setJwt } ) @withPageTitle('Login - OpenReplay') @withRouter -export default class Login extends React.Component { +class Login extends React.Component { state = { email: '', password: '', @@ -34,7 +37,7 @@ export default class Login extends React.Component { componentDidMount() { const { params } = this.props; - const jwt = params.get('jwt') + const jwt = params.get('jwt'); if (jwt) { this.props.setJwt(jwt); window.location.href = '/'; @@ -44,110 +47,140 @@ export default class Login extends React.Component { handleSubmit = (token) => { const { email, password } = this.state; this.props.login({ email: email.trim(), password, 'g-recaptcha-response': token }).then(() => { - const { errors } = this.props; - }) - } + const { errors } = this.props; + }); + }; - onSubmit = (e) => { + onSubmit = (e) => { e.preventDefault(); const { CAPTCHA_ENABLED } = this.state; if (CAPTCHA_ENABLED && recaptchaRef.current) { - recaptchaRef.current.execute(); + recaptchaRef.current.execute(); } else if (!CAPTCHA_ENABLED) { this.handleSubmit(); } - } + }; - write = ({ target: { value, name } }) => this.setState({ [ name ]: value }) - - + write = ({ target: { value, name } }) => this.setState({ [name]: value }); render() { const { errors, loading, authDetails } = this.props; const { CAPTCHA_ENABLED } = this.state; return ( - <div className="flex flex-col md:flex-row" style={{ height: '100vh'}}> - <div className={cn("md:w-6/12", stl.left)}> + <div className="flex flex-col md:flex-row" style={{ height: '100vh' }}> + <div className={cn('md:w-6/12 relative overflow-hidden', stl.left)}> <div className="px-6 pt-10"> <img src="/assets/logo-white.svg" /> </div> - <div className="color-white text-lg flex items-center"> - <div className="flex items-center justify-center w-full" style={{ height: 'calc(100vh - 130px)'}}> - <div className="text-4xl">Welcome Back!</div> - </div> - </div> + <img style={{ width: '800px', position: 'absolute', bottom: -100, left: 0 }} src={LoginBg} /> </div> <div className="md:w-6/12 flex items-center justify-center py-10"> <div className=""> - <Form onSubmit={ this.onSubmit } className="flex items-center justify-center flex-col"> + <Form onSubmit={this.onSubmit} className="flex items-center justify-center flex-col"> <div className="mb-8"> <h2 className="text-center text-3xl mb-6">Login to OpenReplay</h2> - { !authDetails.tenants && <div className="text-center text-xl">Don't have an account? <span className="link"><Link to={ SIGNUP_ROUTE }>Sign up</Link></span></div> } + {!authDetails.tenants && ( + <div className="text-center text-xl"> + Don't have an account?{' '} + <span className="link"> + <Link to={SIGNUP_ROUTE}>Sign up</Link> + </span> + </div> + )} </div> - <Loader loading={ loading }> - { CAPTCHA_ENABLED && ( + <Loader loading={loading}> + {CAPTCHA_ENABLED && ( <ReCAPTCHA - ref={ recaptchaRef } + ref={recaptchaRef} size="invisible" - sitekey={ window.env.CAPTCHA_SITE_KEY } - onChange={ token => this.handleSubmit(token) } + sitekey={window.env.CAPTCHA_SITE_KEY} + onChange={(token) => this.handleSubmit(token)} /> - )} - <div style={{ width: '350px'}}> + )} + <div style={{ width: '350px' }}> <div className="mb-6"> <label>Email</label> - <Input - autoFocus={true} - autoComplete="username" - type="text" - placeholder="Email" - name="email" - onChange={ this.write } - required="true" - icon="user-alt" - /> + <Input + data-test-id={"login"} + autoFocus={true} + autoComplete="username" + type="text" + placeholder="Email" + name="email" + onChange={this.write} + required + icon="user-alt" + /> </div> <div className="mb-6"> <label className="mb-2">Password</label> - <Input - autoComplete="current-password" - type="password" - placeholder="Password" - name="password" - onChange={ this.write } - required="true" - icon="lock-alt" - /> + <Input + data-test-id={"password"} + autoComplete="current-password" + type="password" + placeholder="Password" + name="password" + onChange={this.write} + required + icon="lock-alt" + /> </div> </div> </Loader> - { errors && - <div className={ stl.errors }> - { errors.map(error => ( + {errors && errors.length ? ( + <div className={stl.errors}> + {errors.map((error) => ( <div className={stl.errorItem}> - <Icon name="info" color="red" size="20"/> - <span className="color-red ml-2">{ error }<br /></span> + <Icon name="info" color="red" size="20" /> + <span className="color-red ml-2"> + {error} + <br /> + </span> </div> - )) } + ))} </div> - } + ) : null} {/* <div className={ stl.formFooter }> */} - <Button className="mt-2" type="submit" variant="primary" >{ 'Login' }</Button> + <Button data-test-id={"log-button"} className="mt-2" type="submit" variant="primary"> + {'Login'} + </Button> - <div className={ cn(stl.links, 'text-lg') }> - <Link to={ FORGOT_PASSWORD }>{'Forgot your password?'}</Link> - </div> + <div className={cn(stl.links, 'text-lg')}> + <Link to={FORGOT_PASSWORD}>{'Forgot your password?'}</Link> + </div> {/* </div> */} </Form> - { authDetails.sso && ( - <div className={cn(stl.sso, "py-2 flex flex-col items-center")}> - <div className="mb-4">or</div> + + <div className={cn(stl.sso, 'py-2 flex flex-col items-center')}> + <div className="mb-4">or</div> + + {authDetails.sso ? ( <a href="/api/sso/saml2" rel="noopener noreferrer"> - <Button variant="outline" type="submit" >{ `Login with SSO (${authDetails.ssoProvider})` }</Button> + <Button variant="outline" type="submit"> + {`Login with SSO ${ + authDetails.ssoProvider ? `(${authDetails.ssoProvider})` : '' + }`} + </Button> </a> - </div> - )} + ) : ( + <Tooltip + delay={0} + title={<div>{authDetails.edition === 'ee' ? "SSO has not been configured. Please reach out to your admin." : ENTERPRISE_REQUEIRED}</div>} + placement="top" + > + <Button + variant="outline" + type="submit" + className="pointer-events-none opacity-30" + > + {`Login with SSO ${ + authDetails.ssoProvider ? `(${authDetails.ssoProvider})` : '' + }`} + </Button> + </Tooltip> + )} + </div> </div> </div> </div> diff --git a/frontend/app/components/Modal/Modal.tsx b/frontend/app/components/Modal/Modal.tsx index cc1e79e71..d2e8a838d 100644 --- a/frontend/app/components/Modal/Modal.tsx +++ b/frontend/app/components/Modal/Modal.tsx @@ -1,9 +1,18 @@ import React, { useEffect } from 'react'; import ReactDOM from 'react-dom'; import ModalOverlay from './ModalOverlay'; +import cn from 'classnames'; import { useHistory } from 'react-router'; -export default function Modal({ component, props, hideModal }: any) { +const DEFAULT_WIDTH = 350; +interface Props { + component: any; + className?: string; + props: any; + hideModal?: boolean; + width?: number; +} +function Modal({ component, className = 'bg-white', props, hideModal }: Props) { const history = useHistory(); useEffect(() => { @@ -13,10 +22,16 @@ export default function Modal({ component, props, hideModal }: any) { } }); }); + return component ? ( ReactDOM.createPortal( <ModalOverlay hideModal={hideModal} left={!props.right} right={props.right}> - {component} + <div + className={className} + style={{ width: `${props.width ? props.width : DEFAULT_WIDTH}px` }} + > + {component} + </div> </ModalOverlay>, document.querySelector('#modal-root') ) @@ -24,3 +39,36 @@ export default function Modal({ component, props, hideModal }: any) { <></> ); } + +Modal.Header = ({ title, children }: { title?: string, children?: any }) => { + return !!children ? ( + <div> + {children} + </div> + ): ( + <div className="text-lg flex items-center p-4 font-medium"> + <div>{title}</div> + </div> + ); +}; + +Modal.Content = ({ children, className = 'p-4' }: { children: any; className?: string }) => { + return ( + <div + className={cn('overflow-y-auto relative', className)} + style={{ height: 'calc(100vh - 52px)' }} + > + {children} + </div> + ); +}; + +Modal.Footer = ({ children, className = '' }: any) => { + return ( + <div className={cn('absolute bottom-0 w-full left-0 right-0', className)} style={{}}> + {children} + </div> + ); +}; + +export default Modal; diff --git a/frontend/app/components/Modal/index.tsx b/frontend/app/components/Modal/index.tsx index 920cb2d14..3df7a98cb 100644 --- a/frontend/app/components/Modal/index.tsx +++ b/frontend/app/components/Modal/index.tsx @@ -3,59 +3,59 @@ import React, { Component, createContext } from 'react'; import Modal from './Modal'; const ModalContext = createContext({ - component: null, - props: { - right: true, - onClose: () => {}, - }, - showModal: (component: any, props: any) => {}, - hideModal: () => {}, + component: null, + props: { + right: true, + onClose: () => {}, + }, + showModal: (component: any, props: any) => {}, + hideModal: () => {}, }); export class ModalProvider extends Component { - handleKeyDown = (e: any) => { - if (e.keyCode === 27) { - this.hideModal(); - } - }; - - showModal = (component, props = { right: true }) => { - this.setState({ - component, - props, - }); - document.addEventListener('keydown', this.handleKeyDown); - document.querySelector('body').style.overflow = 'hidden'; - }; - - hideModal = () => { - document.removeEventListener('keydown', this.handleKeyDown); - document.querySelector('body').style.overflow = 'visible'; - const { props } = this.state; - if (props.onClose) { - props.onClose(); - } - this.setState({ - component: null, - props: {}, - }); - }; - - state = { - component: null, - props: {}, - showModal: this.showModal, - hideModal: this.hideModal, - }; - - render() { - return ( - <ModalContext.Provider value={this.state}> - <Modal {...this.state} /> - {this.props.children} - </ModalContext.Provider> - ); + handleKeyDown = (e: any) => { + if (e.keyCode === 27) { + this.hideModal(); } + }; + + showModal = (component, props = { right: true }) => { + this.setState({ + component, + props, + }); + document.addEventListener('keydown', this.handleKeyDown); + document.querySelector('body').style.overflow = 'hidden'; + }; + + hideModal = () => { + document.removeEventListener('keydown', this.handleKeyDown); + document.querySelector('body').style.overflow = 'visible'; + const { props } = this.state; + if (props.onClose) { + props.onClose(); + } + this.setState({ + component: null, + props: {}, + }); + }; + + state = { + component: null, + get isModalActive() { return this.component !== null },props: {}, + showModal: this.showModal, + hideModal: this.hideModal, + }; + + render() { + return ( + <ModalContext.Provider value={this.state}> + <Modal {...this.state} /> + {this.props.children} + </ModalContext.Provider> + ); + } } export const ModalConsumer = ModalContext.Consumer; diff --git a/frontend/app/components/Modal/withModal.tsx b/frontend/app/components/Modal/withModal.tsx index ec5030e54..9c7319eed 100644 --- a/frontend/app/components/Modal/withModal.tsx +++ b/frontend/app/components/Modal/withModal.tsx @@ -1,9 +1,7 @@ import React from 'react'; import { ModalConsumer } from './'; - -export default BaseComponent => React.memo(props => ( - <ModalConsumer> - { value => <BaseComponent { ...value } { ...props } /> } - </ModalConsumer> -)); \ No newline at end of file +export default (BaseComponent) => + React.memo((props) => ( + <ModalConsumer>{(value) => <BaseComponent {...value} {...props} />}</ModalConsumer> + )); diff --git a/frontend/app/components/Onboarding/components/MetadataList/MetadataList.js b/frontend/app/components/Onboarding/components/MetadataList/MetadataList.js index 96dfc6345..76068fddf 100644 --- a/frontend/app/components/Onboarding/components/MetadataList/MetadataList.js +++ b/frontend/app/components/Onboarding/components/MetadataList/MetadataList.js @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react' import { Button, SlideModal, TagBadge } from 'UI' import { connect } from 'react-redux' -import { init, fetchList, save, remove } from 'Duck/customField'; +import { fetchList, save, remove } from 'Duck/customField'; import CustomFieldForm from '../../../Client/CustomFields/CustomFieldForm'; import { confirm } from 'UI'; diff --git a/frontend/app/components/Onboarding/components/OnboardingTabs/ProjectCodeSnippet/ProjectCodeSnippet.js b/frontend/app/components/Onboarding/components/OnboardingTabs/ProjectCodeSnippet/ProjectCodeSnippet.js index 8b520aa49..7bf9ef5bb 100644 --- a/frontend/app/components/Onboarding/components/OnboardingTabs/ProjectCodeSnippet/ProjectCodeSnippet.js +++ b/frontend/app/components/Onboarding/components/OnboardingTabs/ProjectCodeSnippet/ProjectCodeSnippet.js @@ -7,7 +7,6 @@ import GDPR from 'Types/site/gdpr'; import cn from 'classnames' import stl from './projectCodeSnippet.module.css' import CircleNumber from '../../CircleNumber'; -import Highlight from 'react-highlight' import Select from 'Shared/Select' import CodeSnippet from 'Shared/CodeSnippet'; diff --git a/frontend/app/components/Onboarding/components/SideMenu.js b/frontend/app/components/Onboarding/components/SideMenu.js index 537305831..64d35a2e4 100644 --- a/frontend/app/components/Onboarding/components/SideMenu.js +++ b/frontend/app/components/Onboarding/components/SideMenu.js @@ -1,7 +1,7 @@ import React from 'react' import stl from './sideMenu.module.css' import cn from 'classnames' -import { SideMenuitem, Icon } from 'UI' +import { SideMenuitem } from 'UI' import OnboardingMenu from './OnboardingMenu/OnboardingMenu' export default function SideMenu() { diff --git a/frontend/app/components/Session/IOSPlayer/Crashes.js b/frontend/app/components/Session/IOSPlayer/Crashes.js index 015dece96..d3e89c2c8 100644 --- a/frontend/app/components/Session/IOSPlayer/Crashes.js +++ b/frontend/app/components/Session/IOSPlayer/Crashes.js @@ -13,7 +13,7 @@ function Crashes({ player }) { const [ filter, setFilter ] = useState(""); const filterRE = getRE(filter, 'i'); - const filtered = player.lists[CRASHES].listNow.filter(({ name, reason, stacktrace }) => + const filtered = player.lists[CRASHES].listNow.filter(({ name, reason, stacktrace }) => filterRE.test(name) || filterRE.test(reason) || filterRE.test(stacktrace) ); return ( @@ -23,7 +23,6 @@ function Crashes({ player }) { className="input-small" placeholder="Filter" icon="search" - iconPosition="left" name="filter" onChange={ setFilter } /> diff --git a/frontend/app/components/Session/IOSPlayer/Logs.js b/frontend/app/components/Session/IOSPlayer/Logs.js index e9fe033d7..71da9b149 100644 --- a/frontend/app/components/Session/IOSPlayer/Logs.js +++ b/frontend/app/components/Session/IOSPlayer/Logs.js @@ -20,13 +20,13 @@ function Logs({ player }) { const [ activeTab, setTab ] = useState(ALL); const onInputChange = useCallback(({ target }) => setFilter(target.value)); const filterRE = getRE(filter, 'i'); - const filtered = player.lists[LOGS].listNow.filter(({ severity, content }) => + const filtered = player.lists[LOGS].listNow.filter(({ severity, content }) => (activeTab === ALL || activeTab === severity) && filterRE.test(content) ); return ( <> <PanelLayout.Header> - <Tabs + <Tabs tabs={ TABS } active={ activeTab } onClick={ setTab } @@ -36,7 +36,6 @@ function Logs({ player }) { className="input-small" placeholder="Filter" icon="search" - iconPosition="left" name="filter" onChange={ onInputChange } /> @@ -48,8 +47,8 @@ function Logs({ player }) { title="No recordings found" > <Autoscroll> - { filtered.map(log => - <Log text={log.content} level={log.severity}/> + { filtered.map(log => + <Log text={log.content} level={log.severity}/> )} </Autoscroll> </NoContent> diff --git a/frontend/app/components/Session/Layout/Header/Section.js b/frontend/app/components/Session/Layout/Header/Section.js index c98849a19..a432cd169 100644 --- a/frontend/app/components/Session/Layout/Header/Section.js +++ b/frontend/app/components/Session/Layout/Header/Section.js @@ -1,6 +1,5 @@ import React from 'react'; import { Icon } from 'UI'; -import styles from './section.module.css'; export default function Section({ icon, label }) { return ( diff --git a/frontend/app/components/Session/Layout/Player/Controls.js b/frontend/app/components/Session/Layout/Player/Controls.js index 69af9a111..fc1360972 100644 --- a/frontend/app/components/Session/Layout/Player/Controls.js +++ b/frontend/app/components/Session/Layout/Player/Controls.js @@ -1,11 +1,8 @@ import React from 'react'; import { observer } from 'mobx-react-lite'; -import { useEffect, useCallback } from 'react'; -import { connect } from 'react-redux'; +import { useEffect } from 'react'; import cn from 'classnames'; -import { Popup, Icon } from 'UI'; - import Timeline from './Timeline'; import ControlButton from './ControlButton'; diff --git a/frontend/app/components/Session/Layout/ToolPanel.js b/frontend/app/components/Session/Layout/ToolPanel.js index 2f4779c71..6e7fc259e 100644 --- a/frontend/app/components/Session/Layout/ToolPanel.js +++ b/frontend/app/components/Session/Layout/ToolPanel.js @@ -1,5 +1,4 @@ import React from 'react'; -import { useCallback } from 'react'; import { observer } from 'mobx-react-lite'; import { CloseButton } from 'UI'; diff --git a/frontend/app/components/Session/Layout/ToolPanel/Performance.tsx b/frontend/app/components/Session/Layout/ToolPanel/Performance.tsx index 790a9b96d..c5ae6f809 100644 --- a/frontend/app/components/Session/Layout/ToolPanel/Performance.tsx +++ b/frontend/app/components/Session/Layout/ToolPanel/Performance.tsx @@ -9,10 +9,8 @@ import { Tooltip, ResponsiveContainer, ReferenceLine, - CartesianGrid, Label, } from 'recharts'; -import { Checkbox } from 'UI'; import { durationFromMsFormatted } from 'App/date'; import { formatBytes } from 'App/utils'; diff --git a/frontend/app/components/Session/Layout/ToolPanel/StackEvents.js b/frontend/app/components/Session/Layout/ToolPanel/StackEvents.js index cdfccff31..c66c7d7c2 100644 --- a/frontend/app/components/Session/Layout/ToolPanel/StackEvents.js +++ b/frontend/app/components/Session/Layout/ToolPanel/StackEvents.js @@ -2,9 +2,8 @@ import React from 'react'; import { connect } from 'react-redux'; import { useState } from 'react'; import { NoContent, Tabs } from 'UI'; -import withEnumToggle from 'HOCs/withEnumToggle'; import { hideHint } from 'Duck/components/player'; -import { typeList } from 'Types/session/stackEvent'; +import { typeList } from 'Types/session/stackEvent'; import * as PanelLayout from './PanelLayout'; import UserEvent from 'Components/Session_/StackEvents/UserEvent'; @@ -14,7 +13,7 @@ const ALL = 'ALL'; const TABS = [ ALL, ...typeList ].map(tab =>({ text: tab, key: tab })); -function StackEvents({ +function StackEvents({ stackEvents, hintIsHidden, hideHint, @@ -28,10 +27,10 @@ function StackEvents({ return ( <> <PanelLayout.Header> - <Tabs + <Tabs className="uppercase" tabs={ tabs } - active={ activeTab } + active={ activeTab } onClick={ setTab } border={ false } /> @@ -39,12 +38,12 @@ function StackEvents({ <PanelLayout.Body> <NoContent title="Nothing to display yet." - subtext={ !hintIsHidden - ? + subtext={ !hintIsHidden + ? <> <a className="underline color-teal" href="https://docs.openreplay.com/integrations" target="_blank">Integrations</a> {' and '} - <a className="underline color-teal" href="https://docs.openreplay.com/api#event" target="_blank">Events</a> + <a className="underline color-teal" href="https://docs.openreplay.com/api#event" target="_blank">Events</a> { ' make debugging easier. Sync your backend logs and custom events with session replay.' } <br/><br/> <button className="color-teal" onClick={() => hideHint("stack")}>Got It!</button> @@ -66,8 +65,8 @@ function StackEvents({ } export default connect(state => ({ - hintIsHidden: state.getIn(['components', 'player', 'hiddenHints', 'stack']) || + hintIsHidden: state.getIn(['components', 'player', 'hiddenHints', 'stack']) || !state.getIn([ 'site', 'list' ]).some(s => s.stackIntegrations), }), { hideHint -})(StackEvents); \ No newline at end of file +})(StackEvents); diff --git a/frontend/app/components/Session/LivePlayer.js b/frontend/app/components/Session/LivePlayer.js deleted file mode 100644 index ca9f40f28..000000000 --- a/frontend/app/components/Session/LivePlayer.js +++ /dev/null @@ -1,112 +0,0 @@ -import React from 'react'; -import { useEffect, useState } from 'react'; -import { connect } from 'react-redux'; -import { Loader } from 'UI'; -import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player'; -import { withRequest } from 'HOCs'; -import { PlayerProvider, connectPlayer, init as initPlayer, clean as cleanPlayer } from 'Player'; -import withPermissions from 'HOCs/withPermissions'; - -import PlayerBlockHeader from '../Session_/PlayerBlockHeader'; -import PlayerBlock from '../Session_/PlayerBlock'; -import styles from '../Session_/session.module.css'; - -const InitLoader = connectPlayer((state) => ({ - loading: !state.initialized, -}))(Loader); - -function LivePlayer({ - session, - toggleFullscreen, - closeBottomBlock, - fullscreen, - loadingCredentials, - assistCredendials, - request, - isEnterprise, - userEmail, - userName, -}) { - const [fullView, setFullView] = useState(false); - useEffect(() => { - if (!loadingCredentials) { - const sessionWithAgentData = { - ...session.toJS(), - agentInfo: { - email: userEmail, - name: userName, - }, - }; - initPlayer(sessionWithAgentData, assistCredendials, true); - } - return () => cleanPlayer(); - }, [session.sessionId, loadingCredentials, assistCredendials]); - - // LAYOUT (TODO: local layout state - useContext or something..) - useEffect(() => { - const queryParams = new URLSearchParams(window.location.search); - if (queryParams.has('fullScreen') && queryParams.get('fullScreen') === 'true') { - setFullView(true); - } - - if (isEnterprise) { - request(); - } - return () => { - toggleFullscreen(false); - closeBottomBlock(); - }; - }, []); - - const TABS = { - EVENTS: 'User Steps', - HEATMAPS: 'Click Map', - }; - const [activeTab, setActiveTab] = useState(''); - - return ( - <PlayerProvider> - <InitLoader className="flex-1 p-3"> - {!fullView && ( - <PlayerBlockHeader - activeTab={activeTab} - setActiveTab={setActiveTab} - tabs={TABS} - fullscreen={fullscreen} - /> - )} - <div className={styles.session} data-fullscreen={fullscreen || fullView}> - <PlayerBlock fullView={fullView} /> - </div> - </InitLoader> - </PlayerProvider> - ); -} - -export default withRequest({ - initialData: null, - endpoint: '/assist/credentials', - dataWrapper: (data) => data, - dataName: 'assistCredendials', - loadingName: 'loadingCredentials', -})( - withPermissions( - ['ASSIST_LIVE'], - '', - true - )( - connect( - (state) => { - return { - session: state.getIn(['sessions', 'current']), - showAssist: state.getIn(['sessions', 'showChatWindow']), - fullscreen: state.getIn(['components', 'player', 'fullscreen']), - isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee', - userEmail: state.getIn(['user', 'account', 'email']), - userName: state.getIn(['user', 'account', 'name']), - }; - }, - { toggleFullscreen, closeBottomBlock } - )(LivePlayer) - ) -); diff --git a/frontend/app/components/Session/LivePlayer.tsx b/frontend/app/components/Session/LivePlayer.tsx new file mode 100644 index 000000000..cace337e8 --- /dev/null +++ b/frontend/app/components/Session/LivePlayer.tsx @@ -0,0 +1,124 @@ +import React from 'react'; +import { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import withRequest from 'HOCs/withRequest'; +import withPermissions from 'HOCs/withPermissions'; +import { PlayerContext, defaultContextValue, ILivePlayerContext } from './playerContext'; +import { makeAutoObservable } from 'mobx'; +import { createLiveWebPlayer } from 'Player'; +import PlayerBlockHeader from './Player/LivePlayer/LivePlayerBlockHeader'; +import PlayerBlock from './Player/LivePlayer/LivePlayerBlock'; +import styles from '../Session_/session.module.css'; +import Session from 'App/mstore/types/session'; +import withLocationHandlers from 'HOCs/withLocationHandlers'; + +interface Props { + session: Session; + loadingCredentials: boolean; + assistCredentials: RTCIceServer[]; + isEnterprise: boolean; + userEmail: string; + userName: string; + customSession?: Session; + isMultiview?: boolean; + query?: Record<string, (key: string) => any>; + request: () => void; +} + +function LivePlayer({ + session, + loadingCredentials, + assistCredentials, + request, + isEnterprise, + userEmail, + userName, + isMultiview, + customSession, + query +}: Props) { + // @ts-ignore + const [contextValue, setContextValue] = useState<ILivePlayerContext>(defaultContextValue); + const [fullView, setFullView] = useState(false); + const openedFromMultiview = query?.get('multi') === 'true' + const usedSession = isMultiview ? customSession! : session; + + useEffect(() => { + if (loadingCredentials || !usedSession.sessionId) return; + const sessionWithAgentData = { + ...usedSession, + agentInfo: { + email: userEmail, + name: userName, + }, + }; + const [player, store] = createLiveWebPlayer(sessionWithAgentData, assistCredentials, (state) => + makeAutoObservable(state) + ); + setContextValue({ player, store }); + + return () => player.clean(); + }, [session.sessionId, assistCredentials]); + + // LAYOUT (TODO: local layout state - useContext or something..) + useEffect(() => { + const queryParams = new URLSearchParams(window.location.search); + if ( + (queryParams.has('fullScreen') && queryParams.get('fullScreen') === 'true') || + location.pathname.includes('multiview') + ) { + setFullView(true); + } + + if (isEnterprise) { + request(); + } + }, []); + + if (!contextValue.player) return null; + + return ( + <PlayerContext.Provider value={contextValue}> + {!fullView && ( + <PlayerBlockHeader + // @ts-ignore + isMultiview={openedFromMultiview} + /> + )} + <div + className={styles.session} + style={{ + height: isMultiview ? '100%' : undefined, + width: isMultiview ? '100%' : undefined, + }} + > + <PlayerBlock isMultiview={isMultiview} /> + </div> + </PlayerContext.Provider> + ); +} + +export default withRequest({ + initialData: null, + endpoint: '/assist/credentials', + dataName: 'assistCredentials', + loadingName: 'loadingCredentials', +})( + withPermissions( + ['ASSIST_LIVE'], + '', + true + )( + connect( + (state: any) => { + return { + session: state.getIn(['sessions', 'current']), + showAssist: state.getIn(['sessions', 'showChatWindow']), + isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee', + userEmail: state.getIn(['user', 'account', 'email']), + userName: state.getIn(['user', 'account', 'name']), + }; + } + )(withLocationHandlers()(LivePlayer)) + ) +); diff --git a/frontend/app/components/Session/LiveSession.js b/frontend/app/components/Session/LiveSession.js index f35997b11..8f898b787 100644 --- a/frontend/app/components/Session/LiveSession.js +++ b/frontend/app/components/Session/LiveSession.js @@ -5,57 +5,65 @@ import usePageTitle from 'App/hooks/usePageTitle'; import { fetch as fetchSession } from 'Duck/sessions'; import { fetchList as fetchSlackList } from 'Duck/integrations/slack'; import { Loader } from 'UI'; -// import { sessions as sessionsRoute } from 'App/routes'; -import withPermissions from 'HOCs/withPermissions' +import withPermissions from 'HOCs/withPermissions'; import LivePlayer from './LivePlayer'; +import { clearLogs } from 'App/dev/console'; -// const SESSIONS_ROUTE = sessionsRoute(); +function LiveSession({ + sessionId, + loading, + fetchSession, + fetchSlackList, + hasSessionsPath, +}) { + usePageTitle('OpenReplay Assist'); -function LiveSession({ - sessionId, - loading, - // hasErrors, - session, - fetchSession, - fetchSlackList, - hasSessionsPath - }) { - usePageTitle("OpenReplay Assist"); + useEffect(() => { + clearLogs(); + fetchSlackList(); + }, []); - useEffect(() => { - fetchSlackList() - }, []); + useEffect(() => { + if (sessionId != null) { + fetchSession(sessionId, true); + } else { + console.error('No sessionID in route.'); + } + }, [sessionId, hasSessionsPath]); - useEffect(() => { - if (sessionId != null) { - fetchSession(sessionId, true) - } else { - console.error("No sessionID in route.") - } - return () => { - if (!session.exists()) return; - } - },[ sessionId, hasSessionsPath ]); - - return ( - <Loader className="flex-1" loading={ loading }> - <LivePlayer /> - </Loader> - ); + return ( + <Loader className="flex-1" loading={loading}> + <LivePlayer /> + </Loader> + ); } -export default withPermissions(['ASSIST_LIVE'], '', true)(connect((state, props) => { - const { match: { params: { sessionId } } } = props; - const isAssist = state.getIn(['sessions', 'activeTab']).type === 'live'; - const hasSessiosPath = state.getIn([ 'sessions', 'sessionPath' ]).pathname.includes('/sessions'); - return { - sessionId, - loading: state.getIn([ 'sessions', 'loading' ]), - // hasErrors: !!state.getIn([ 'sessions', 'errors' ]), - session: state.getIn([ 'sessions', 'current' ]), - hasSessionsPath: hasSessiosPath && !isAssist, - }; -}, { - fetchSession, - fetchSlackList, -})(LiveSession)); \ No newline at end of file +export default withPermissions( + ['ASSIST_LIVE'], + '', + true +)( + connect( + (state, props) => { + const { + match: { + params: { sessionId }, + }, + } = props; + const isAssist = state.getIn(['sessions', 'activeTab']).type === 'live'; + const hasSessiosPath = state + .getIn(['sessions', 'sessionPath']) + .pathname.includes('/sessions'); + return { + sessionId, + loading: state.getIn(['sessions', 'loading']), + session: state.getIn(['sessions', 'current']), + hasSessionsPath: hasSessiosPath && !isAssist, + }; + }, + { + fetchSession, + fetchSlackList, + } + )(LiveSession) +); diff --git a/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx b/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx new file mode 100644 index 000000000..8a51717e5 --- /dev/null +++ b/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { findDOMNode } from 'react-dom'; +import cn from 'classnames'; +import Overlay from 'Components/Session_/Player/Overlay'; +import stl from 'Components/Session_/Player/player.module.css'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite' + +function Player() { + const playerContext = React.useContext(PlayerContext); + const screenWrapper = React.useRef<HTMLDivElement>(null); + React.useEffect(() => { + const parentElement = findDOMNode(screenWrapper.current) as HTMLDivElement | null; + if (parentElement) { + playerContext.player.attach(parentElement); + } + }, []); + + + if (!playerContext.player) return null; + + return ( + <div + className={cn(stl.playerBody, 'flex-1 flex flex-col relative')} + > + <div className={cn("relative flex-1", 'overflow-visible')}> + <Overlay isClickmap /> + <div className={cn(stl.screenWrapper, '!overflow-y-scroll')} style={{ maxHeight: 800 }} ref={screenWrapper} /> + </div> + </div> + ); +} + +export default observer(Player); diff --git a/frontend/app/components/Session/Player/ClickMapRenderer/ThinPlayer.tsx b/frontend/app/components/Session/Player/ClickMapRenderer/ThinPlayer.tsx new file mode 100644 index 000000000..886bb848c --- /dev/null +++ b/frontend/app/components/Session/Player/ClickMapRenderer/ThinPlayer.tsx @@ -0,0 +1,63 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import { createClickMapPlayer } from 'Player'; +import { makeAutoObservable } from 'mobx'; +import withLocationHandlers from 'HOCs/withLocationHandlers'; +import PlayerContent from './ThinPlayerContent'; +import { IPlayerContext, PlayerContext, defaultContextValue } from '../../playerContext'; +import { observer } from 'mobx-react-lite'; + + +function WebPlayer(props: any) { + const { + session, + customSession, + insights, + jumpTimestamp, + onMarkerClick, + } = props; + // @ts-ignore + const [contextValue, setContextValue] = useState<IPlayerContext>(defaultContextValue); + + useEffect(() => { + const [WebPlayerInst, PlayerStore] = createClickMapPlayer(customSession, (state) => + makeAutoObservable(state) + ); + setContextValue({ player: WebPlayerInst, store: PlayerStore }); + + return () => WebPlayerInst.clean(); + }, [session.sessionId]); + + const isPlayerReady = contextValue.store?.get().ready + + React.useEffect(() => { + contextValue.player && contextValue.player.play() + if (isPlayerReady && insights.size > 0) { + setTimeout(() => { + contextValue.player.pause() + contextValue.player.jump(jumpTimestamp) + contextValue.player.scale() + setTimeout(() => { contextValue.player.showClickmap(insights, onMarkerClick) }, 250) + }, 500) + } + return () => { + isPlayerReady && contextValue.player.showClickmap(null) + } + }, [insights, isPlayerReady, jumpTimestamp]) + + if (!contextValue.player || !session) return null; + + return ( + <PlayerContext.Provider value={contextValue}> + <PlayerContent /> + </PlayerContext.Provider> + ); +} + +export default connect( + (state: any) => ({ + session: state.getIn(['sessions', 'current']), + insights: state.getIn(['sessions', 'insights']), + jwt: state.getIn(['user', 'jwt']), + }) +)(withLocationHandlers()(observer(WebPlayer))); diff --git a/frontend/app/components/Session/Player/ClickMapRenderer/ThinPlayerContent.tsx b/frontend/app/components/Session/Player/ClickMapRenderer/ThinPlayerContent.tsx new file mode 100644 index 000000000..242c1c21f --- /dev/null +++ b/frontend/app/components/Session/Player/ClickMapRenderer/ThinPlayerContent.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import cn from 'classnames'; +import styles from './styles.module.css'; +import Renderer from './Renderer'; + +function PlayerContent() { + + return ( + <div className="relative"> + <div className={'flex'}> + <div + className="w-full" + > + <div className={cn(styles.session, 'relative')}> + <div + className={cn(styles.playerBlock, 'flex flex-col', 'overflow-visible')} + style={{ zIndex: 1, minWidth: '100%' }} + > + <Renderer /> + </div> + </div> + </div> + </div> + </div> + ); +} + + +export default observer(PlayerContent); diff --git a/frontend/app/components/Session/Player/ClickMapRenderer/index.ts b/frontend/app/components/Session/Player/ClickMapRenderer/index.ts new file mode 100644 index 000000000..23e0a0566 --- /dev/null +++ b/frontend/app/components/Session/Player/ClickMapRenderer/index.ts @@ -0,0 +1 @@ +export { default } from './ThinPlayer' \ No newline at end of file diff --git a/frontend/app/components/Session/Player/ClickMapRenderer/styles.module.css b/frontend/app/components/Session/Player/ClickMapRenderer/styles.module.css new file mode 100644 index 000000000..ed724966f --- /dev/null +++ b/frontend/app/components/Session/Player/ClickMapRenderer/styles.module.css @@ -0,0 +1,4 @@ +.session { + display: flex; + max-height: 800px; +} \ No newline at end of file diff --git a/frontend/app/components/Session/Player/LivePlayer/AssistDuration.tsx b/frontend/app/components/Session/Player/LivePlayer/AssistDuration.tsx new file mode 100644 index 000000000..226017722 --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/AssistDuration.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Duration } from 'luxon'; +import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; + +const AssistDurationCont = () => { + // @ts-ignore ??? TODO + const { store } = React.useContext<ILivePlayerContext>(PlayerContext) + const { assistStart } = store.get() + + const [assistDuration, setAssistDuration] = React.useState('00:00'); + React.useEffect(() => { + const interval = setInterval(() => { + setAssistDuration(Duration.fromMillis(+new Date() - assistStart).toFormat('mm:ss')); + } + , 500); + return () => clearInterval(interval); + }, []) + return ( + <> + Elapsed {assistDuration} + </> + ) +} + +const AssistDuration = observer(AssistDurationCont) + +export default AssistDuration; diff --git a/frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/AssistSessionsTabs.tsx b/frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/AssistSessionsTabs.tsx new file mode 100644 index 000000000..b156ee3f4 --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/AssistSessionsTabs.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import cn from 'classnames'; +import { Icon } from 'UI'; +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite'; +import { useHistory } from 'react-router-dom'; +import { multiview, liveSession, withSiteId } from 'App/routes'; +import { connect } from 'react-redux'; +import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext'; + +interface ITab { + onClick?: () => void; + classNames?: string; + children: React.ReactNode; + style?: Record<string, any>; + isDisabled?: boolean; +} + +const Tab = (props: ITab) => ( + <div + onClick={props.onClick} + className={cn('p-1 rounded flex items-center justify-center', !props.isDisabled ? 'cursor-pointer' : 'cursor-not-allowed', props.classNames)} + style={props.style} + > + {props.children} + </div> +); + +export const InactiveTab = React.memo((props: Omit<ITab, 'children'>) => ( + <Tab onClick={props.onClick} classNames={cn("hover:bg-gray-bg bg-gray-light", !props.isDisabled ? 'cursor-pointer' : 'cursor-not-allowed', props.classNames)}> + <Icon name="plus" size="22" color="white" /> + </Tab> +)); + +const ActiveTab = React.memo((props: Omit<ITab, 'children'>) => ( + <Tab onClick={props.onClick} classNames="hover:bg-teal" style={{ background: 'rgba(57, 78, 255, 0.5)' }}> + <Icon name="play-fill-new" size="22" color="white" /> + </Tab> +)); + +const CurrentTab = React.memo(() => ( + <Tab classNames="bg-teal color-white"> + <span style={{ fontSize: '0.65rem' }}>PLAYING</span> + </Tab> +)); + +function AssistTabs({ session, siteId }: { session: Record<string, any>; siteId: string }) { + const history = useHistory(); + const { store } = React.useContext(PlayerContext) as unknown as ILivePlayerContext + const { recordingState, calling, remoteControl } = store.get() + const isDisabled = recordingState !== 0 || calling !== 0 || remoteControl !== 0 + + const { assistMultiviewStore } = useStore(); + + const placeholder = new Array(4 - assistMultiviewStore.sessions.length).fill(0); + + React.useEffect(() => { + if (assistMultiviewStore.sessions.length === 0) { + assistMultiviewStore.setDefault(session); + } + }, []); + + const openGrid = () => { + if (isDisabled) return; + const sessionIdQuery = encodeURIComponent(assistMultiviewStore.sessions.map((s) => s.sessionId).join(',')); + return history.push(withSiteId(multiview(sessionIdQuery), siteId)); + }; + const openLiveSession = (sessionId: string) => { + if (isDisabled) return; + assistMultiviewStore.setActiveSession(sessionId); + history.push(withSiteId(liveSession(sessionId), siteId)); + }; + + return ( + <div className="grid grid-cols-2 w-28 h-full" style={{ gap: '4px', opacity: isDisabled ? 0.6 : 1, cursor: isDisabled ? 'not-allowed' : undefined }}> + {assistMultiviewStore.sortedSessions.map((session: { key: number, sessionId: string }) => ( + <React.Fragment key={session.key}> + {assistMultiviewStore.isActive(session.sessionId) ? ( + <CurrentTab /> + ) : ( + <ActiveTab isDisabled={isDisabled} onClick={() => openLiveSession(session.sessionId)} /> + )} + </React.Fragment> + ))} + {placeholder.map((_, i) => ( + <React.Fragment key={i}> + <InactiveTab isDisabled={isDisabled} onClick={openGrid} /> + </React.Fragment> + ))} + </div> + ); +} + +export default connect((state: any) => ({ siteId: state.getIn(['site', 'siteId']) }))( + observer(AssistTabs) +); diff --git a/frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/index.tsx b/frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/index.tsx new file mode 100644 index 000000000..c7668e331 --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/AssistSessionsTabs/index.tsx @@ -0,0 +1 @@ +export { default, InactiveTab } from './AssistSessionsTabs' diff --git a/frontend/app/components/Session/Player/LivePlayer/LiveControls.tsx b/frontend/app/components/Session/Player/LivePlayer/LiveControls.tsx new file mode 100644 index 000000000..c17902380 --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/LiveControls.tsx @@ -0,0 +1,131 @@ +import React from 'react'; +import cn from 'classnames'; +import { connect } from 'react-redux'; +import LiveTag from './LiveTag'; +import AssistSessionsTabs from './AssistSessionsTabs'; + +import { + CONSOLE, toggleBottomBlock, +} from 'Duck/components/player'; +import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { fetchSessions } from 'Duck/liveSearch'; + +import AssistDuration from './AssistDuration'; +import Timeline from './Timeline'; +import ControlButton from 'Components/Session_/Player/Controls/ControlButton'; + +import styles from 'Components/Session_/Player/Controls/controls.module.css'; + +function Controls(props: any) { + // @ts-ignore ?? TODO + const { player, store } = React.useContext<ILivePlayerContext>(PlayerContext); + + const { jumpToLive } = player; + const { + livePlay, + logMarkedCountNow: logRedCount, + exceptionsList, + } = store.get(); + const showExceptions = exceptionsList.length > 0; + const { + bottomBlock, + toggleBottomBlock, + closedLive, + skipInterval, + session, + fetchSessions: fetchAssistSessions, + totalAssistSessions, + } = props; + + const onKeyDown = (e: any) => { + if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { + return; + } + if (e.key === 'ArrowRight') { + forthTenSeconds(); + } + if (e.key === 'ArrowLeft') { + backTenSeconds(); + } + }; + + React.useEffect(() => { + document.addEventListener('keydown', onKeyDown.bind(this)); + if (totalAssistSessions === 0) { + fetchAssistSessions(); + } + return () => { + document.removeEventListener('keydown', onKeyDown.bind(this)); + }; + }, []); + + const forthTenSeconds = () => { + // @ts-ignore + player.jumpInterval(SKIP_INTERVALS[skipInterval]); + }; + + const backTenSeconds = () => { + // @ts-ignore + player.jumpInterval(-SKIP_INTERVALS[skipInterval]); + }; + + + + const toggleBottomTools = (blockName: number) => { + toggleBottomBlock(blockName); + }; + + return ( + <div className={styles.controls}> + <Timeline /> + <div className={cn(styles.buttons, '!px-5 !pt-0')} data-is-live> + <div className="flex items-center"> + {!closedLive && ( + <div className={styles.buttonsLeft}> + <LiveTag isLive={livePlay} onClick={() => (livePlay ? null : jumpToLive())} /> + <div className="font-semibold px-2"> + <AssistDuration /> + </div> + </div> + )} + </div> + + {totalAssistSessions > 1 ? ( + <div> + <AssistSessionsTabs session={session} /> + </div> + ) : null} + + <div className="flex items-center h-full"> + <ControlButton + onClick={() => toggleBottomTools(CONSOLE)} + active={bottomBlock === CONSOLE} + label="CONSOLE" + noIcon + labelClassName="!text-base font-semibold" + hasErrors={logRedCount > 0 || showExceptions} + containerClassName="mx-2" + /> + </div> + </div> + </div> + ); +} + +const ControlPlayer = observer(Controls); + +export default connect( + (state: any) => { + return { + session: state.getIn(['sessions', 'current']), + totalAssistSessions: state.getIn(['liveSearch', 'total']), + closedLive: + !!state.getIn(['sessions', 'errors']) || !state.getIn(['sessions', 'current']).live, + }; + }, + { + fetchSessions, + toggleBottomBlock + } +)(ControlPlayer); \ No newline at end of file diff --git a/frontend/app/components/Session/Player/LivePlayer/LivePlayerBlock.tsx b/frontend/app/components/Session/Player/LivePlayer/LivePlayerBlock.tsx new file mode 100644 index 000000000..0bdd169d5 --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/LivePlayerBlock.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import cn from 'classnames'; +import Player from './LivePlayerInst'; +import SubHeader from './LivePlayerSubHeader'; + +import styles from 'Components/Session_/playerBlock.module.css'; + +interface IProps { + fullView?: boolean; + isMultiview?: boolean; +} + +function LivePlayerBlock(props: IProps) { + const { fullView = false, isMultiview } = props; + + const shouldShowSubHeader = !fullView && !isMultiview + + return ( + <div className={cn(styles.playerBlock, 'flex flex-col', 'overflow-x-hidden')} style={{ zIndex: undefined, minWidth: isMultiview ? '100%' : undefined }}> + {shouldShowSubHeader ? ( + <SubHeader /> + ) : null} + <Player + fullView={fullView} + isMultiview={isMultiview} + /> + </div> + ); +} + +export default LivePlayerBlock \ No newline at end of file diff --git a/frontend/app/components/Session/Player/LivePlayer/LivePlayerBlockHeader.tsx b/frontend/app/components/Session/Player/LivePlayer/LivePlayerBlockHeader.tsx new file mode 100644 index 000000000..1fd3b503e --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/LivePlayerBlockHeader.tsx @@ -0,0 +1,122 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { withRouter } from 'react-router-dom'; +import { + assist as assistRoute, + withSiteId, + multiview, +} from 'App/routes'; +import { BackLink, Icon } from 'UI'; +import cn from 'classnames'; +import SessionMetaList from 'Shared/SessionItem/SessionMetaList'; +import UserCard from '../ReplayPlayer/EventsBlock/UserCard'; +import { PlayerContext } from 'Components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { useStore } from 'App/mstore' +import stl from '../ReplayPlayer/playerBlockHeader.module.css'; +import AssistActions from 'Components/Assist/components/AssistActions'; +import AssistTabs from 'Components/Assist/components/AssistTabs'; + +const ASSIST_ROUTE = assistRoute(); + +// TODO props +function LivePlayerBlockHeader(props: any) { + const [hideBack, setHideBack] = React.useState(false); + const { store } = React.useContext(PlayerContext); + const { assistMultiviewStore } = useStore(); + + const { width, height } = store.get(); + + const { + session, + metaList, + closedLive = false, + siteId, + location, + history, + isMultiview, + } = props; + + React.useEffect(() => { + const queryParams = new URLSearchParams(location.search); + setHideBack(queryParams.has('iframe') && queryParams.get('iframe') === 'true'); + }, []); + + const backHandler = () => { + history.push(withSiteId(ASSIST_ROUTE, siteId)); + }; + + const { userId, userNumericHash, metadata, isCallActive, agentIds } = session; + let _metaList = Object.keys(metadata) + .filter((i) => metaList.includes(i)) + .map((key) => { + const value = metadata[key]; + return { label: key, value }; + }); + + const openGrid = () => { + const sessionIdQuery = encodeURIComponent(assistMultiviewStore.sessions.map((s) => s?.sessionId).join(',')); + return history.push(withSiteId(multiview(sessionIdQuery), siteId)); + }; + + return ( + <div className={cn(stl.header, 'flex justify-between')}> + <div className="flex w-full items-center"> + {!hideBack && ( + <div + className="flex items-center h-full cursor-pointer group" + onClick={() => (assistMultiviewStore.sessions.length > 1 || isMultiview ? openGrid() : backHandler())} + > + {assistMultiviewStore.sessions.length > 1 || isMultiview ? ( + <> + <div className="rounded-full border group-hover:border-teal group-hover:text-teal group-hover:fill-teal p-1 mr-2"> + <Icon name="close" color="inherit" size={13} /> + </div> + <span className="group-hover:text-teal group-hover:fill-teal"> + Close + </span> + <div className={stl.divider} /> + </> + ) : ( + <> + {/* @ts-ignore TODO */} + <BackLink label="Back" className="h-full" /> + <div className={stl.divider} /> + </> + )} + </div> + )} + <UserCard className="" width={width} height={height} /> + <AssistTabs userId={userId} userNumericHash={userNumericHash} /> + + <div className={cn('ml-auto flex items-center h-full', { hidden: closedLive })}> + {_metaList.length > 0 && ( + <div className="border-l h-full flex items-center px-2"> + <SessionMetaList className="" metaList={_metaList} maxLength={2} /> + </div> + )} + + <AssistActions userId={userId} isCallActive={isCallActive} agentIds={agentIds} /> + </div> + </div> + </div> + ); +} + +const PlayerHeaderCont = connect( + (state: any) => { + const isAssist = window.location.pathname.includes('/assist/'); + const session = state.getIn(['sessions', 'current']); + + return { + isAssist, + session, + sessionPath: state.getIn(['sessions', 'sessionPath']), + siteId: state.getIn(['site', 'siteId']), + metaList: state.getIn(['customFields', 'list']).map((i: any) => i.key), + closedLive: !!state.getIn(['sessions', 'errors']) || (isAssist && !session.live), + }; + } +)(observer(LivePlayerBlockHeader)); + +export default withRouter(PlayerHeaderCont); diff --git a/frontend/app/components/Session/Player/LivePlayer/LivePlayerInst.tsx b/frontend/app/components/Session/Player/LivePlayer/LivePlayerInst.tsx new file mode 100644 index 000000000..c17007648 --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/LivePlayerInst.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { findDOMNode } from 'react-dom'; +import cn from 'classnames'; +import LiveControls from './LiveControls'; +import ConsolePanel from 'Shared/DevTools/ConsolePanel'; +import { observer } from 'mobx-react-lite' +import Overlay from './Overlay'; +import stl from 'Components/Session_/Player/player.module.css'; +import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext'; +import { CONSOLE } from "Duck/components/player"; + +interface IProps { + closedLive: boolean; + fullView: boolean; + isMultiview?: boolean; + bottomBlock: number; +} + +function Player(props: IProps) { + const { + closedLive, + fullView, + isMultiview, + bottomBlock, + } = props; + // @ts-ignore TODO + const playerContext = React.useContext<ILivePlayerContext>(PlayerContext); + const screenWrapper = React.useRef<HTMLDivElement>(null); + const ready = playerContext.store.get().ready + + console.log(ready) + React.useEffect(() => { + if (!props.closedLive || isMultiview) { + const parentElement = findDOMNode(screenWrapper.current) as HTMLDivElement | null; //TODO: good architecture + if (parentElement) { + playerContext.player.attach(parentElement); + playerContext.player.play(); + } + } + }, []); + + React.useEffect(() => { + playerContext.player.scale(); + }, [playerContext.player, ready]); + + if (!playerContext.player) return null; + + const maxWidth = '100vw'; + return ( + <div + className={cn(stl.playerBody, 'flex flex-1 flex-col relative')} + > + <div className="relative flex-1 overflow-hidden"> + <Overlay closedLive={closedLive} /> + <div className={cn(stl.screenWrapper)} ref={screenWrapper} /> + </div> + {bottomBlock === CONSOLE ? ( + <div style={{ maxWidth, width: '100%' }}> + <ConsolePanel /> + </div> + ) : null} + {!fullView && !isMultiview ? ( + <LiveControls + jump={playerContext.player.jump} + /> + ) : null} + </div> + ); +} + +export default connect( + (state: any) => { + const isAssist = window.location.pathname.includes('/assist/'); + return { + sessionId: state.getIn(['sessions', 'current']).sessionId, + bottomBlock: state.getIn(['components', 'player', 'bottomBlock']), + closedLive: + !!state.getIn(['sessions', 'errors']) || + (isAssist && !state.getIn(['sessions', 'current']).live), + }; + } +)(observer(Player)); diff --git a/frontend/app/components/Session/Player/LivePlayer/LivePlayerSubHeader.tsx b/frontend/app/components/Session/Player/LivePlayer/LivePlayerSubHeader.tsx new file mode 100644 index 000000000..94892a3a7 --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/LivePlayerSubHeader.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Icon, Tooltip } from 'UI'; +import copy from 'copy-to-clipboard'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; + +function SubHeader() { + const { store } = React.useContext(PlayerContext) + const { + location: currentLocation, + } = store.get() + const [isCopied, setCopied] = React.useState(false); + + const location = + currentLocation !== undefined ? currentLocation.length > 60 + ? `${currentLocation.slice(0, 60)}...` + : currentLocation : undefined; + + return ( + <div className="w-full px-4 py-2 flex items-center border-b min-h-3"> + {location && ( + <div + className="flex items-center cursor-pointer color-gray-medium text-sm p-1 hover:bg-gray-light-shade rounded-md" + onClick={() => { + copy(currentLocation || ''); + setCopied(true); + setTimeout(() => setCopied(false), 5000); + }} + > + <Icon size="20" name="event/link" className="mr-1" /> + <Tooltip title={isCopied ? 'URL Copied to clipboard' : 'Click to copy'}> + {location} + </Tooltip> + </div> + )} + </div> + ); +} + +export default observer(SubHeader); diff --git a/frontend/app/components/shared/LiveTag/LiveTag.module.css b/frontend/app/components/Session/Player/LivePlayer/LiveTag/LiveTag.module.css similarity index 100% rename from frontend/app/components/shared/LiveTag/LiveTag.module.css rename to frontend/app/components/Session/Player/LivePlayer/LiveTag/LiveTag.module.css diff --git a/frontend/app/components/shared/LiveTag/LiveTag.tsx b/frontend/app/components/Session/Player/LivePlayer/LiveTag/LiveTag.tsx similarity index 100% rename from frontend/app/components/shared/LiveTag/LiveTag.tsx rename to frontend/app/components/Session/Player/LivePlayer/LiveTag/LiveTag.tsx diff --git a/frontend/app/components/shared/LiveTag/index.js b/frontend/app/components/Session/Player/LivePlayer/LiveTag/index.js similarity index 100% rename from frontend/app/components/shared/LiveTag/index.js rename to frontend/app/components/Session/Player/LivePlayer/LiveTag/index.js diff --git a/frontend/app/components/Session/Player/LivePlayer/Overlay/LiveOverlay.tsx b/frontend/app/components/Session/Player/LivePlayer/Overlay/LiveOverlay.tsx new file mode 100644 index 000000000..38eef2ba1 --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/Overlay/LiveOverlay.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { + SessionRecordingStatus, + getStatusText, + CallingState, + ConnectionStatus, + RemoteControlStatus, +} from 'Player'; + +import LiveStatusText from './LiveStatusText'; +import Loader from 'Components/Session_/Player/Overlay/Loader'; +import RequestingWindow, { WindowType } from 'App/components/Assist/RequestingWindow'; +import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; + +interface Props { + closedLive?: boolean, +} + +function Overlay({ + closedLive, +}: Props) { + // @ts-ignore ?? TODO + const { store } = React.useContext<ILivePlayerContext>(PlayerContext) + + const { + messagesLoading, + cssLoading, + peerConnectionStatus, + livePlay, + calling, + remoteControl, + recordingState, + } = store.get() + const loading = messagesLoading || cssLoading + const liveStatusText = getStatusText(peerConnectionStatus) + const connectionStatus = peerConnectionStatus + + const showLiveStatusText = livePlay && liveStatusText && !loading; + + const showRequestWindow = + (calling === CallingState.Connecting || + remoteControl === RemoteControlStatus.Requesting || + recordingState === SessionRecordingStatus.Requesting); + + const getRequestWindowType = () => { + if (calling === CallingState.Connecting) { + return WindowType.Call + } + if (remoteControl === RemoteControlStatus.Requesting) { + return WindowType.Control + } + if (recordingState === SessionRecordingStatus.Requesting) { + return WindowType.Record + } + + return null; + } + + return ( + <> + {/* @ts-ignore wtf */} + {showRequestWindow ? <RequestingWindow getWindowType={getRequestWindowType} /> : null} + {showLiveStatusText && ( + <LiveStatusText + connectionStatus={closedLive ? ConnectionStatus.Closed : connectionStatus} + /> + )} + {loading ? <Loader /> : null} + </> + ); +} + +export default observer(Overlay); diff --git a/frontend/app/components/Session/Player/LivePlayer/Overlay/LiveStatusText.tsx b/frontend/app/components/Session/Player/LivePlayer/Overlay/LiveStatusText.tsx new file mode 100644 index 000000000..4c7d39f6f --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/Overlay/LiveStatusText.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import ovStl from 'Components/Session_/Player/Overlay/overlay.module.css'; +import { ConnectionStatus } from 'Player'; +import { Loader } from 'UI'; + +interface Props { + connectionStatus: ConnectionStatus; +} + +export default function LiveStatusText({ connectionStatus }: Props) { + const renderView = () => { + switch (connectionStatus) { + case ConnectionStatus.Closed: + return ( + <div className="flex flex-col items-center text-center"> + <div className="text-lg -mt-8">Session not found</div> + <div className="text-sm">The remote session doesn’t exist anymore. <br/> The user may have closed the tab/browser while you were trying to establish a connection.</div> + </div> + ) + + case ConnectionStatus.Connecting: + return ( + <div className="flex flex-col items-center"> + <Loader loading={true} /> + <div className="text-lg -mt-8">Connecting...</div> + <div className="text-sm">Establishing a connection with the remote session.</div> + </div> + ) + case ConnectionStatus.WaitingMessages: + return ( + <div className="flex flex-col items-center"> + <Loader loading={true} /> + <div className="text-lg -mt-8">Waiting for the session to become active...</div> + <div className="text-sm">If it's taking too much time, it could mean the user is simply inactive.</div> + </div> + ) + case ConnectionStatus.Connected: + return ( + <div className="flex flex-col items-center"> + <div className="text-lg -mt-8">Connected</div> + </div> + ) + case ConnectionStatus.Inactive: + return ( + <div className="flex flex-col items-center"> + <Loader loading={true} /> + <div className="text-lg -mt-8">Waiting for the session to become active...</div> + <div className="text-sm">If it's taking too much time, it could mean the user is simply inactive.</div> + </div> + ) + case ConnectionStatus.Disconnected: + return ( + <div className="flex flex-col items-center"> + <div className="text-lg -mt-8">Disconnected</div> + <div className="text-sm">The connection was lost with the remote session. The user may have simply closed the tab/browser.</div> + </div> + ) + case ConnectionStatus.Error: + return ( + <div className="flex flex-col items-center"> + <div className="text-lg -mt-8">Error</div> + <div className="text-sm">Something wrong just happened. Try refreshing the page.</div> + </div> + ) + } + } + return <div className={ovStl.overlay}> + { renderView()} + </div> +} diff --git a/frontend/app/components/Session/Player/LivePlayer/Overlay/index.ts b/frontend/app/components/Session/Player/LivePlayer/Overlay/index.ts new file mode 100644 index 000000000..0cd39ac1a --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/Overlay/index.ts @@ -0,0 +1 @@ +export { default } from './LiveOverlay' \ No newline at end of file diff --git a/frontend/app/components/Session/Player/LivePlayer/Timeline.tsx b/frontend/app/components/Session/Player/LivePlayer/Timeline.tsx new file mode 100644 index 000000000..cba5547b9 --- /dev/null +++ b/frontend/app/components/Session/Player/LivePlayer/Timeline.tsx @@ -0,0 +1,165 @@ +import React, { useMemo, useContext, useState, useRef } from 'react'; +import { connect } from 'react-redux'; +import TimeTracker from 'Components/Session_/Player/Controls/TimeTracker'; +import stl from 'Components/Session_/Player/Controls/timeline.module.css'; +import { setTimelinePointer, setTimelineHoverTime } from 'Duck/sessions'; +import DraggableCircle from 'Components/Session_/Player/Controls/components/DraggableCircle'; +import CustomDragLayer, { OnDragCallback } from 'Components/Session_/Player/Controls/components/CustomDragLayer'; +import { debounce } from 'App/utils'; +import TooltipContainer from 'Components/Session_/Player/Controls/components/TooltipContainer'; +import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { Duration } from 'luxon'; + +interface IProps { + setTimelineHoverTime: (t: number) => void + startedAt: number + tooltipVisible: boolean +} + +function Timeline(props: IProps) { + // @ts-ignore + const { player, store } = useContext<ILivePlayerContext>(PlayerContext) + const [wasPlaying, setWasPlaying] = useState(false) + const { + playing, + time, + ready, + endTime, + liveTimeTravel, + } = store.get() + + const timelineRef = useRef<HTMLDivElement>(null) + const progressRef = useRef<HTMLDivElement>(null) + + const scale = 100 / endTime; + + const debouncedJump = useMemo(() => debounce(player.jump, 500), []) + const debouncedTooltipChange = useMemo(() => debounce(props.setTimelineHoverTime, 50), []) + + const onDragEnd = () => { + if (!liveTimeTravel) return; + + if (wasPlaying) { + player.togglePlay(); + } + }; + + const onDrag: OnDragCallback = (offset: { x: number }) => { + if ((!liveTimeTravel) || !progressRef.current) return; + + const p = (offset.x) / progressRef.current.offsetWidth; + const time = Math.max(Math.round(p * endTime), 0); + debouncedJump(time); + hideTimeTooltip(); + if (playing) { + setWasPlaying(true) + player.pause(); + } + }; + + const getLiveTime = (e: React.MouseEvent) => { + const duration = new Date().getTime() - props.startedAt; + // @ts-ignore type mismatch from react? + const p = e.nativeEvent.offsetX / e.target.offsetWidth; + const time = Math.max(Math.round(p * duration), 0); + + return [time, duration]; + }; + + const showTimeTooltip = (e: React.MouseEvent<HTMLDivElement>) => { + if (e.target !== progressRef.current && e.target !== timelineRef.current) { + return props.tooltipVisible && hideTimeTooltip(); + } + + const [time, duration] = getLiveTime(e); + const timeLineTooltip = { + time: Duration.fromMillis(duration - time).toFormat(`-mm:ss`), + offset: e.nativeEvent.offsetX, + isVisible: true, + }; + + debouncedTooltipChange(timeLineTooltip); + } + + const hideTimeTooltip = () => { + const timeLineTooltip = { isVisible: false }; + debouncedTooltipChange(timeLineTooltip); + }; + + const seekProgress = (e: React.MouseEvent<HTMLDivElement>) => { + const time = getTime(e); + player.jump(time); + hideTimeTooltip(); + }; + + const loadAndSeek = async (e: React.MouseEvent<HTMLDivElement>) => { + e.persist(); + const result = await player.toggleTimetravel(); + if (result) { + seekProgress(e); + } + }; + + const jumpToTime = (e: React.MouseEvent<HTMLDivElement>) => { + if (!liveTimeTravel) { + void loadAndSeek(e); + } else { + seekProgress(e); + } + }; + + const getTime = (e: React.MouseEvent<HTMLDivElement>, customEndTime?: number) => { + // @ts-ignore type mismatch from react? + const p = e.nativeEvent.offsetX / e.target.offsetWidth; + const targetTime = customEndTime || endTime; + + return Math.max(Math.round(p * targetTime), 0); + }; + + return ( + <div + className="flex items-center absolute w-full" + style={{ + top: '-4px', + zIndex: 100, + maxWidth: 'calc(100% - 1rem)', + left: '0.5rem', + }} + > + <div + className={stl.progress} + onClick={ready ? jumpToTime : undefined } + ref={progressRef} + role="button" + onMouseMoveCapture={showTimeTooltip} + onMouseEnter={showTimeTooltip} + onMouseLeave={hideTimeTooltip} + > + <TooltipContainer live /> + <DraggableCircle + left={time * scale} + onDrop={onDragEnd} + live + /> + <CustomDragLayer + onDrag={onDrag} + minX={0} + maxX={progressRef.current ? progressRef.current.offsetWidth : 0} + /> + <TimeTracker scale={scale} live left={time * scale} /> + + + <div className={stl.timeline} ref={timelineRef} /> + </div> + </div> + ) +} + +export default connect( + (state: any) => ({ + startedAt: state.getIn(['sessions', 'current']).startedAt || 0, + tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']), + }), + { setTimelinePointer, setTimelineHoverTime } +)(observer(Timeline)) diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Event.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Event.js new file mode 100644 index 000000000..e8f985aa0 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Event.js @@ -0,0 +1,174 @@ +import React from 'react'; +import copy from 'copy-to-clipboard'; +import cn from 'classnames'; +import { Icon, TextEllipsis } from 'UI'; +import { TYPES } from 'Types/session/event'; +import { prorata } from 'App/utils'; +import withOverlay from 'Components/hocs/withOverlay'; +import LoadInfo from './LoadInfo'; +import cls from './event.module.css'; +import { numberWithCommas } from 'App/utils'; + +@withOverlay() +export default class Event extends React.PureComponent { + state = { + menuOpen: false, + } + + componentDidMount() { + this.wrapper.addEventListener('contextmenu', this.onContextMenu); + } + + onContextMenu = (e) => { + e.preventDefault(); + this.setState({ menuOpen: true }); + } + onMouseLeave = () => this.setState({ menuOpen: false }) + + copyHandler = (e) => { + e.stopPropagation(); + //const ctrlOrCommandPressed = e.ctrlKey || e.metaKey; + //if (ctrlOrCommandPressed && e.keyCode === 67) { + const { event } = this.props; + copy(event.getIn([ 'target', 'path' ]) || event.url || ''); + this.setState({ menuOpen: false }); + } + + toggleInfo = (e) => { + e.stopPropagation(); + this.props.toggleInfo(); + } + + // eslint-disable-next-line complexity + renderBody = () => { + const { event } = this.props; + let title = event.type; + let body; + switch (event.type) { + case TYPES.LOCATION: + title = 'Visited'; + body = event.url; + break; + case TYPES.CLICK: + title = 'Clicked'; + body = event.label; + break; + case TYPES.INPUT: + title = 'Input'; + body = event.value; + break; + case TYPES.CLICKRAGE: + title = `${ event.count } Clicks`; + body = event.label; + break; + case TYPES.IOS_VIEW: + title = 'View'; + body = event.name; + break; + } + const isLocation = event.type === TYPES.LOCATION; + const isClickrage = event.type === TYPES.CLICKRAGE; + + return ( + <div className={ cn(cls.main, 'flex flex-col w-full') } > + <div className="flex items-center w-full"> + { event.type && <Icon name={`event/${event.type.toLowerCase()}`} size="16" color={isClickrage? 'red' : 'gray-dark' } /> } + <div className="ml-3 w-full"> + <div className="flex w-full items-first justify-between"> + <div className="flex items-center w-full" style={{ minWidth: '0'}}> + <span className={cls.title}>{ title }</span> + {/* { body && !isLocation && <div className={ cls.data }>{ body }</div> } */} + { body && !isLocation && + <TextEllipsis maxWidth="60%" className="w-full ml-2 text-sm color-gray-medium" text={body} /> + } + </div> + { isLocation && event.speedIndex != null && + <div className="color-gray-medium flex font-medium items-center leading-none justify-end"> + <div className="font-size-10 pr-2">{"Speed Index"}</div> + <div>{ numberWithCommas(event.speedIndex || 0) }</div> + </div> + } + </div> + { event.target && event.target.label && + <div className={ cls.badge } >{ event.target.label }</div> + } + </div> + </div> + { isLocation && + <div className="mt-1"> + <span className="text-sm font-normal color-gray-medium">{ body }</span> + </div> + } + </div> + ); + }; + + render() { + const { + event, + selected, + isCurrent, + onClick, + showSelection, + onCheckboxClick, + showLoadInfo, + toggleLoadInfo, + isRed, + extended, + highlight = false, + presentInSearch = false, + isLastInGroup, + whiteBg, + } = this.props; + const { menuOpen } = this.state; + return ( + <div + ref={ ref => { this.wrapper = ref } } + onMouseLeave={ this.onMouseLeave } + data-openreplay-label="Event" + data-type={event.type} + className={ cn(cls.event, { + [ cls.menuClosed ]: !menuOpen, + [ cls.highlighted ]: showSelection ? selected : isCurrent, + [ cls.selected ]: selected, + [ cls.showSelection ]: showSelection, + [ cls.red ]: isRed, + [ cls.clickType ]: event.type === TYPES.CLICK, + [ cls.inputType ]: event.type === TYPES.INPUT, + [ cls.clickrageType ]: event.type === TYPES.CLICKRAGE, + [ cls.highlight ] : presentInSearch, + [ cls.lastInGroup ]: whiteBg, + }) } + onClick={ onClick } + > + { menuOpen && + <button onClick={ this.copyHandler } className={ cls.contextMenu }> + { event.target ? 'Copy CSS' : 'Copy URL' } + </button> + } + <div className={ cls.topBlock }> + <div className={ cls.firstLine }> + { this.renderBody() } + </div> + {/* { event.type === TYPES.LOCATION && + <div className="text-sm font-normal color-gray-medium">{event.url}</div> + } */} + </div> + { event.type === TYPES.LOCATION && (event.fcpTime || event.visuallyComplete || event.timeToInteractive) && + <LoadInfo + showInfo={ showLoadInfo } + onClick={ toggleLoadInfo } + event={ event } + prorata={ prorata({ + parts: 100, + elements: { a: event.fcpTime, b: event.visuallyComplete, c: event.timeToInteractive }, + startDivisorFn: elements => elements / 1.2, + // eslint-disable-next-line no-mixed-operators + divisorFn: (elements, parts) => elements / (2 * parts + 1), + }) } + /> + } + </div> + ); + } +} diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventGroupWrapper.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventGroupWrapper.js new file mode 100644 index 000000000..924be9f2c --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventGroupWrapper.js @@ -0,0 +1,130 @@ +import React from 'react'; +import cn from 'classnames'; +import { connect } from 'react-redux' +import { TextEllipsis } from 'UI'; +import withToggle from 'HOCs/withToggle'; +import { TYPES } from 'Types/session/event'; +import Event from './Event' +import stl from './eventGroupWrapper.module.css'; +import NoteEvent from './NoteEvent'; +import { setEditNoteTooltip } from 'Duck/sessions';; + +// TODO: incapsulate toggler in LocationEvent +@withToggle('showLoadInfo', 'toggleLoadInfo') +@connect( + (state) => ({ + members: state.getIn(['members', 'list']), + currentUserId: state.getIn(['user', 'account', 'id']), + }), + { setEditNoteTooltip } +) +class EventGroupWrapper extends React.Component { + toggleLoadInfo = (e) => { + e.stopPropagation(); + this.props.toggleLoadInfo(); + }; + + componentDidUpdate(prevProps) { + if ( + prevProps.showLoadInfo !== this.props.showLoadInfo || + prevProps.query !== this.props.query || + prevProps.event.timestamp !== this.props.event.timestamp || + prevProps.isNote !== this.props.isNote + ) { + this.props.mesureHeight(); + } + } + componentDidMount() { + this.props.toggleLoadInfo(this.props.isFirst); + this.props.mesureHeight(); + } + + onEventClick = (e) => this.props.onEventClick(e, this.props.event); + + onCheckboxClick = (e) => this.props.onCheckboxClick(e, this.props.event); + + render() { + const { + event, + isLastEvent, + isLastInGroup, + isSelected, + isCurrent, + isEditing, + showSelection, + showLoadInfo, + isFirst, + presentInSearch, + isNote, + filterOutNote, + } = this.props; + const isLocation = event.type === TYPES.LOCATION; + + const whiteBg = + (isLastInGroup && event.type !== TYPES.LOCATION) || + (!isLastEvent && event.type !== TYPES.LOCATION); + const safeRef = String(event.referrer || ''); + + return ( + <div + className={cn( + stl.container, + '!py-1', + { + [stl.last]: isLastInGroup, + [stl.first]: event.type === TYPES.LOCATION, + [stl.dashAfter]: isLastInGroup && !isLastEvent, + }, + isLastInGroup && '!pb-2', + event.type === TYPES.LOCATION && '!pt-2 !pb-2' + )} + > + {isFirst && isLocation && event.referrer && ( + <div className={stl.referrer}> + <TextEllipsis> + Referrer: <span className={stl.url}>{safeRef}</span> + </TextEllipsis> + </div> + )} + {isNote ? ( + <NoteEvent + note={event} + filterOutNote={filterOutNote} + onEdit={this.props.setEditNoteTooltip} + noEdit={this.props.currentUserId !== event.userId} + /> + ) : isLocation ? ( + <Event + extended={isFirst} + key={event.key} + event={event} + onClick={this.onEventClick} + selected={isSelected} + showLoadInfo={showLoadInfo} + toggleLoadInfo={this.toggleLoadInfo} + isCurrent={isCurrent} + presentInSearch={presentInSearch} + isLastInGroup={isLastInGroup} + whiteBg={whiteBg} + /> + ) : ( + <Event + key={event.key} + event={event} + onClick={this.onEventClick} + onCheckboxClick={this.onCheckboxClick} + selected={isSelected} + isCurrent={isCurrent} + showSelection={showSelection} + overlayed={isEditing} + presentInSearch={presentInSearch} + isLastInGroup={isLastInGroup} + whiteBg={whiteBg} + /> + )} + </div> + ); + } +} + +export default EventGroupWrapper diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventSearch/EventSearch.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventSearch/EventSearch.js new file mode 100644 index 000000000..419434d22 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventSearch/EventSearch.js @@ -0,0 +1,43 @@ +import React from 'react' +import { Input, Icon } from 'UI' +import { PlayerContext } from 'App/components/Session/playerContext'; + +function EventSearch(props) { + const { player } = React.useContext(PlayerContext) + + const { onChange, value, header, setActiveTab } = props; + + const toggleEvents = () => player.toggleEvents() + + return ( + <div className="flex items-center w-full relative"> + <div className="flex flex-1 flex-col"> + <div className='flex flex-center justify-between'> + <span>{header}</span> + <div + onClick={() => { setActiveTab(''); toggleEvents(); }} + className=" flex items-center justify-center bg-white cursor-pointer" + > + <Icon name="close" size="18" /> + </div> + </div> + <div className="flex items-center mt-2"> + <Input + autoFocus + type="text" + placeholder="Filter by Event Type, URL or Keyword" + className="inset-0 w-full" + name="query" + value={value} + onChange={onChange} + wrapperClassName="w-full" + style={{ height: '32px' }} + autoComplete="off chromebugfix" + /> + </div> + </div> + </div> + ) +} + +export default EventSearch diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventSearch/index.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventSearch/index.js new file mode 100644 index 000000000..8be1f4ddc --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventSearch/index.js @@ -0,0 +1 @@ +export { default } from './EventSearch' \ No newline at end of file diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventsBlock.tsx b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventsBlock.tsx new file mode 100644 index 000000000..2b59dd18f --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/EventsBlock.tsx @@ -0,0 +1,192 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import cn from 'classnames'; +import { Icon } from 'UI'; +import { List, AutoSizer, CellMeasurer } from "react-virtualized"; +import { TYPES } from 'Types/session/event'; +import { setEventFilter, filterOutNote } from 'Duck/sessions'; +import EventGroupWrapper from './EventGroupWrapper'; +import styles from './eventsBlock.module.css'; +import EventSearch from './EventSearch/EventSearch'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { RootStore } from 'App/duck' +import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache' +import { InjectedEvent } from 'Types/session/event' +import Session from 'Types/session' + +interface IProps { + setEventFilter: (filter: { query: string }) => void + filteredEvents: InjectedEvent[] + setActiveTab: (tab?: string) => void + query: string + events: Session['events'] + notesWithEvents: Session['notesWithEvents'] + filterOutNote: (id: string) => void + eventsIndex: number[] +} + +function EventsBlock(props: IProps) { + const [mouseOver, setMouseOver] = React.useState(true) + const scroller = React.useRef<List>(null) + const cache = useCellMeasurerCache(undefined, { + fixedWidth: true, + defaultHeight: 300 + }); + + const { store, player } = React.useContext(PlayerContext) + + const { eventListNow, playing } = store.get() + + const { + filteredEvents, + eventsIndex, + filterOutNote, + query, + setActiveTab, + events, + notesWithEvents, + } = props + + const currentTimeEventIndex = eventListNow.length > 0 ? eventListNow.length - 1 : 0 + const usedEvents = filteredEvents || notesWithEvents + + const write = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => { + props.setEventFilter({ query: value }) + + setTimeout(() => { + if (!scroller.current) return; + + scroller.current.scrollToRow(0); + }, 100) + } + + const clearSearch = () => { + props.setEventFilter({ query: '' }) + if (scroller.current) { + scroller.current.forceUpdateGrid(); + } + + setTimeout(() => { + if (!scroller.current) return; + + scroller.current.scrollToRow(0); + }, 100) + } + + React.useEffect(() => { + return () => { + clearSearch() + } + }, []) + React.useEffect(() => { + if (scroller.current) { + scroller.current.forceUpdateGrid(); + if (!mouseOver) { + scroller.current.scrollToRow(currentTimeEventIndex); + } + } + }, [currentTimeEventIndex]) + + const onEventClick = (_: React.MouseEvent, event: { time: number }) => player.jump(event.time) + const onMouseOver = () => setMouseOver(true) + const onMouseLeave = () => setMouseOver(false) + + const renderGroup = ({ index, key, style, parent }: { index: number; key: string; style: React.CSSProperties; parent: any }) => { + const isLastEvent = index === usedEvents.length - 1; + const isLastInGroup = isLastEvent || usedEvents[index + 1]?.type === TYPES.LOCATION; + const event = usedEvents[index]; + const isNote = 'noteId' in event + const isCurrent = index === currentTimeEventIndex; + + const heightBug = index === 0 && event?.type === TYPES.LOCATION && 'referrer' in event ? { top: 2 } : {} + return ( + <CellMeasurer + key={key} + cache={cache} + parent={parent} + rowIndex={index} + > + {({measure, registerChild}) => ( + <div style={{ ...style, ...heightBug }} ref={registerChild}> + <EventGroupWrapper + query={query} + presentInSearch={eventsIndex.includes(index)} + isFirst={index==0} + mesureHeight={measure} + onEventClick={ onEventClick } + event={ event } + isLastEvent={ isLastEvent } + isLastInGroup={ isLastInGroup } + isCurrent={ isCurrent } + showSelection={ !playing } + isNote={isNote} + filterOutNote={filterOutNote} + /> + </div> + )} + </CellMeasurer> + ); + } + + const isEmptySearch = query && (usedEvents.length === 0 || !usedEvents) + return ( + <> + <div className={ cn(styles.header, 'p-4') }> + <div className={ cn(styles.hAndProgress, 'mt-3') }> + <EventSearch + onChange={write} + setActiveTab={setActiveTab} + value={query} + header={ + <div className="text-xl">User Steps <span className="color-gray-medium">{ events.length }</span></div> + } + /> + </div> + </div> + <div + className={ cn("flex-1 px-4 pb-4", styles.eventsList) } + id="eventList" + data-openreplay-masked + onMouseOver={ onMouseOver } + onMouseLeave={ onMouseLeave } + > + {isEmptySearch && ( + <div className='flex items-center'> + <Icon name="binoculars" size={18} /> + <span className='ml-2'>No Matching Results</span> + </div> + )} + <AutoSizer disableWidth> + {({ height }) => ( + <List + ref={scroller} + className={ styles.eventsList } + height={height + 10} + width={248} + overscanRowCount={6} + itemSize={230} + rowCount={usedEvents.length} + deferredMeasurementCache={cache} + rowHeight={cache.rowHeight} + rowRenderer={renderGroup} + scrollToAlignment="start" + /> + )} + </AutoSizer> + </div> + </> + ); +} + +export default connect((state: RootStore) => ({ + session: state.getIn([ 'sessions', 'current' ]), + notesWithEvents: state.getIn([ 'sessions', 'current' ]).notesWithEvents, + events: state.getIn([ 'sessions', 'current' ]).events, + filteredEvents: state.getIn([ 'sessions', 'filteredEvents' ]), + query: state.getIn(['sessions', 'eventsQuery']), + eventsIndex: state.getIn([ 'sessions', 'eventsIndex' ]), +}), { + setEventFilter, + filterOutNote +})(observer(EventsBlock)) diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/LoadInfo.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/LoadInfo.js new file mode 100644 index 000000000..664caeb9b --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/LoadInfo.js @@ -0,0 +1,40 @@ +import React from 'react'; +import styles from './loadInfo.module.css'; +import { numberWithCommas } from 'App/utils' + +const LoadInfo = ({ showInfo = false, onClick, event: { fcpTime, visuallyComplete, timeToInteractive }, prorata: { a, b, c } }) => ( + <div> + <div className={ styles.bar } onClick={ onClick }> + { typeof fcpTime === 'number' && <div style={ { width: `${ a }%` } } /> } + { typeof visuallyComplete === 'number' && <div style={ { width: `${ b }%` } } /> } + { typeof timeToInteractive === 'number' && <div style={ { width: `${ c }%` } } /> } + </div> + <div className={ styles.bottomBlock } data-hidden={ !showInfo }> + { typeof fcpTime === 'number' && + <div className={ styles.wrapper }> + <div className={ styles.lines } /> + <div className={ styles.label } >{ 'Time to Render' }</div> + <div className={ styles.value }>{ `${ numberWithCommas(fcpTime || 0) }ms` }</div> + </div> + } + { typeof visuallyComplete === 'number' && + <div className={ styles.wrapper }> + <div className={ styles.lines } /> + <div className={ styles.label } >{ 'Visually Complete' }</div> + <div className={ styles.value }>{ `${ numberWithCommas(visuallyComplete || 0) }ms` }</div> + </div> + } + { typeof timeToInteractive === 'number' && + <div className={ styles.wrapper }> + <div className={ styles.lines } /> + <div className={ styles.label } >{ 'Time To Interactive' }</div> + <div className={ styles.value }>{ `${ numberWithCommas(timeToInteractive || 0) }ms` }</div> + </div> + } + </div> + </div> +); + +LoadInfo.displayName = 'LoadInfo'; + +export default LoadInfo; diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/Metadata.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/Metadata.js new file mode 100644 index 000000000..ca0a953a2 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/Metadata.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import MetadataItem from './MetadataItem'; + +export default connect(state => ({ + metadata: state.getIn([ 'sessions', 'current' ]).metadata, +}))(function Metadata ({ metadata }) { + + const metaLenth = Object.keys(metadata).length; + + if (metaLenth === 0) { + return ( + (<span className="text-sm color-gray-medium">Check <a href="https://docs.openreplay.com/installation/metadata" target="_blank" className="link">how to use Metadata</a> if you haven’t yet done so.</span>) + ) + } + return ( + <div> + { Object.keys(metadata).map((key) => { + // const key = Object.keys(i)[0] + const value = metadata[key] + return <MetadataItem item={ { value, key } } key={ key } /> + }) } + </div> + ); +}); diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/MetadataItem.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/MetadataItem.js new file mode 100644 index 000000000..47a785c5a --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/MetadataItem.js @@ -0,0 +1,75 @@ +import React from 'react'; +import { List } from 'immutable'; +import cn from 'classnames'; +import { withRequest, withToggle } from 'HOCs'; +import { Button, Icon, SlideModal, TextEllipsis } from 'UI'; +import stl from './metadataItem.module.css'; +import SessionList from './SessionList'; + +@withToggle() +@withRequest({ + initialData: List(), + endpoint: '/metadata/session_search', + dataWrapper: data => Object.values(data), + dataName: 'similarSessions', +}) +export default class extends React.PureComponent { + state = { + requested: false, + } + switchOpen = () => { + const { + item: { + key, value + }, + request, + switchOpen, + } = this.props; + + const { requested } = this.state; + if (!requested) { + this.setState({ requested: true }); + request({ key, value }); + } + switchOpen(); + } + + render() { + const { + item, + similarSessions, + open, + loading, + } = this.props; + + return ( + <div> + <SlideModal + title={ <div className={ stl.searchResultsHeader }>{ `All Sessions Matching - ` } <span>{ item.key + ' - ' + item.value }</span> </div> } + isDisplayed={ open } + content={ open && <SessionList similarSessions={ similarSessions } loading={ loading } /> } + onClose={ open ? this.switchOpen : () => null } + /> + <div className={ cn("flex justify-between items-center p-3 capitalize", stl.field) } > + <div> + <div className={ stl.key }>{ item.key }</div> + <TextEllipsis + maxWidth="210px" + popupProps={ { disabled: item.value && item.value.length < 30 } } + > + { item.value } + </TextEllipsis> + </div> + <Button + onClick={ this.switchOpen } + variant="text" + className={ stl.searchButton } + id="metadata-item" + > + <Icon name="search" size="16" color="teal" /> + </Button> + </div> + </div> + ); + } +} \ No newline at end of file diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/SessionLine.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/SessionLine.js new file mode 100644 index 000000000..729271ece --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/SessionLine.js @@ -0,0 +1,40 @@ +import { BrowserIcon, OsIcon, Icon, CountryFlag, Link } from 'UI'; +import { deviceTypeIcon } from 'App/iconNames'; +import { session as sessionRoute } from 'App/routes'; +import { formatTimeOrDate } from 'App/date'; + + +const SessionLine = ({ session: { + userBrowser, + userOs, + userCountry, + siteId, + sessionId, + viewed, + userDeviceType, + startedAt + } }) => ( + <div className="flex justify-between items-center" style={{ padding: '5px 20px' }}> + <div className="color-gray-medium font-size-10" > + <CountryFlag country={ userCountry } className="mr-4" /> + { formatTimeOrDate(startedAt) } + </div> + <div className="flex"> + <BrowserIcon browser={ userBrowser } className="mr-4" /> + <OsIcon os={ userOs } size="20" className="mr-4" /> + <Icon name={ deviceTypeIcon(userDeviceType) } size="20" className="mr-4" /> + </div> + <Link to={ sessionRoute(sessionId) } siteId={ siteId } > + <Icon + name={ viewed ? 'play-fill' : 'play-circle-light' } + size="20" + color="teal" + /> + </Link> + </div> +); + + +SessionLine.displayName = "SessionLine"; + +export default SessionLine; diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/SessionList.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/SessionList.js new file mode 100644 index 000000000..92ac93432 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/SessionList.js @@ -0,0 +1,60 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { NoContent, Icon, Loader } from 'UI'; +import Session from 'Types/session'; +import SessionItem from 'Shared/SessionItem'; +import stl from './sessionList.module.css'; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; + +@connect((state) => ({ + currentSessionId: state.getIn(['sessions', 'current']).sessionId, +})) +class SessionList extends React.PureComponent { + render() { + const { similarSessions, loading, currentSessionId } = this.props; + + const similarSessionWithoutCurrent = similarSessions + .map(({ sessions, ...rest }) => { + return { + ...rest, + sessions: sessions.map(s => new Session(s)).filter(({ sessionId }) => sessionId !== currentSessionId), + }; + }) + .filter((site) => site.sessions.length > 0); + + return ( + <Loader loading={loading}> + <NoContent + show={!loading && (similarSessionWithoutCurrent.length === 0 || similarSessionWithoutCurrent.size === 0)} + title={ + <div className="flex items-center justify-center flex-col"> + <AnimatedSVG name={ICONS.NO_SESSIONS} size={170} /> + <div className="mt-2" /> + <div className="text-center text-gray-600">No sessions found.</div> + </div> + } + > + <div className={stl.sessionList}> + {similarSessionWithoutCurrent.map((site) => ( + <div className={stl.siteWrapper} key={site.host}> + <div className={stl.siteHeader}> + <Icon name="window" size="14" color="gray-medium" marginRight="10" /> + <span>{site.name}</span> + </div> + <div className="bg-white p-3 rounded border"> + {site.sessions.map((session) => ( + <div className="border-b last:border-none"> + <SessionItem key={session.sessionId} session={session} /> + </div> + ))} + </div> + </div> + ))} + </div> + </NoContent> + </Loader> + ); + } +} + +export default SessionList; diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/index.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/index.js new file mode 100644 index 000000000..17932470b --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/index.js @@ -0,0 +1 @@ +export { default } from './Metadata'; \ No newline at end of file diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/metadata.module.css b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/metadata.module.css new file mode 100644 index 000000000..268e04ba7 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/metadata.module.css @@ -0,0 +1,51 @@ + +@import 'mixins.css'; + +/* .wrapper { + position: relative; +} */ + +.modal { + /* width: 288px; */ + /* position: absolute; */ + /* top: 50px; */ + /* right: 0; */ + /* background-color: white; */ + /* border-radius: 3px; */ + /* z-index: 99; */ + /* padding: 10px; */ + /* min-height: 90px; */ + max-height: 300px; + overflow: auto; + /* @mixin shadow; */ + /* border: solid thin $gray-light; */ + + /* & .tooltipArrow { + width: 50px; + height: 25px; + position: absolute; + bottom: 100%; + right: 0; + transform: translateX(-50%); + overflow: hidden; + + &::after { + content: ""; + position: absolute; + width: 16px; + height: 16px; + background: white; + transform: translateX(-50%) translateY(50%) rotate(45deg); + bottom: 0; + left: 50%; + box-shadow: 2px 2px 6px 0px rgba(0,0,0,0.6); + } + } */ +} + +.header { + font-size: 18px; + font-weight: 500; + /* padding: 10px 20px; */ + border-bottom: solid thin $gray-light; +} diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/metadataItem.module.css b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/metadataItem.module.css new file mode 100644 index 000000000..2dc522306 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/metadataItem.module.css @@ -0,0 +1,37 @@ + + +.field { + &:not(:last-child) { + border-bottom: solid thin $gray-light-shade; + } + /* padding: 10px 20px; */ +} + +.key { + color: $gray-medium; + font-weight: 500; +} + +.searchResultsHeader { + & span { + padding: 4px 8px; + font-size: 18px; + background-color: $gray-lightest; + border: solid thin $gray-light; + margin-left: 10px; + border-radius: 3px; + } +} + +.searchButton { + border-radius: 3px; + height: 30px !important; + width: 30px !important; + display: flex !important; + align-items: center !important; + padding: 0 !important; + justify-content: center !important; + &:hover { + background-color: $gray-lightest !important; + } +} diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/sessionList.module.css b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/sessionList.module.css new file mode 100644 index 000000000..0c63d2ae0 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/Metadata/sessionList.module.css @@ -0,0 +1,26 @@ + + +.sessionList { + padding: 0 20px; + background-color: #f6f6f6; + min-height: calc(100vh - 59px); +} + +.siteWrapper { + padding-top: 10px; + margin-bottom: 10px; +} + +.siteHeader { + display: flex; + align-items: center; + margin-bottom: 15px; + font-weight: 400; + font-size: 14px; + background-color: white; + border-top: solid thin $gray-lightest; + margin: -15px; + margin-top: -10px; + margin-bottom: 20px; + padding: 10px; +} \ No newline at end of file diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/NoteEvent.tsx b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/NoteEvent.tsx new file mode 100644 index 000000000..a09869ba5 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/NoteEvent.tsx @@ -0,0 +1,127 @@ +import React from 'react'; +import { Icon } from 'UI'; +import { tagProps, Note } from 'App/services/NotesService'; +import { formatTimeOrDate } from 'App/date'; +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite'; +import { ItemMenu } from 'UI'; +import copy from 'copy-to-clipboard'; +import { toast } from 'react-toastify'; +import { session } from 'App/routes'; +import { confirm } from 'UI'; +import { TeamBadge } from 'Shared/SessionListContainer/components/Notes'; + +interface Props { + note: Note; + noEdit: boolean; + filterOutNote: (id: number) => void; + onEdit: (noteTooltipObj: Record<string, any>) => void; +} + +function NoteEvent(props: Props) { + const { settingsStore, notesStore } = useStore(); + const { timezone } = settingsStore.sessionSettings; + + const onEdit = () => { + props.onEdit({ + isVisible: true, + isEdit: true, + time: props.note.timestamp, + note: { + timestamp: props.note.timestamp, + tag: props.note.tag, + isPublic: props.note.isPublic, + message: props.note.message, + sessionId: props.note.sessionId, + noteId: props.note.noteId, + }, + }); + }; + + const onCopy = () => { + copy( + `${window.location.origin}/${window.location.pathname.split('/')[1]}${session( + props.note.sessionId + )}${props.note.timestamp > 0 ? `?jumpto=${props.note.timestamp}¬e=${props.note.noteId}` : `?note=${props.note.noteId}`}` + ); + toast.success('Note URL copied to clipboard'); + }; + + const onDelete = async () => { + if ( + await confirm({ + header: 'Confirm', + confirmButton: 'Yes, delete', + confirmation: `Are you sure you want to delete this note?`, + }) + ) { + notesStore.deleteNote(props.note.noteId).then((r) => { + props.filterOutNote(props.note.noteId); + toast.success('Note deleted'); + }); + } + }; + const menuItems = [ + { icon: 'pencil', text: 'Edit', onClick: onEdit, disabled: props.noEdit }, + { icon: 'link-45deg', text: 'Copy URL', onClick: onCopy }, + { icon: 'trash', text: 'Delete', onClick: onDelete }, + ]; + return ( + <div + className="flex items-start flex-col p-2 border rounded" + style={{ background: '#FFFEF5' }} + > + <div className="flex items-center w-full relative"> + <div className="p-3 bg-gray-light rounded-full"> + <Icon name="quotes" color="main" /> + </div> + <div className="ml-2"> + <div + className="text-base" + style={{ + maxWidth: 150, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }} + > + {props.note.userName} + </div> + <div className="text-disabled-text text-sm"> + {formatTimeOrDate(props.note.createdAt as unknown as number, timezone)} + </div> + </div> + <div className="cursor-pointer absolute" style={{ right: -5 }}> + <ItemMenu bold items={menuItems} /> + </div> + </div> + <div + className="text-base capitalize-first my-3 overflow-y-scroll overflow-x-hidden" + style={{ maxHeight: 200, maxWidth: 220 }} + > + {props.note.message} + </div> + <div> + <div className="flex items-center gap-2 flex-wrap w-full"> + {props.note.tag ? ( + <div + key={props.note.tag} + style={{ + // @ts-ignore + background: tagProps[props.note.tag], + userSelect: 'none', + padding: '1px 6px', + }} + className="rounded-full text-white text-xs select-none w-fit" + > + {props.note.tag} + </div> + ) : null} + {!props.note.isPublic ? null : <TeamBadge />} + </div> + </div> + </div> + ); +} + +export default observer(NoteEvent); diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/UserCard.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/UserCard.js new file mode 100644 index 000000000..e6893ca3e --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/UserCard.js @@ -0,0 +1,123 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { List } from 'immutable'; +import { countries } from 'App/constants'; +import { useStore } from 'App/mstore'; +import { browserIcon, osIcon, deviceTypeIcon } from 'App/iconNames'; +import { formatTimeOrDate } from 'App/date'; +import { Avatar, TextEllipsis, CountryFlag, Icon, Tooltip, Popover } from 'UI'; +import cn from 'classnames'; +import { withRequest } from 'HOCs'; +import SessionInfoItem from 'Components/Session_/SessionInfoItem'; +import { useModal } from 'App/components/Modal'; +import UserSessionsModal from 'Shared/UserSessionsModal'; + +function UserCard({ className, request, session, width, height, similarSessions, loading }) { + const { settingsStore } = useStore(); + const { timezone } = settingsStore.sessionSettings; + + const { + userBrowser, + userDevice, + userCountry, + userBrowserVersion, + userOs, + userOsVersion, + startedAt, + userId, + userAnonymousId, + userNumericHash, + userDisplayName, + userDeviceType, + revId, + } = session; + + const hasUserDetails = !!userId || !!userAnonymousId; + + const getDimension = (width, height) => { + return width && height ? ( + <div className="flex items-center"> + {width || 'x'} <Icon name="close" size="12" className="mx-1" /> {height || 'x'} + </div> + ) : ( + <span className="">Resolution N/A</span> + ); + }; + + const avatarbgSize = '38px'; + return ( + <div className={cn('bg-white flex items-center w-full', className)}> + <div className="flex items-center"> + <Avatar iconSize="23" width={avatarbgSize} height={avatarbgSize} seed={userNumericHash} /> + <div className="ml-3 overflow-hidden leading-tight"> + <TextEllipsis + noHint + className={cn('font-medium', { 'color-teal cursor-pointer': hasUserDetails })} + // onClick={hasUserDetails ? showSimilarSessions : undefined} + > + <UserName name={userDisplayName} userId={userId} hash={userNumericHash} /> + </TextEllipsis> + + <div className="text-sm color-gray-medium flex items-center"> + <span style={{ whiteSpace: 'nowrap' }}> + <Tooltip + title={`${formatTimeOrDate(startedAt, timezone, true)} ${timezone.label}`} + className="w-fit !block" + > + {formatTimeOrDate(startedAt, timezone)} + </Tooltip> + + </span> + <span className="mx-1 font-bold text-xl">·</span> + <span>{countries[userCountry]}</span> + <span className="mx-1 font-bold text-xl">·</span> + <span className="capitalize"> + {userBrowser}, {userOs}, {userDevice} + </span> + <span className="mx-1 font-bold text-xl">·</span> + <Popover + render={() => ( + <div className="text-left bg-white"> + <SessionInfoItem + comp={<CountryFlag country={userCountry} />} + label={countries[userCountry]} + value={<span style={{ whiteSpace: 'nowrap' }}>{formatTimeOrDate(startedAt)}</span>} + /> + <SessionInfoItem icon={browserIcon(userBrowser)} label={userBrowser} value={`v${userBrowserVersion}`} /> + <SessionInfoItem icon={osIcon(userOs)} label={userOs} value={userOsVersion} /> + <SessionInfoItem + icon={deviceTypeIcon(userDeviceType)} + label={userDeviceType} + value={getDimension(width, height)} + isLast={!revId} + /> + {revId && <SessionInfoItem icon="info" label="Rev ID:" value={revId} isLast />} + </div> + )} + > + <span className="link">More</span> + </Popover> + </div> + </div> + </div> + </div> + ); +} + +const component = React.memo(connect((state) => ({ session: state.getIn(['sessions', 'current']) }))(UserCard)); + +export default withRequest({ + initialData: List(), + endpoint: '/metadata/session_search', + dataWrapper: (data) => Object.values(data), + dataName: 'similarSessions', +})(component); + +// inner component +function UserName({ name, userId, hash }) { + const { showModal } = useModal(); + const onClick = () => { + showModal(<UserSessionsModal userId={userId} hash={hash} name={name} />, { right: true, width: 700 }); + }; + return <div onClick={userId ? onClick : () => {}}>{name}</div>; +} diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/index.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/index.js new file mode 100644 index 000000000..30f2f5b8a --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/UserCard/index.js @@ -0,0 +1 @@ +export { default } from './UserCard'; \ No newline at end of file diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/event.module.css b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/event.module.css new file mode 100644 index 000000000..8afea78f2 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/event.module.css @@ -0,0 +1,165 @@ +.contextMenu { + position: absolute; + top: 27px; + right: 15px; + padding: 2px 3px; + background: $white; + border: 1px solid $gray-light; + border-radius: 3px; + cursor: pointer; + color: $gray-medium; + font-size: 12px; + z-index: 2; +} + +.event { + position: relative; + background: #f6f6f6; + border-radius: 3px; + user-select: none; + /* box-shadow: 0px 1px 3px 0 $gray-light; */ + transition: all 0.2s; + cursor: pointer; + border: 1px solid transparent; + &:hover { + background-color: $active-blue; + border: 1px solid $active-blue-border; + } + + & .title { + font-size: 13px; + } + + & .topBlock { + min-height: 30px; + position: relative; + padding: 8px 10px; + } + + & .checkbox { + position: absolute; + left: 10px; + top: 8px; + bottom: 0; + /* margin: auto; */ + display: none; + /* align-items: center; */ + } + + &.menuClosed:hover { + & .edit { + opacity: 1; + transition: all 0.2s; + } + } + + &.menuClosed.showSelection { + &:hover, &.selected { + background-color: #EFFCFB; + + & .checkbox { + display: flex; + } + + & .icon { + opacity: 0; + } + } + } + + &.highlighted { + transition: all 0.2s; + box-shadow: 0px 2px 10px 0 $gray-light; + border: 1px solid $active-blue-border; + /* background-color: red; */ + } + + &.red { + border-color: $red; + } +} + +.firstLine { + display: flex; + justify-content: space-between; + align-items: center; +} + +.main { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: flex; + align-items: flex-start; +} + + +.type { + color: $gray-dark; + font-size: 12px; + text-transform: capitalize; + font-weight: bold; +} + +.data { + margin-left: 5px; + color: $gray-medium; + font-size: 12px; + max-width: 100%; + /* overflow: hidden; */ + /* text-overflow: ellipsis; */ +} + +.badge { + display: inline-block; + padding: 0; + border-radius: 3px; + font-size: 9px; + /* margin-left: 28px; */ + max-width: 170px; + word-wrap: break-word; + line-height: normal; + color: #999; + text-transform: none; +} + +.icon { + margin-right: 10px; + display: inline-flex; + align-items: center; + justify-content: center; + & i { + width: 18px; + height: 18px; + } +} + + +.clickType, .inputType { + /* border: 1px solid $gray-light; */ + background-color: $gray-lightest; + cursor: pointer; +} + +.clickrageType { + background-color: #FFF3F3; + border: 1px solid #CC0000; + box-shadow: + /* The top layer shadow */ + /* 0 1px 1px rgba(0,0,0,0.15), */ + /* The second layer */ + 2px 2px 1px 1px white, + /* The second layer shadow */ + 2px 2px 0px 1px rgba(0,0,0,0.4); + /* Padding for demo purposes */ + /* padding: 12px; */ +} + +.highlight { + border: solid thin red; +} + +.lastInGroup { + background: white; + box-shadow: 0px 1px 1px 0px rgb(0 0 0 / 18%); +} diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/eventGroupWrapper.module.css b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/eventGroupWrapper.module.css new file mode 100644 index 000000000..f07fa4f25 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/eventGroupWrapper.module.css @@ -0,0 +1,36 @@ +.container { + padding: 0px 7px; /*0.35rem 0.5rem */ + background-color: #f6f6f6; +} + +.first { + padding-top: 7px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} + +.last { + padding-bottom: 7px; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; +} + +.dashAfter { + margin-bottom: 0.8rem; +} + +.referrer { + font-size: 14px; + color: $gray-dark; + font-weight: 500 !important; + display: flex; + align-items: center; + & .url { + margin-left: 5px; + font-weight: 300; + color: $gray-medium; + max-width: 70%; + overflow: hidden; + text-overflow: ellipsis; + } +} diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/eventsBlock.module.css b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/eventsBlock.module.css new file mode 100644 index 000000000..9efb4be93 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/eventsBlock.module.css @@ -0,0 +1,66 @@ +.eventsBlock { + width: 270px; + margin-bottom: 5px; +} + +.header { + & .hAndProgress { + display:flex; + justify-content: space-between; + align-items: center; + /* margin-bottom: 5px; */ + /* height: 40px; */ + & .progress { + flex: 1; + margin: 0 0 0 15px; + & :global(.bar) { + background: #ffcc99; + } + & :global(.progress) { + font-size: 9px; + } + } + } + + & h5 { + margin: 0; /* get rid of semantic, please*/ + font-size: 14px; + font-weight: 700; + } +} + +.eventsList { + /* box-shadow: inset 0px 2px 4px rgba(0, 0, 0, 0.1); */ + /* border-top: solid thin $gray-light-shade; */ + &::-webkit-scrollbar { + width: 2px; + background: transparent !important; + background: rgba(0,0,0,0); + } + + &::-webkit-scrollbar-thumb { + background: transparent !important; + } + &::-webkit-scrollbar-track { + background: transparent !important; + } + &:hover { + &::-webkit-scrollbar { + width: 2px; + background: rgba(0,0,0,0.1) + } + &::-webkit-scrollbar-track { + background: rgba(0,0,0,0.1) + } + &::-webkit-scrollbar-thumb { + background: rgba(0,0,0,0.1) + } + } +} + +.sessionDetails { + display: flex; + font-size: 10px; + color: $gray-medium; + justify-content: space-between; +} diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/index.js b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/index.js new file mode 100644 index 000000000..47e4d4efb --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/index.js @@ -0,0 +1 @@ +export { default } from './EventsBlock'; \ No newline at end of file diff --git a/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/loadInfo.module.css b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/loadInfo.module.css new file mode 100644 index 000000000..1e2a95927 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/EventsBlock/loadInfo.module.css @@ -0,0 +1,102 @@ + + +$green-light: #A0D6AE; +$green-middle: #859D9A; +$green-dark: #3A625E; + +.bar { + display: flex; + overflow: hidden; + cursor: pointer; + + /* margin: 0 -11px; + margin-bottom: -9px; */ + + & div { + height: 5px; + } + & div:nth-child(1) { + background-color: #C5E6E7; + } + & div:nth-child(2) { + background-color: #8BCCCF; + } + & div:nth-child(3) { + background-color :rgba(62, 170, 175, 1); + } +} + +.bottomBlock { + overflow: hidden; +} + +.wrapper { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 5px 12px 34px; + font-size: 13px; + /* font-weight: 500; */ + + & .lines { + border-bottom: 1px solid $gray-light; + border-left: 2px solid; + position: absolute; + height: 100%; + top: -21px; + left: 14px; + width: 15px; + + &:before { + content: ""; + border-radius: 5px; + border: 5px solid; + display: block; + width: 0; + height: 0; + position: absolute; + bottom: -5px; + left: -6px; + z-index: 1; /* in context */ + } + } +} + +.wrapper:nth-child(1) { + /* overflow: hidden; */ + & .lines { + border-left-color: #C5E6E7; + &:before { + border-color: #C5E6E7; + } + } +} + +.wrapper:nth-child(2) { + & .lines { + border-left-color: #8BCCCF; + &:before { + border-color: #8BCCCF; + } + } +} + +.wrapper:nth-child(3) { + & .lines { + border-left-color: rgba(62, 170, 175, 1); + &:before { + border-color: rgba(62, 170, 175, 1); + } + } +} + +.value { + font-weight: 500; + color: $gray-medium; +} + +.download { + display: flex; + justify-content: space-between; +} \ No newline at end of file diff --git a/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlock.tsx b/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlock.tsx new file mode 100644 index 000000000..3aa3f1f8e --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlock.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import cn from 'classnames'; +import { connect } from 'react-redux'; +import Player from './PlayerInst'; +import SubHeader from 'Components/Session_/Subheader'; + +import styles from 'Components/Session_/playerBlock.module.css'; + +interface IProps { + fullscreen: boolean; + sessionId: string; + disabled: boolean; + activeTab: string; + jiraConfig: Record<string, any> + fullView?: boolean +} + +function PlayerBlock(props: IProps) { + const { + fullscreen, + sessionId, + disabled, + activeTab, + jiraConfig, + fullView = false, + } = props; + + const shouldShowSubHeader = !fullscreen && !fullView + return ( + <div + className={cn(styles.playerBlock, 'flex flex-col', 'overflow-x-hidden')} + > + {shouldShowSubHeader ? ( + <SubHeader sessionId={sessionId} disabled={disabled} jiraConfig={jiraConfig} /> + ) : null} + <Player + activeTab={activeTab} + fullView={fullView} + /> + </div> + ); +} + +export default connect((state: any) => ({ + fullscreen: state.getIn(['components', 'player', 'fullscreen']), + sessionId: state.getIn(['sessions', 'current']).sessionId, + disabled: state.getIn(['components', 'targetDefiner', 'inspectorMode']), + jiraConfig: state.getIn(['issues', 'list'])[0], +}))(PlayerBlock) \ No newline at end of file diff --git a/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx b/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx new file mode 100644 index 000000000..162f92237 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/PlayerBlockHeader.tsx @@ -0,0 +1,150 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { withRouter } from 'react-router-dom'; +import { + sessions as sessionsRoute, + liveSession as liveSessionRoute, + withSiteId, +} from 'App/routes'; +import { BackLink, Link } from 'UI'; +import { toggleFavorite, setSessionPath } from 'Duck/sessions'; +import cn from 'classnames'; +import SessionMetaList from 'Shared/SessionItem/SessionMetaList'; +import UserCard from './EventsBlock/UserCard'; +import Tabs from 'Components/Session/Tabs'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import stl from './playerBlockHeader.module.css'; +import { fetchListActive as fetchMetadata } from 'Duck/customField'; + +const SESSIONS_ROUTE = sessionsRoute(); + +// TODO props +function PlayerBlockHeader(props: any) { + const [hideBack, setHideBack] = React.useState(false); + const { player, store } = React.useContext(PlayerContext); + + const { width, height, showEvents } = store.get(); + + const { + session, + fullscreen, + metaList, + closedLive = false, + siteId, + setActiveTab, + activeTab, + location, + history, + sessionPath, + fetchMetadata, + } = props; + + React.useEffect(() => { + const queryParams = new URLSearchParams(location.search); + setHideBack(queryParams.has('iframe') && queryParams.get('iframe') === 'true'); + + if (metaList.size === 0) fetchMetadata(); + }, []); + + const backHandler = () => { + if ( + sessionPath.pathname === history.location.pathname || + sessionPath.pathname.includes('/session/') + ) { + history.push(withSiteId(SESSIONS_ROUTE, siteId)); + } else { + history.push( + sessionPath ? sessionPath.pathname + sessionPath.search : withSiteId(SESSIONS_ROUTE, siteId) + ); + } + }; + + const { sessionId, live, metadata } = session; + let _metaList = Object.keys(metadata || {}) + .filter((i) => metaList.includes(i)) + .map((key) => { + const value = metadata[key]; + return { label: key, value }; + }); + + const TABS = [props.tabs.EVENTS, props.tabs.CLICKMAP].map((tab) => ({ + text: tab, + key: tab, + })); + + return ( + <div className={cn(stl.header, 'flex justify-between', { hidden: fullscreen })}> + <div className="flex w-full items-center"> + {!hideBack && ( + <div + className="flex items-center h-full cursor-pointer group" + onClick={backHandler} + > + {/* @ts-ignore TODO */} + <BackLink label="Back" className="h-full" /> + <div className={stl.divider} /> + </div> + )} + <UserCard className="" width={width} height={height} /> + + <div className={cn('ml-auto flex items-center h-full', { hidden: closedLive })}> + {live && ( + <> + <div className={cn(stl.liveSwitchButton, 'pr-4')}> + <Link to={withSiteId(liveSessionRoute(sessionId), siteId)}> + This Session is Now Continuing Live + </Link> + </div> + {_metaList.length > 0 && <div className={stl.divider} />} + </> + )} + + {_metaList.length > 0 && ( + <div className="border-l h-full flex items-center px-2"> + <SessionMetaList className="" metaList={_metaList} maxLength={2} /> + </div> + )} + </div> + </div> + <div className="relative border-l" style={{ minWidth: '270px' }}> + <Tabs + tabs={TABS} + active={activeTab} + onClick={(tab) => { + if (activeTab === tab) { + setActiveTab(''); + player.toggleEvents(); + } else { + setActiveTab(tab); + !showEvents && player.toggleEvents(); + } + }} + border={false} + /> + </div> + </div> + ); +} + +const PlayerHeaderCont = connect( + (state: any) => { + const session = state.getIn(['sessions', 'current']); + + return { + session, + sessionPath: state.getIn(['sessions', 'sessionPath']), + local: state.getIn(['sessions', 'timezone']), + funnelRef: state.getIn(['funnels', 'navRef']), + siteId: state.getIn(['site', 'siteId']), + metaList: state.getIn(['customFields', 'list']).map((i: any) => i.key), + }; + }, + { + toggleFavorite, + setSessionPath, + fetchMetadata, + } +)(observer(PlayerBlockHeader)); + +export default withRouter(PlayerHeaderCont); diff --git a/frontend/app/components/Session/Player/ReplayPlayer/PlayerContent.tsx b/frontend/app/components/Session/Player/ReplayPlayer/PlayerContent.tsx new file mode 100644 index 000000000..7a4f3c132 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/PlayerContent.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import cn from 'classnames'; +import styles from 'Components/Session_/session.module.css'; +import { countDaysFrom } from 'App/date'; +import RightBlock from 'Components/Session/RightBlock'; +import { PlayerContext } from 'Components/Session/playerContext'; +import Session from 'Types/session' +import PlayerBlock from './PlayerBlock'; + +const TABS = { + EVENTS: 'User Steps', + HEATMAPS: 'Click Map', +}; + +interface IProps { + fullscreen: boolean; + activeTab: string; + setActiveTab: (tab: string) => void; + session: Session +} + +function PlayerContent({ session, fullscreen, activeTab, setActiveTab }: IProps) { + const { store } = React.useContext(PlayerContext) + + const { + error, + } = store.get() + + const hasError = !!error + + const sessionDays = countDaysFrom(session.startedAt); + return ( + <div className="relative"> + {hasError ? ( + <div + className="inset-0 flex items-center justify-center absolute" + style={{ + // background: '#f6f6f6', + height: 'calc(100vh - 50px)', + zIndex: '999', + }} + > + <div className="flex flex-col items-center"> + <div className="text-lg -mt-8"> + {sessionDays > 2 ? 'Session not found.' : 'This session is still being processed.'} + </div> + <div className="text-sm"> + {sessionDays > 2 + ? 'Please check your data retention policy.' + : 'Please check it again in a few minutes.'} + </div> + </div> + </div> + ) : ( + <div className={cn('flex', { 'pointer-events-none': hasError })}> + <div + className="w-full" + style={activeTab && !fullscreen ? { maxWidth: 'calc(100% - 270px)' } : undefined} + > + <div className={cn(styles.session, 'relative')} data-fullscreen={fullscreen}> + <PlayerBlock activeTab={activeTab} /> + </div> + </div> + {activeTab !== '' && ( + <RightMenu + activeTab={activeTab} + setActiveTab={setActiveTab} + fullscreen={fullscreen} + tabs={TABS} + /> + )} + </div> + )} + </div> + ); +} + +function RightMenu({ tabs, activeTab, setActiveTab, fullscreen }: any) { + return ( + !fullscreen ? <RightBlock tabs={tabs} setActiveTab={setActiveTab} activeTab={activeTab} /> : null + ); +} + +export default observer(PlayerContent); diff --git a/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx b/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx new file mode 100644 index 000000000..6a300cce7 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx @@ -0,0 +1,129 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { findDOMNode } from 'react-dom'; +import cn from 'classnames'; +import { EscapeButton } from 'UI'; +import { + NONE, + CONSOLE, + NETWORK, + STACKEVENTS, + STORAGE, + PROFILER, + PERFORMANCE, + GRAPHQL, + EXCEPTIONS, + INSPECTOR, + OVERVIEW, + fullscreenOff, +} from 'Duck/components/player'; +import NetworkPanel from 'Shared/DevTools/NetworkPanel'; +import Storage from 'Components/Session_/Storage'; +import { ConnectedPerformance } from 'Components/Session_/Performance'; +import GraphQL from 'Components/Session_/GraphQL'; +import Exceptions from 'Components/Session_/Exceptions/Exceptions'; +import Inspector from 'Components/Session_/Inspector'; +import Controls from 'Components/Session_/Player/Controls'; +import Overlay from 'Components/Session_/Player/Overlay'; +import stl from 'Components/Session_/Player/player.module.css'; +import { updateLastPlayedSession } from 'Duck/sessions'; +import OverviewPanel from 'Components/Session_/OverviewPanel'; +import ConsolePanel from 'Shared/DevTools/ConsolePanel'; +import ProfilerPanel from 'Shared/DevTools/ProfilerPanel'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import StackEventPanel from 'Shared/DevTools/StackEventPanel'; + + +interface IProps { + fullView: boolean; + isMultiview?: boolean; + bottomBlock: number; + fullscreen: boolean; + fullscreenOff: () => any; + nextId: string; + sessionId: string; + activeTab: string; + updateLastPlayedSession: (id: string) => void +} + +function Player(props: IProps) { + const { + fullscreen, + fullscreenOff, + nextId, + bottomBlock, + activeTab, + fullView, + } = props; + const playerContext = React.useContext(PlayerContext); + const isReady = playerContext.store.get().ready + const screenWrapper = React.useRef<HTMLDivElement>(null); + const bottomBlockIsActive = !fullscreen && bottomBlock !== NONE; + const [isAttached, setAttached] = React.useState(false); + + React.useEffect(() => { + props.updateLastPlayedSession(props.sessionId); + const parentElement = findDOMNode(screenWrapper.current) as HTMLDivElement | null; //TODO: good architecture + if (parentElement && !isAttached) { + playerContext.player.attach(parentElement); + setAttached(true) + } + if (isAttached && isReady) { + playerContext.player.play(); + } + }, [isReady]); + + React.useEffect(() => { + playerContext.player.scale(); + }, [props.bottomBlock, props.fullscreen, playerContext.player, activeTab, fullView]); + + if (!playerContext.player) return null; + + const maxWidth = activeTab ? 'calc(100vw - 270px)' : '100vw'; + return ( + <div + className={cn(stl.playerBody, 'flex-1 flex flex-col relative', fullscreen && 'pb-2')} + data-bottom-block={bottomBlockIsActive} + > + {fullscreen && <EscapeButton onClose={fullscreenOff} />} + <div className={cn("relative flex-1",'overflow-hidden')}> + <Overlay nextId={nextId} /> + <div className={cn(stl.screenWrapper)} ref={screenWrapper} /> + </div> + {!fullscreen && !!bottomBlock && ( + <div style={{ maxWidth, width: '100%' }}> + {bottomBlock === OVERVIEW && <OverviewPanel />} + {bottomBlock === CONSOLE && <ConsolePanel />} + {bottomBlock === NETWORK && <NetworkPanel />} + {bottomBlock === STACKEVENTS && <StackEventPanel />} + {bottomBlock === STORAGE && <Storage />} + {bottomBlock === PROFILER && <ProfilerPanel />} + {bottomBlock === PERFORMANCE && <ConnectedPerformance />} + {bottomBlock === GRAPHQL && <GraphQL />} + {bottomBlock === EXCEPTIONS && <Exceptions />} + {bottomBlock === INSPECTOR && <Inspector />} + </div> + )} + {!fullView ? ( + <Controls + speedDown={playerContext.player.speedDown} + speedUp={playerContext.player.speedUp} + jump={playerContext.player.jump} + /> + ) : null} + </div> + ); +} + +export default connect( + (state: any) => ({ + fullscreen: state.getIn(['components', 'player', 'fullscreen']), + nextId: state.getIn(['sessions', 'nextId']), + sessionId: state.getIn(['sessions', 'current']).sessionId, + bottomBlock: state.getIn(['components', 'player', 'bottomBlock']), + }), + { + fullscreenOff, + updateLastPlayedSession, + } +)(Player); diff --git a/frontend/app/components/Session/Player/ReplayPlayer/playerBlockHeader.module.css b/frontend/app/components/Session/Player/ReplayPlayer/playerBlockHeader.module.css new file mode 100644 index 000000000..29c6e1648 --- /dev/null +++ b/frontend/app/components/Session/Player/ReplayPlayer/playerBlockHeader.module.css @@ -0,0 +1,21 @@ +.header { + height: 50px; + border-bottom: solid thin $gray-light; + padding-left: 15px; + padding-right: 0; + background-color: white; +} + +.divider { + width: 1px; + height: 49px; + margin: 0 10px; + background-color: $gray-light; +} + +.liveSwitchButton { + cursor: pointer; + color: $green; + text-decoration: underline; + white-space: nowrap; +} diff --git a/frontend/app/components/Session/RightBlock.tsx b/frontend/app/components/Session/RightBlock.tsx index 8ae42b622..9e4302648 100644 --- a/frontend/app/components/Session/RightBlock.tsx +++ b/frontend/app/components/Session/RightBlock.tsx @@ -1,32 +1,30 @@ -import React, { useState } from 'react' +import React from 'react' import EventsBlock from '../Session_/EventsBlock'; import PageInsightsPanel from '../Session_/PageInsightsPanel/PageInsightsPanel' -import { Controls as PlayerControls } from 'Player'; -import { connectPlayer } from 'Player'; + import cn from 'classnames'; import stl from './rightblock.module.css'; -const EventsBlockConnected = connectPlayer(state => ({ - currentTimeEventIndex: state.eventListNow.length > 0 ? state.eventListNow.length - 1 : 0, - playing: state.playing, -}))(EventsBlock) - -function RightBlock(props) { +function RightBlock(props: any) { const { activeTab } = props; - const renderActiveTab = (tab) => { - switch(tab) { - case props.tabs.EVENTS: - return <EventsBlockConnected setActiveTab={props.setActiveTab} player={PlayerControls}/> - case props.tabs.HEATMAPS: - return <PageInsightsPanel setActiveTab={props.setActiveTab} /> - } + if (activeTab === props.tabs.EVENTS) { + return ( + <div className={cn("flex flex-col bg-white border-l", stl.panel)}> + <EventsBlock + setActiveTab={props.setActiveTab} + /> + </div> + ) } - return ( - <div className={cn("flex flex-col bg-white border-l", stl.panel)}> - {renderActiveTab(activeTab)} - </div> - ) + if (activeTab === props.tabs.HEATMAPS) { + return ( + <div className={cn("flex flex-col bg-white border-l", stl.panel)}> + <PageInsightsPanel setActiveTab={props.setActiveTab} /> + </div> + ) + } + return null } -export default React.memo(RightBlock) +export default RightBlock diff --git a/frontend/app/components/Session/Session.js b/frontend/app/components/Session/Session.js index dae27df35..2a7cf81d8 100644 --- a/frontend/app/components/Session/Session.js +++ b/frontend/app/components/Session/Session.js @@ -9,6 +9,7 @@ import { sessions as sessionsRoute } from 'App/routes'; import withPermissions from 'HOCs/withPermissions' import WebPlayer from './WebPlayer'; import { useStore } from 'App/mstore'; +import { clearLogs } from 'App/dev/console'; const SESSIONS_ROUTE = sessionsRoute(); @@ -16,9 +17,7 @@ function Session({ sessionId, loading, hasErrors, - session, fetchSession, - fetchSlackList, }) { usePageTitle("OpenReplay Session Player"); const [ initializing, setInitializing ] = useState(true) @@ -33,6 +32,7 @@ function Session({ },[ sessionId ]); useEffect(() => { + clearLogs() sessionStore.resetUserFilter(); } ,[]) diff --git a/frontend/app/components/Session/WebPlayer.js b/frontend/app/components/Session/WebPlayer.js deleted file mode 100644 index 6e71d3ea7..000000000 --- a/frontend/app/components/Session/WebPlayer.js +++ /dev/null @@ -1,139 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { connect } from 'react-redux'; -import { Loader, Modal } from 'UI'; -import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player'; -import { fetchList } from 'Duck/integrations'; -import { PlayerProvider, injectNotes, connectPlayer, init as initPlayer, clean as cleanPlayer, Controls } from 'Player'; -import cn from 'classnames'; -import RightBlock from './RightBlock'; -import withLocationHandlers from 'HOCs/withLocationHandlers'; -import { useStore } from 'App/mstore' -import PlayerBlockHeader from '../Session_/PlayerBlockHeader'; -import PlayerBlock from '../Session_/PlayerBlock'; -import styles from '../Session_/session.module.css'; -import { countDaysFrom } from 'App/date'; -import ReadNote from '../Session_/Player/Controls/components/ReadNote'; -import { fetchList as fetchMembers } from 'Duck/member'; - -const TABS = { - EVENTS: 'User Steps', - HEATMAPS: 'Click Map', -}; - -const InitLoader = connectPlayer((state) => ({ - loading: !state.initialized, -}))(Loader); - -const PlayerContentConnected = connectPlayer((state) => ({ - showEvents: !state.showEvents, - hasError: state.error, -}))(PlayerContent); - -function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab, hasError }) { - const sessionDays = countDaysFrom(session.startedAt); - return ( - <div className="relative"> - {hasError ? ( - <div className="inset-0 flex items-center justify-center absolute" style={{ - // background: '#f6f6f6', - height: 'calc(100vh - 50px)', - zIndex: '999', - }}> - <div className="flex flex-col items-center"> - <div className="text-lg -mt-8">{sessionDays > 2 ? 'Session not found.' : 'This session is still being processed.'}</div> - <div className="text-sm">{sessionDays > 2 ? 'Please check your data retention policy.' : 'Please check it again in a few minutes.'}</div> - </div> - </div> - ) : ( - <div className={cn('flex', { 'pointer-events-none': hasError })}> - <div className="w-full" style={activeTab && !fullscreen ? { maxWidth: 'calc(100% - 270px)'} : undefined}> - <div className={cn(styles.session, 'relative')} data-fullscreen={fullscreen}> - <PlayerBlock activeTab={activeTab} /> - </div> - </div> - {activeTab !== '' && <RightMenu activeTab={activeTab} setActiveTab={setActiveTab} fullscreen={fullscreen} tabs={TABS} live={live} />} - </div> - )} - </div> - ); -} - -function RightMenu({ live, tabs, activeTab, setActiveTab, fullscreen }) { - return !live && !fullscreen && <RightBlock tabs={tabs} setActiveTab={setActiveTab} activeTab={activeTab} />; -} - -function WebPlayer(props) { - const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, fetchList } = props; - const { notesStore } = useStore() - const [activeTab, setActiveTab] = useState(''); - const [showNoteModal, setShowNote] = useState(false) - const [noteItem, setNoteItem] = useState(null) - - useEffect(() => { - fetchList('issues'); - initPlayer(session, jwt); - props.fetchMembers() - - notesStore.fetchSessionNotes(session.sessionId).then(r => { - injectNotes(r) - const note = props.query.get('note'); - if (note) { - Controls.pause() - setNoteItem(notesStore.getNoteById(parseInt(note, 10), r)) - setShowNote(true) - } - }) - - const jumptTime = props.query.get('jumpto'); - if (jumptTime) { - Controls.jump(parseInt(jumptTime)); - } - - return () => cleanPlayer(); - }, [session.sessionId]); - - // LAYOUT (TODO: local layout state - useContext or something..) - useEffect( - () => () => { - toggleFullscreen(false); - closeBottomBlock(); - }, - [] - ); - - const onNoteClose = () => {setShowNote(false); Controls.togglePlay()} - return ( - <PlayerProvider> - <InitLoader className="flex-1"> - <PlayerBlockHeader activeTab={activeTab} setActiveTab={setActiveTab} tabs={TABS} fullscreen={fullscreen} /> - <PlayerContentConnected activeTab={activeTab} fullscreen={fullscreen} live={live} setActiveTab={setActiveTab} session={session} /> - <Modal open={showNoteModal} onClose={onNoteClose}> - {showNoteModal ? ( - <ReadNote - userEmail={props.members.find(m => m.id === noteItem?.userId)?.email || ''} - note={noteItem} - onClose={onNoteClose} - notFound={!noteItem} - /> - ) : null} - </Modal> - </InitLoader> - </PlayerProvider> - ); -} - -export default connect( - (state) => ({ - session: state.getIn(['sessions', 'current']), - jwt: state.get('jwt'), - fullscreen: state.getIn(['components', 'player', 'fullscreen']), - showEvents: state.get('showEvents'), - members: state.getIn(['members', 'list']), - }), - { - toggleFullscreen, - closeBottomBlock, - fetchList, - fetchMembers, - } -)(withLocationHandlers()(WebPlayer)); diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx new file mode 100644 index 000000000..383b70c63 --- /dev/null +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -0,0 +1,138 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import { Modal } from 'UI'; +import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player'; +import { fetchList } from 'Duck/integrations'; +import { createWebPlayer } from 'Player'; +import { makeAutoObservable } from 'mobx'; +import withLocationHandlers from 'HOCs/withLocationHandlers'; +import { useStore } from 'App/mstore'; +import PlayerBlockHeader from './Player/ReplayPlayer/PlayerBlockHeader'; +import ReadNote from '../Session_/Player/Controls/components/ReadNote'; +import PlayerContent from './Player/ReplayPlayer/PlayerContent'; +import { IPlayerContext, PlayerContext, defaultContextValue } from './playerContext'; +import { observer } from 'mobx-react-lite'; +import { Note } from "App/services/NotesService"; + +const TABS = { + EVENTS: 'User Steps', + CLICKMAP: 'Click Map', +}; + +function WebPlayer(props: any) { + const { + session, + toggleFullscreen, + closeBottomBlock, + fullscreen, + fetchList, + } = props; + const { notesStore } = useStore(); + const [activeTab, setActiveTab] = useState(''); + const [showNoteModal, setShowNote] = useState(false); + const [noteItem, setNoteItem] = useState<Note | undefined>(undefined); + // @ts-ignore + const [contextValue, setContextValue] = useState<IPlayerContext>(defaultContextValue); + + useEffect(() => { + if (!session.sessionId) return; + fetchList('issues'); + + const [WebPlayerInst, PlayerStore] = createWebPlayer(session, (state) => + makeAutoObservable(state) + ); + setContextValue({ player: WebPlayerInst, store: PlayerStore }); + + notesStore.fetchSessionNotes(session.sessionId).then((r) => { + const note = props.query.get('note'); + if (note) { + setNoteItem(notesStore.getNoteById(parseInt(note, 10), r)); + setShowNote(true); + WebPlayerInst.pause(); + } + }) + + const jumpToTime = props.query.get('jumpto'); + const freeze = props.query.get('freeze') + if (jumpToTime) { + WebPlayerInst.jump(parseInt(jumpToTime)); + } + if (freeze) { + WebPlayerInst.freeze() + } + + return () => WebPlayerInst.clean(); + }, [session.sessionId]); + + const isPlayerReady = contextValue.store?.get().ready + + React.useEffect(() => { + if (showNoteModal) { + contextValue.player.pause() + } + if (activeTab !== 'Click Map' && !showNoteModal && isPlayerReady) { + contextValue.player && contextValue.player.play() + } + }, [activeTab, isPlayerReady, showNoteModal]) + + // LAYOUT (TODO: local layout state - useContext or something..) + useEffect( + () => () => { + toggleFullscreen(false); + closeBottomBlock(); + }, + [] + ); + + const onNoteClose = () => { + setShowNote(false); + contextValue.player.play(); + }; + + if (!contextValue.player || !session) return null; + + return ( + <PlayerContext.Provider value={contextValue}> + <PlayerBlockHeader + // @ts-ignore TODO? + activeTab={activeTab} + setActiveTab={setActiveTab} + tabs={TABS} + fullscreen={fullscreen} + /> + {/* @ts-ignore */} + <PlayerContent + activeTab={activeTab} + fullscreen={fullscreen} + setActiveTab={setActiveTab} + session={session} + /> + <Modal open={showNoteModal} onClose={onNoteClose}> + {showNoteModal ? ( + <ReadNote + note={noteItem} + onClose={onNoteClose} + notFound={!noteItem} + /> + ) : null} + </Modal> + </PlayerContext.Provider> + ); +} + +export default connect( + (state: any) => ({ + session: state.getIn(['sessions', 'current']), + insights: state.getIn(['sessions', 'insights']), + visitedEvents: state.getIn(['sessions', 'visitedEvents']), + jwt: state.getIn(['user', 'jwt']), + fullscreen: state.getIn(['components', 'player', 'fullscreen']), + showEvents: state.get('showEvents'), + members: state.getIn(['members', 'list']), + }), + { + toggleFullscreen, + closeBottomBlock, + fetchList, + } +)(withLocationHandlers()(observer(WebPlayer))); diff --git a/frontend/app/components/Session/playerContext.ts b/frontend/app/components/Session/playerContext.ts new file mode 100644 index 000000000..2f6c7c540 --- /dev/null +++ b/frontend/app/components/Session/playerContext.ts @@ -0,0 +1,24 @@ +import { createContext } from 'react'; +import { + IWebPlayer, + IWebPlayerStore, + IWebLivePlayer, + IWebLivePlayerStore, +} from 'Player' + +export interface IPlayerContext { + player: IWebPlayer + store: IWebPlayerStore, +} + +export interface ILivePlayerContext { + player: IWebLivePlayer + store: IWebLivePlayerStore +} + +type ContextType = + | IPlayerContext + | ILivePlayerContext +export const defaultContextValue = { player: undefined, store: undefined} +// @ts-ignore +export const PlayerContext = createContext<ContextType>(defaultContextValue); diff --git a/frontend/app/components/Session_/Autoplay/index.ts b/frontend/app/components/Session_/Autoplay/index.ts deleted file mode 100644 index acc3d46de..000000000 --- a/frontend/app/components/Session_/Autoplay/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Autoplay' \ No newline at end of file diff --git a/frontend/app/components/Session_/Autoscroll.tsx b/frontend/app/components/Session_/Autoscroll.tsx index ad2e82e01..88db41d59 100644 --- a/frontend/app/components/Session_/Autoscroll.tsx +++ b/frontend/app/components/Session_/Autoscroll.tsx @@ -1,5 +1,4 @@ import React, { ReactNode } from 'react'; -import { IconButton } from 'UI'; import cn from 'classnames'; import stl from './autoscroll.module.css'; diff --git a/frontend/app/components/Session_/BottomBlock/Header.js b/frontend/app/components/Session_/BottomBlock/Header.js index 15cdf3365..4812305b7 100644 --- a/frontend/app/components/Session_/BottomBlock/Header.js +++ b/frontend/app/components/Session_/BottomBlock/Header.js @@ -2,7 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import cn from 'classnames'; import { closeBottomBlock } from 'Duck/components/player'; -import { Input, CloseButton } from 'UI'; +import { CloseButton } from 'UI'; import stl from './header.module.css'; const Header = ({ diff --git a/frontend/app/components/Session_/BottomBlock/tabs.js b/frontend/app/components/Session_/BottomBlock/tabs.js deleted file mode 100644 index 6addd161e..000000000 --- a/frontend/app/components/Session_/BottomBlock/tabs.js +++ /dev/null @@ -1,9 +0,0 @@ -// import { NONE, CONSOLE, NETWORK, STACKEVENTS, REDUX_STATE, PROFILER, PERFORMANCE, GRAPHQL } from 'Duck/components/player'; -// -// -// export default { -// [NONE]: { -// Component: null, -// -// } -// } \ No newline at end of file diff --git a/frontend/app/components/Session_/BugReport/BugReportModal.tsx b/frontend/app/components/Session_/BugReport/BugReportModal.tsx index da4fa64b7..f43716b5b 100644 --- a/frontend/app/components/Session_/BugReport/BugReportModal.tsx +++ b/frontend/app/components/Session_/BugReport/BugReportModal.tsx @@ -119,21 +119,20 @@ function BugReportModal({ hideModal, session, width, height, account, xrayProps, }).then((canvas) => { const imgData = canvas.toDataURL('img/png'); - var imgWidth = 200; - var pageHeight = 295; - var imgHeight = (canvas.height * imgWidth) / canvas.width; - var heightLeft = imgHeight; - var position = 0; + let imgWidth = 200; + let pageHeight = 295; + let imgHeight = (canvas.height * imgWidth) / canvas.width; + let heightLeft = imgHeight - pageHeight; + let position = 0; doc.addImage(imgData, 'PNG', 5, 5, imgWidth, imgHeight); doc.addImage('/assets/img/report-head.png', 'png', 210/2 - 40/2, 2, 45, 5); - - heightLeft -= pageHeight - 7; + if (position === 0 && heightLeft === 0) doc.addImage('/assets/img/report-head.png', 'png', 210/2 - 40/2, pageHeight - 5, 45, 5); while (heightLeft >= 0) { - position = heightLeft - imgHeight + 10; + position = heightLeft - imgHeight; doc.addPage(); doc.addImage(imgData, 'PNG', 5, position, imgWidth, imgHeight); doc.addImage('/assets/img/report-head.png', 'png', 210/2 - 40/2, pageHeight - 5, 45, 5); @@ -141,7 +140,6 @@ function BugReportModal({ hideModal, session, width, height, account, xrayProps, } doc.link(5, 295 - Math.abs(heightLeft) - 25, 200, 30, { url: sessionUrl }); - if (position === 0) doc.addImage('/assets/img/report-head.png', 'png', 210/2 - 40/2, pageHeight - 5, 45, 5); doc.save('Bug Report: ' + sessionId + '.pdf'); setRendering(false); @@ -184,7 +182,7 @@ function BugReportModal({ hideModal, session, width, height, account, xrayProps, return ( <div className="bg-white overflow-y-scroll" - style={{ maxWidth: '70vw', width: 620, height: '100vh' }} + style={{ height: '100vh' }} > <div className="flex flex-col p-4 gap-8 bg-white relative" ref={reportRef}> <Title userName={account.name} /> diff --git a/frontend/app/components/Session_/BugReport/components/ReportTitle.tsx b/frontend/app/components/Session_/BugReport/components/ReportTitle.tsx index 6e00d397a..93eb4148a 100644 --- a/frontend/app/components/Session_/BugReport/components/ReportTitle.tsx +++ b/frontend/app/components/Session_/BugReport/components/ReportTitle.tsx @@ -45,7 +45,7 @@ function ReportTitle() { /> ) : ( // @ts-ignore - <Tooltip title="Double click to rename"> + <Tooltip delay={200} title="Double click to rename"> <div onDoubleClick={toggleEdit} className={cn( diff --git a/frontend/app/components/Session_/BugReport/components/Steps.tsx b/frontend/app/components/Session_/BugReport/components/Steps.tsx index 8307eea47..2f1517d24 100644 --- a/frontend/app/components/Session_/BugReport/components/Steps.tsx +++ b/frontend/app/components/Session_/BugReport/components/Steps.tsx @@ -46,6 +46,12 @@ function Steps({ xrayProps, notes, members }: Props) { bugReportStore.resetSteps(); }; + React.useEffect(() => { + if (bugReportStore.sessionEventSteps.length < RADIUS && bugReportStore.sessionEventSteps.length > 0) { + setRadius(bugReportStore.sessionEventSteps.length); + } + }, [bugReportStore.sessionEventSteps]) + return ( <div> <SectionTitle>Steps to reproduce</SectionTitle> @@ -63,7 +69,7 @@ function Steps({ xrayProps, notes, members }: Props) { STEPS <div id="pdf-ignore"> {timePointer > 0 ? ( - <StepRadius pickRadius={stepPickRadius} setRadius={setRadius} /> + <StepRadius pickRadius={stepPickRadius} setRadius={setRadius} stepsNum={bugReportStore.sessionEventSteps.length}/> ) : null} </div> </div> diff --git a/frontend/app/components/Session_/BugReport/components/StepsComponents/EventStep.tsx b/frontend/app/components/Session_/BugReport/components/StepsComponents/EventStep.tsx index 255cac92b..f28771c47 100644 --- a/frontend/app/components/Session_/BugReport/components/StepsComponents/EventStep.tsx +++ b/frontend/app/components/Session_/BugReport/components/StepsComponents/EventStep.tsx @@ -21,17 +21,17 @@ function Step({ step, ind, isDefault }: { step: IStep; ind: number; isDefault?: const menuItems = [ { icon: 'quotes', - text: 'Add/Remove Note', + text: 'Notes', onClick: () => bugReportStore.toggleSubStepModal(true, 'note', step.key), }, { icon: 'info-circle', - text: `Add/Remove Error`, + text: `Errors`, onClick: () => bugReportStore.toggleSubStepModal(true, 'error', step.key), }, { icon: 'network', - text: 'Add/Remove Network Request', + text: 'Bad Network Requests', onClick: () => bugReportStore.toggleSubStepModal(true, 'network', step.key), }, ]; @@ -64,7 +64,7 @@ function Step({ step, ind, isDefault }: { step: IStep; ind: number; isDefault?: )} > {/* @ts-ignore */} - <Tooltip title="Add Note, Error or Network Request" className="!flex items-center"> + <Tooltip title="Add Note, Error or bad Network Request" className="!flex items-center"> <ItemMenu label={ <Icon diff --git a/frontend/app/components/Session_/BugReport/components/StepsComponents/StepRadius.tsx b/frontend/app/components/Session_/BugReport/components/StepsComponents/StepRadius.tsx index 543d2c803..afa8b15c7 100644 --- a/frontend/app/components/Session_/BugReport/components/StepsComponents/StepRadius.tsx +++ b/frontend/app/components/Session_/BugReport/components/StepsComponents/StepRadius.tsx @@ -4,9 +4,10 @@ import { Tooltip } from 'UI' interface Props { pickRadius: number; setRadius: (v: number) => void; + stepsNum: number; } -function StepRadius({ pickRadius, setRadius }: Props) { +function StepRadius({ pickRadius, setRadius, stepsNum }: Props) { return ( <div className="w-full flex items-center gap-4"> <div className="border-b border-dotted border-gray-medium cursor-help"> @@ -18,7 +19,7 @@ function StepRadius({ pickRadius, setRadius }: Props) { <div className="flex items-center gap-1"> <div className="rounded px-2 bg-light-blue-bg cursor-pointer hover:bg-teal-light" - onClick={() => setRadius(pickRadius + 1)} + onClick={() => pickRadius < Math.floor(stepsNum/2) ? setRadius(pickRadius + 1) : null} > +1 </div> diff --git a/frontend/app/components/Session_/BugReport/components/StepsComponents/SubModal.tsx b/frontend/app/components/Session_/BugReport/components/StepsComponents/SubModal.tsx index e70f19150..f557216bf 100644 --- a/frontend/app/components/Session_/BugReport/components/StepsComponents/SubModal.tsx +++ b/frontend/app/components/Session_/BugReport/components/StepsComponents/SubModal.tsx @@ -7,9 +7,9 @@ import { filterList, debounce } from 'App/utils'; import { useStore } from 'App/mstore'; const Titles = { - note: 'Note', - network: 'Fetch/XHR', - error: 'Console Error', + note: 'Notes', + network: 'Fetch/XHR Errors', + error: 'Console Errors', }; const Icons = { note: 'quotes' as const, @@ -171,6 +171,7 @@ function SubModal(props: ModalProps) { zIndex: 999, }} > + {/* @ts-ignore */} <ModalContent type={props.type} items={items} /> </div> ); diff --git a/frontend/app/components/Session_/BugReport/components/StepsComponents/SubModalItems.tsx b/frontend/app/components/Session_/BugReport/components/StepsComponents/SubModalItems.tsx index 136bfe0fc..bf68d0144 100644 --- a/frontend/app/components/Session_/BugReport/components/StepsComponents/SubModalItems.tsx +++ b/frontend/app/components/Session_/BugReport/components/StepsComponents/SubModalItems.tsx @@ -28,7 +28,10 @@ export interface INetworkReq extends Item { export type SubItem = INoteItem | IError | INetworkReq; -const safeStr = (ogStr: string) => (ogStr.length > 60 ? ogStr.slice(0, 60) + '...' : ogStr); +const safeStr = (ogStr: string) => { + if (!ogStr) return '' + return (ogStr.length > 80 ? ogStr.slice(0, 80) + '...' : ogStr) +} export const NetworkComp = ({ item }: { item: INetworkReq }) => ( <div className="flex items-start flex-col z-10"> diff --git a/frontend/app/components/Session_/BugReport/utils.ts b/frontend/app/components/Session_/BugReport/utils.ts index a97e275d7..053ece364 100644 --- a/frontend/app/components/Session_/BugReport/utils.ts +++ b/frontend/app/components/Session_/BugReport/utils.ts @@ -62,7 +62,7 @@ export function getClosestEventStep(time: number, arr: Step[]) { export const selectEventSteps = (steps: Step[], targetTime: number, radius: number) => { const { targetStep, index } = getClosestEventStep(targetTime, steps) - const stepsBeforeEvent = steps.slice(index - radius, index) + const stepsBeforeEvent = steps.slice(Math.max(index - radius, 0), index) const stepsAfterEvent = steps.slice(index + 1, index + 1 + radius) return [...stepsBeforeEvent, targetStep, ...stepsAfterEvent] diff --git a/frontend/app/components/Session_/Console/Console.js b/frontend/app/components/Session_/Console/Console.js deleted file mode 100644 index 3c4a3752c..000000000 --- a/frontend/app/components/Session_/Console/Console.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import { connectPlayer, jump } from 'Player'; -import ConsoleContent from './ConsoleContent'; - -@connectPlayer(state => ({ - logs: state.logList, - // time: state.time, - livePlay: state.livePlay, - listNow: state.logListNow, -})) -export default class Console extends React.PureComponent { - render() { - const { logs, time, listNow } = this.props; - return ( - <ConsoleContent jump={!this.props.livePlay && jump} logs={logs} lastIndex={listNow.length - 1} logsNow={listNow} /> - ); - } -} diff --git a/frontend/app/components/Session_/Console/ConsoleContent.js b/frontend/app/components/Session_/Console/ConsoleContent.js deleted file mode 100644 index a7482b69e..000000000 --- a/frontend/app/components/Session_/Console/ConsoleContent.js +++ /dev/null @@ -1,123 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { getRE } from 'App/utils'; -import { Icon, NoContent, Tabs, Input } from 'UI'; -import { jump } from 'Player'; -import { LEVEL } from 'Types/session/log'; -import Autoscroll from '../Autoscroll'; -import BottomBlock from '../BottomBlock'; -import stl from './console.module.css'; -import ConsoleRow from './ConsoleRow'; -// import { Duration } from 'luxon'; - -const ALL = 'ALL'; -const INFO = 'INFO'; -const WARNINGS = 'WARNINGS'; -const ERRORS = 'ERRORS'; - -const LEVEL_TAB = { - [LEVEL.INFO]: INFO, - [LEVEL.LOG]: INFO, - [LEVEL.WARNING]: WARNINGS, - [LEVEL.ERROR]: ERRORS, - [LEVEL.EXCEPTION]: ERRORS, -}; - -const TABS = [ALL, ERRORS, WARNINGS, INFO].map((tab) => ({ text: tab, key: tab })); - -// eslint-disable-next-line complexity -const getIconProps = (level) => { - switch (level) { - case LEVEL.INFO: - case LEVEL.LOG: - return { - name: 'console/info', - color: 'blue2', - }; - case LEVEL.WARN: - case LEVEL.WARNING: - return { - name: 'console/warning', - color: 'red2', - }; - case LEVEL.ERROR: - return { - name: 'console/error', - color: 'red', - }; - } - return null; -}; - -function renderWithNL(s = '') { - if (typeof s !== 'string') return ''; - return s.split('\n').map((line, i) => <div className={cn({ 'ml-20': i !== 0 })}>{line}</div>); -} - -export default class ConsoleContent extends React.PureComponent { - state = { - filter: '', - activeTab: ALL, - }; - onTabClick = (activeTab) => this.setState({ activeTab }); - onFilterChange = ({ target: { value } }) => this.setState({ filter: value }); - - render() { - const { logs, isResult, additionalHeight, logsNow } = this.props; - const time = logsNow.length > 0 ? logsNow[logsNow.length - 1].time : undefined; - const { filter, activeTab, currentError } = this.state; - const filterRE = getRE(filter, 'i'); - const filtered = logs.filter(({ level, value }) => - activeTab === ALL - ? filterRE.test(value) - : filterRE.test(value) && LEVEL_TAB[level] === activeTab - ); - - const lastIndex = filtered.filter((item) => item.time <= time).length - 1; - - return ( - <> - <BottomBlock style={{ height: 300 + additionalHeight + 'px' }}> - <BottomBlock.Header showClose={!isResult}> - <div className="flex items-center"> - <span className="font-semibold color-gray-medium mr-4">Console</span> - <Tabs tabs={TABS} active={activeTab} onClick={this.onTabClick} border={false} /> - </div> - <Input - className="input-small h-8" - placeholder="Filter by keyword" - icon="search" - iconPosition="left" - name="filter" - height={28} - onChange={this.onFilterChange} - /> - </BottomBlock.Header> - <BottomBlock.Content> - <NoContent - title={ - <div className="capitalize flex items-center mt-16"> - <Icon name="info-circle" className="mr-2" size="18" /> - No Data - </div> - } - size="small" - show={filtered.length === 0} - > - <Autoscroll autoScrollTo={Math.max(lastIndex, 0)}> - {filtered.map((l) => ( - <ConsoleRow - log={l} - jump={jump} - iconProps={getIconProps(l.level)} - renderWithNL={renderWithNL} - /> - ))} - </Autoscroll> - </NoContent> - </BottomBlock.Content> - </BottomBlock> - </> - ); - } -} diff --git a/frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx b/frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx index 85457d6b1..855ece5eb 100644 --- a/frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx +++ b/frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx @@ -20,9 +20,9 @@ function ConsoleRow(props: Props) { return ( <div className={cn(stl.line, 'flex py-2 px-4 overflow-hidden group relative select-none', { - info: !log.isYellow() && !log.isRed(), - warn: log.isYellow(), - error: log.isRed(), + info: !log.isYellow && !log.isRed, + warn: log.isYellow, + error: log.isRed, 'cursor-pointer': canExpand, })} style={style} @@ -34,14 +34,14 @@ function ConsoleRow(props: Props) { {/* <div className={cn(stl.timestamp, {})}> {Duration.fromMillis(log.time).toFormat('mm:ss.SSS')} </div> */} - <div key={log.key} className={cn('')} data-scroll-item={log.isRed()}> + <div key={log.key} className={cn('')} data-scroll-item={log.isRed}> <div className={cn(stl.message, 'flex items-center')}> {canExpand && ( <Icon name={expanded ? 'caret-down-fill' : 'caret-right-fill'} className="mr-2" /> )} <span>{renderWithNL(lines.pop())}</span> </div> - {canExpand && expanded && lines.map((l: any, i: number) => <div key={l.slice(0,3)+i} className="ml-4 mb-1">{l}</div>)} + {/* {canExpand && expanded && lines.map((l: any, i: number) => <div key={l.slice(0,3)+i} className="ml-4 mb-1">{l}</div>)} */} </div> <JumpButton onClick={() => jump(log.time)} /> </div> diff --git a/frontend/app/components/Session_/Console/ConsoleRow/index.ts b/frontend/app/components/Session_/Console/ConsoleRow/index.ts deleted file mode 100644 index c9140d748..000000000 --- a/frontend/app/components/Session_/Console/ConsoleRow/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ConsoleRow'; diff --git a/frontend/app/components/Session_/Console/console.module.css b/frontend/app/components/Session_/Console/console.module.css deleted file mode 100644 index 2da78f540..000000000 --- a/frontend/app/components/Session_/Console/console.module.css +++ /dev/null @@ -1,38 +0,0 @@ - -.message { - overflow-x: auto; - margin-left: 10px; - font-size: 13px; - overflow-x: auto; - &::-webkit-scrollbar { - height: 2px; - } -} - -.line { - font-family: 'Menlo', 'monaco', 'consolas', monospace; - /* margin-top: -1px; ??? */ - display: flex; - align-items: flex-start; - border-bottom: solid thin $gray-light-shade; - &:hover { - background-coor: $active-blue !important; - } -} - -.timestamp { - -} - -.activeRow { - background-color: $teal-light !important; -} - -.icon { - padding-top: 4px; - margin-right: 7px; -} - -.inactiveRow { - opacity: 0.5; -} \ No newline at end of file diff --git a/frontend/app/components/Session_/EventsBlock/Event.js b/frontend/app/components/Session_/EventsBlock/Event.js index bfae9fe5d..e8f985aa0 100644 --- a/frontend/app/components/Session_/EventsBlock/Event.js +++ b/frontend/app/components/Session_/EventsBlock/Event.js @@ -147,14 +147,6 @@ export default class Event extends React.PureComponent { </button> } <div className={ cls.topBlock }> - {/* <div className={ cls.checkbox }> - <Checkbox - className="customCheckbox" - name={ event.key } - checked={ selected } - onClick={ onCheckboxClick } - /> - </div> */} <div className={ cls.firstLine }> { this.renderBody() } </div> diff --git a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js index 1ec053462..924be9f2c 100644 --- a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js +++ b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js @@ -88,7 +88,6 @@ class EventGroupWrapper extends React.Component { )} {isNote ? ( <NoteEvent - userEmail={this.props.members.find((m) => m.id === event.userId)?.email || event.userId} note={event} filterOutNote={filterOutNote} onEdit={this.props.setEditNoteTooltip} diff --git a/frontend/app/components/Session_/EventsBlock/EventSearch/EventSearch.js b/frontend/app/components/Session_/EventsBlock/EventSearch/EventSearch.js index f0c7efe06..419434d22 100644 --- a/frontend/app/components/Session_/EventsBlock/EventSearch/EventSearch.js +++ b/frontend/app/components/Session_/EventsBlock/EventSearch/EventSearch.js @@ -1,15 +1,13 @@ -import React, { useEffect } from 'react' +import React from 'react' import { Input, Icon } from 'UI' -import { connectPlayer, toggleEvents, scale } from 'Player'; +import { PlayerContext } from 'App/components/Session/playerContext'; function EventSearch(props) { - const { onChange, clearSearch, value, header, toggleEvents, setActiveTab } = props; + const { player } = React.useContext(PlayerContext) - useEffect(() => { - return () => { - clearSearch() - } - }, []) + const { onChange, value, header, setActiveTab } = props; + + const toggleEvents = () => player.toggleEvents() return ( <div className="flex items-center w-full relative"> @@ -42,4 +40,4 @@ function EventSearch(props) { ) } -export default connectPlayer(() => ({}), { toggleEvents })(EventSearch) +export default EventSearch diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.js b/frontend/app/components/Session_/EventsBlock/EventsBlock.js deleted file mode 100644 index 69ba51996..000000000 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.js +++ /dev/null @@ -1,248 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import cn from 'classnames'; -import { Icon } from 'UI'; -import { List, AutoSizer, CellMeasurer, CellMeasurerCache } from "react-virtualized"; -import { TYPES } from 'Types/session/event'; -import { setSelected } from 'Duck/events'; -import { setEventFilter, filterOutNote } from 'Duck/sessions'; -import { show as showTargetDefiner } from 'Duck/components/targetDefiner'; -import EventGroupWrapper from './EventGroupWrapper'; -import styles from './eventsBlock.module.css'; -import EventSearch from './EventSearch/EventSearch'; - -@connect(state => ({ - session: state.getIn([ 'sessions', 'current' ]), - filteredEvents: state.getIn([ 'sessions', 'filteredEvents' ]), - eventsIndex: state.getIn([ 'sessions', 'eventsIndex' ]), - selectedEvents: state.getIn([ 'events', 'selected' ]), - targetDefinerDisplayed: state.getIn([ 'components', 'targetDefiner', 'isDisplayed' ]), - testsAvaliable: false, -}), { - showTargetDefiner, - setSelected, - setEventFilter, - filterOutNote -}) -export default class EventsBlock extends React.Component { - state = { - editingEvent: null, - mouseOver: false, - query: '' - } - - scroller = React.createRef(); - cache = new CellMeasurerCache({ - fixedWidth: true, - defaultHeight: 300 - }); - - write = ({ target: { value, name } }) => { - const { filter } = this.state; - this.setState({ query: value }) - this.props.setEventFilter({ query: value, filter }) - - setTimeout(() => { - if (!this.scroller.current) return; - - this.scroller.current.scrollToRow(0); - }, 100) - } - - clearSearch = () => { - const { filter } = this.state; - this.setState({ query: '' }) - this.props.setEventFilter({ query: '', filter }) - if (this.scroller.current) { - this.scroller.current.forceUpdateGrid(); - } - - setTimeout(() => { - if (!this.scroller.current) return; - - this.scroller.current.scrollToRow(0); - }, 100) - } - - onSetEventFilter = (e, { name, value }) => { - const { query } = this.state; - this.setState({ filter: value }) - this.props.setEventFilter({ filter: value, query }); - } - - componentDidUpdate(prevProps) { - if (prevProps.targetDefinerDisplayed && !this.props.targetDefinerDisplayed) { - this.setState({ editingEvent: null }); - } - if (prevProps.session !== this.props.session) { // Doesn't happen - this.cache = new CellMeasurerCache({ - fixedWidth: true, - defaultHeight: 300 - }); - } - if (prevProps.currentTimeEventIndex !== this.props.currentTimeEventIndex && - this.scroller.current !== null) { - this.scroller.current.forceUpdateGrid(); - if (!this.state.mouseOver) { - this.scroller.current.scrollToRow(this.props.currentTimeEventIndex); - } - } - } - - onCheckboxClick(e, event) { - e.stopPropagation(); - const { - session: { events }, - selectedEvents, - } = this.props; - - this.props.player.pause(); - - let newSelectedSet; - const wasSelected = selectedEvents.contains(event); - if (wasSelected) { - newSelectedSet = selectedEvents.remove(event); - } else { - newSelectedSet = selectedEvents.add(event); - } - - let selectNextLoad = false; - events.reverse().forEach((sessEvent) => { - if (sessEvent.type === TYPES.LOCATION) { - if (selectNextLoad) { - newSelectedSet = newSelectedSet.add(sessEvent); - } - selectNextLoad = false; - } else if (newSelectedSet.contains(sessEvent)) { - selectNextLoad = true; - } - }); - this.props.setSelected(newSelectedSet); - } - - onEventClick = (e, event) => this.props.player.jump(event.time) - - onMouseOver = () => this.setState({ mouseOver: true }) - onMouseLeave = () => this.setState({ mouseOver: false }) - - get eventsList() { - const { session: { notesWithEvents }, filteredEvents } = this.props - const usedEvents = filteredEvents || notesWithEvents - - return usedEvents - } - - renderGroup = ({ index, key, style, parent }) => { - const { - selectedEvents, - currentTimeEventIndex, - testsAvaliable, - playing, - eventsIndex, - filterOutNote, - } = this.props; - const { query } = this.state; - const _events = this.eventsList - const isLastEvent = index === _events.size - 1; - const isLastInGroup = isLastEvent || _events.get(index + 1).type === TYPES.LOCATION; - const event = _events.get(index); - const isNote = !!event.noteId - const isSelected = selectedEvents.includes(event); - const isCurrent = index === currentTimeEventIndex; - const isEditing = this.state.editingEvent === event; - - const heightBug = index === 0 && event.type === TYPES.LOCATION && event.referrer ? { top: 2 } : {} - return ( - <CellMeasurer - key={key} - cache={this.cache} - parent={parent} - rowIndex={index} - > - {({measure, registerChild}) => ( - <div style={{ ...style, ...heightBug }} ref={registerChild}> - <EventGroupWrapper - query={query} - presentInSearch={eventsIndex.includes(index)} - isFirst={index==0} - mesureHeight={measure} - onEventClick={ this.onEventClick } - onCheckboxClick={ this.onCheckboxClick } - event={ event } - isLastEvent={ isLastEvent } - isLastInGroup={ isLastInGroup } - isSelected={ isSelected } - isCurrent={ isCurrent } - isEditing={ isEditing } - showSelection={ testsAvaliable && !playing } - isNote={isNote} - filterOutNote={filterOutNote} - /> - </div> - )} - </CellMeasurer> - ); - } - - render() { - const { query } = this.state; - const { - session: { - events, - }, - setActiveTab, - } = this.props; - - const _events = this.eventsList - - const isEmptySearch = query && (_events.size === 0 || !_events) - return ( - <> - <div className={ cn(styles.header, 'p-4') }> - <div className={ cn(styles.hAndProgress, 'mt-3') }> - <EventSearch - onChange={this.write} - clearSearch={this.clearSearch} - setActiveTab={setActiveTab} - value={query} - header={ - <div className="text-xl">User Steps <span className="color-gray-medium">{ events.size }</span></div> - } - /> - </div> - </div> - <div - className={ cn("flex-1 px-4 pb-4", styles.eventsList) } - id="eventList" - data-openreplay-masked - onMouseOver={ this.onMouseOver } - onMouseLeave={ this.onMouseLeave } - > - {isEmptySearch && ( - <div className='flex items-center'> - <Icon name="binoculars" size={18} /> - <span className='ml-2'>No Matching Results</span> - </div> - )} - <AutoSizer disableWidth> - {({ height }) => ( - <List - ref={this.scroller} - className={ styles.eventsList } - height={height + 10} - width={248} - overscanRowCount={6} - itemSize={230} - rowCount={_events.size} - deferredMeasurementCache={this.cache} - rowHeight={this.cache.rowHeight} - rowRenderer={this.renderGroup} - scrollToAlignment="start" - /> - )} - </AutoSizer> - </div> - </> - ); - } -} diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx new file mode 100644 index 000000000..82dedce8e --- /dev/null +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx @@ -0,0 +1,192 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import cn from 'classnames'; +import { Icon } from 'UI'; +import { List, AutoSizer, CellMeasurer } from "react-virtualized"; +import { TYPES } from 'Types/session/event'; +import { setEventFilter, filterOutNote } from 'Duck/sessions'; +import EventGroupWrapper from './EventGroupWrapper'; +import styles from './eventsBlock.module.css'; +import EventSearch from './EventSearch/EventSearch'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { RootStore } from 'App/duck' +import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache' +import { InjectedEvent } from 'Types/session/event' +import Session from 'Types/session' + +interface IProps { + setEventFilter: (filter: { query: string }) => void + filteredEvents: InjectedEvent[] + setActiveTab: (tab?: string) => void + query: string + events: Session['events'] + notesWithEvents: Session['notesWithEvents'] + filterOutNote: (id: string) => void + eventsIndex: number[] +} + +function EventsBlock(props: IProps) { + const [mouseOver, setMouseOver] = React.useState(true) + const scroller = React.useRef<List>(null) + const cache = useCellMeasurerCache(undefined, { + fixedWidth: true, + defaultHeight: 300 + }); + + const { store, player } = React.useContext(PlayerContext) + + const { eventListNow, playing } = store.get() + + const { + filteredEvents, + eventsIndex, + filterOutNote, + query, + setActiveTab, + events, + notesWithEvents, + } = props + + const currentTimeEventIndex = eventListNow.length > 0 ? eventListNow.length - 1 : 0 + const usedEvents = filteredEvents || notesWithEvents + + const write = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => { + props.setEventFilter({ query: value }) + + setTimeout(() => { + if (!scroller.current) return; + + scroller.current.scrollToRow(0); + }, 100) + } + + const clearSearch = () => { + props.setEventFilter({ query: '' }) + if (scroller.current) { + scroller.current.forceUpdateGrid(); + } + + setTimeout(() => { + if (!scroller.current) return; + + scroller.current.scrollToRow(0); + }, 100) + } + + React.useEffect(() => { + return () => { + clearSearch() + } + }, []) + React.useEffect(() => { + if (scroller.current) { + scroller.current.forceUpdateGrid(); + if (!mouseOver) { + scroller.current.scrollToRow(currentTimeEventIndex); + } + } + }, [currentTimeEventIndex]) + + const onEventClick = (_: React.MouseEvent, event: { time: number }) => player.jump(event.time) + const onMouseOver = () => setMouseOver(true) + const onMouseLeave = () => setMouseOver(false) + + const renderGroup = ({ index, key, style, parent }: { index: number; key: string; style: React.CSSProperties; parent: any }) => { + const isLastEvent = index === usedEvents.length - 1; + const isLastInGroup = isLastEvent || usedEvents[index + 1]?.type === TYPES.LOCATION; + const event = usedEvents[index]; + const isNote = 'noteId' in event + const isCurrent = index === currentTimeEventIndex; + + const heightBug = index === 0 && event?.type === TYPES.LOCATION && 'referrer' in event ? { top: 2 } : {} + return ( + <CellMeasurer + key={key} + cache={cache} + parent={parent} + rowIndex={index} + > + {({measure, registerChild}) => ( + <div style={{ ...style, ...heightBug }} ref={registerChild}> + <EventGroupWrapper + query={query} + presentInSearch={eventsIndex.includes(index)} + isFirst={index==0} + mesureHeight={measure} + onEventClick={ onEventClick } + event={ event } + isLastEvent={ isLastEvent } + isLastInGroup={ isLastInGroup } + isCurrent={ isCurrent } + showSelection={ !playing } + isNote={isNote} + filterOutNote={filterOutNote} + /> + </div> + )} + </CellMeasurer> + ); + } + + const isEmptySearch = query && (usedEvents.length === 0 || !usedEvents) + return ( + <> + <div className={ cn(styles.header, 'p-4') }> + <div className={ cn(styles.hAndProgress, 'mt-3') }> + <EventSearch + onChange={write} + setActiveTab={setActiveTab} + value={query} + header={ + <div className="text-xl">User Steps <span className="color-gray-medium">{ events.length }</span></div> + } + /> + </div> + </div> + <div + className={ cn("flex-1 px-4 pb-4", styles.eventsList) } + id="eventList" + data-openreplay-masked + onMouseOver={ onMouseOver } + onMouseLeave={ onMouseLeave } + > + {isEmptySearch && ( + <div className='flex items-center'> + <Icon name="binoculars" size={18} /> + <span className='ml-2'>No Matching Results</span> + </div> + )} + <AutoSizer disableWidth> + {({ height }) => ( + <List + ref={scroller} + className={ styles.eventsList } + height={height + 10} + width={248} + overscanRowCount={6} + itemSize={230} + rowCount={usedEvents.length} + deferredMeasurementCache={cache} + rowHeight={cache.rowHeight} + rowRenderer={renderGroup} + scrollToAlignment="start" + /> + )} + </AutoSizer> + </div> + </> + ); +} + +export default connect((state: RootStore) => ({ + session: state.getIn([ 'sessions', 'current' ]), + notesWithEvents: state.getIn([ 'sessions', 'current' ]).notesWithEvents, + events: state.getIn([ 'sessions', 'current' ]).events, + filteredEvents: state.getIn([ 'sessions', 'filteredEvents' ]), + query: state.getIn(['sessions', 'eventsQuery']), + eventsIndex: state.getIn([ 'sessions', 'eventsIndex' ]), +}), { + setEventFilter, + filterOutNote +})(observer(EventsBlock)) diff --git a/frontend/app/components/Session_/EventsBlock/Metadata/Metadata.js b/frontend/app/components/Session_/EventsBlock/Metadata/Metadata.js index f6724385f..ca0a953a2 100644 --- a/frontend/app/components/Session_/EventsBlock/Metadata/Metadata.js +++ b/frontend/app/components/Session_/EventsBlock/Metadata/Metadata.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import MetadataItem from './MetadataItem'; export default connect(state => ({ - metadata: state.getIn([ 'sessions', 'current', 'metadata' ]), + metadata: state.getIn([ 'sessions', 'current' ]).metadata, }))(function Metadata ({ metadata }) { const metaLenth = Object.keys(metadata).length; diff --git a/frontend/app/components/Session_/EventsBlock/Metadata/SessionList.js b/frontend/app/components/Session_/EventsBlock/Metadata/SessionList.js index fe414a30d..92ac93432 100644 --- a/frontend/app/components/Session_/EventsBlock/Metadata/SessionList.js +++ b/frontend/app/components/Session_/EventsBlock/Metadata/SessionList.js @@ -7,7 +7,7 @@ import stl from './sessionList.module.css'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; @connect((state) => ({ - currentSessionId: state.getIn(['sessions', 'current', 'sessionId']), + currentSessionId: state.getIn(['sessions', 'current']).sessionId, })) class SessionList extends React.PureComponent { render() { @@ -17,7 +17,7 @@ class SessionList extends React.PureComponent { .map(({ sessions, ...rest }) => { return { ...rest, - sessions: sessions.map(Session).filter(({ sessionId }) => sessionId !== currentSessionId), + sessions: sessions.map(s => new Session(s)).filter(({ sessionId }) => sessionId !== currentSessionId), }; }) .filter((site) => site.sessions.length > 0); diff --git a/frontend/app/components/Session_/EventsBlock/NoteEvent.tsx b/frontend/app/components/Session_/EventsBlock/NoteEvent.tsx index 08e5631a9..a09869ba5 100644 --- a/frontend/app/components/Session_/EventsBlock/NoteEvent.tsx +++ b/frontend/app/components/Session_/EventsBlock/NoteEvent.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Icon } from 'UI'; -import { tagProps, iTag, Note } from 'App/services/NotesService'; +import { tagProps, Note } from 'App/services/NotesService'; import { formatTimeOrDate } from 'App/date'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; @@ -9,13 +9,11 @@ import copy from 'copy-to-clipboard'; import { toast } from 'react-toastify'; import { session } from 'App/routes'; import { confirm } from 'UI'; -import { filterOutNote as filterOutTimelineNote } from 'Player'; import { TeamBadge } from 'Shared/SessionListContainer/components/Notes'; interface Props { note: Note; noEdit: boolean; - userEmail: string; filterOutNote: (id: number) => void; onEdit: (noteTooltipObj: Record<string, any>) => void; } @@ -24,7 +22,6 @@ function NoteEvent(props: Props) { const { settingsStore, notesStore } = useStore(); const { timezone } = settingsStore.sessionSettings; - console.log(props.noEdit); const onEdit = () => { props.onEdit({ isVisible: true, @@ -60,7 +57,6 @@ function NoteEvent(props: Props) { ) { notesStore.deleteNote(props.note.noteId).then((r) => { props.filterOutNote(props.note.noteId); - filterOutTimelineNote(props.note.noteId); toast.success('Note deleted'); }); } @@ -89,7 +85,7 @@ function NoteEvent(props: Props) { whiteSpace: 'nowrap', }} > - {props.userEmail}, {props.userEmail} + {props.note.userName} </div> <div className="text-disabled-text text-sm"> {formatTimeOrDate(props.note.createdAt as unknown as number, timezone)} diff --git a/frontend/app/components/Session_/EventsBlock/UserCard/UserCard.js b/frontend/app/components/Session_/EventsBlock/UserCard/UserCard.js index 2e4e4daa1..bda25ad88 100644 --- a/frontend/app/components/Session_/EventsBlock/UserCard/UserCard.js +++ b/frontend/app/components/Session_/EventsBlock/UserCard/UserCard.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import { connect } from 'react-redux'; import { List } from 'immutable'; import { countries } from 'App/constants'; diff --git a/frontend/app/components/Session_/Exceptions/Exceptions.js b/frontend/app/components/Session_/Exceptions/Exceptions.js deleted file mode 100644 index 334e57688..000000000 --- a/frontend/app/components/Session_/Exceptions/Exceptions.js +++ /dev/null @@ -1,159 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import { getRE } from 'App/utils'; -import { - NoContent, - Loader, - Input, - ErrorItem, - SlideModal, - ErrorDetails, - ErrorHeader, - Link, - QuestionMarkHint, - Tabs, -} from 'UI'; -import { fetchErrorStackList } from 'Duck/sessions'; -import { connectPlayer, jump } from 'Player'; -import { error as errorRoute } from 'App/routes'; -import Autoscroll from '../Autoscroll'; -import BottomBlock from '../BottomBlock'; - -@connectPlayer((state) => ({ - logs: state.logListNow, - exceptions: state.exceptionsList, - // exceptionsNow: state.exceptionsListNow, -})) -@connect( - (state) => ({ - session: state.getIn(['sessions', 'current']), - errorStack: state.getIn(['sessions', 'errorStack']), - sourcemapUploaded: state.getIn(['sessions', 'sourcemapUploaded']), - loading: state.getIn(['sessions', 'fetchErrorStackList', 'loading']), - }), - { fetchErrorStackList } -) -export default class Exceptions extends React.PureComponent { - state = { - filter: '', - currentError: null, - }; - - onFilterChange = ({ target: { value } }) => this.setState({ filter: value }); - - setCurrentError = (err) => { - const { session } = this.props; - this.props.fetchErrorStackList(session.sessionId, err.errorId); - this.setState({ currentError: err }); - }; - closeModal = () => this.setState({ currentError: null }); - - render() { - const { exceptions, loading, errorStack, sourcemapUploaded } = this.props; - const { filter, currentError } = this.state; - const filterRE = getRE(filter, 'i'); - - const filtered = exceptions.filter((e) => filterRE.test(e.name) || filterRE.test(e.message)); - - // let lastIndex = -1; - // filtered.forEach((item, index) => { - // if ( - // this.props.exceptionsNow.length > 0 && - // item.time <= this.props.exceptionsNow[this.props.exceptionsNow.length - 1].time - // ) { - // lastIndex = index; - // } - // }); - - return ( - <> - <SlideModal - title={ - currentError && ( - <div className="mb-4"> - <div className="text-xl mb-2"> - <Link to={errorRoute(currentError.errorId)}> - <span className="font-bold">{currentError.name}</span> - </Link> - <span className="ml-2 text-sm color-gray-medium">{currentError.function}</span> - </div> - <div>{currentError.message}</div> - </div> - ) - } - isDisplayed={currentError != null} - content={ - currentError && ( - <div className="px-4"> - <Loader loading={loading}> - <NoContent show={!loading && errorStack.size === 0} title="Nothing found!"> - <ErrorDetails - error={currentError} - errorStack={errorStack} - sourcemapUploaded={sourcemapUploaded} - /> - </NoContent> - </Loader> - </div> - ) - } - onClose={this.closeModal} - /> - <BottomBlock> - <BottomBlock.Header> - <div className="flex items-center"> - <span className="font-semibold color-gray-medium mr-4">Exceptions</span> - </div> - - <div className={'flex items-center justify-between'}> - <Input - className="input-small" - placeholder="Filter by name or message" - icon="search" - iconPosition="left" - name="filter" - onChange={this.onFilterChange} - height={28} - /> - <QuestionMarkHint - className={'mx-4'} - content={ - <> - <a - className="color-teal underline" - target="_blank" - href="https://docs.openreplay.com/installation/upload-sourcemaps" - > - Upload Source Maps{' '} - </a> - and see source code context obtained from stack traces in their original form. - </> - } - /> - </div> - </BottomBlock.Header> - <BottomBlock.Content> - <NoContent size="small" show={filtered.length === 0} title="No recordings found"> - <Autoscroll> - {filtered.map((e, index) => ( - <ErrorItem - onJump={() => jump(e.time)} - error={e} - key={e.key} - // selected={lastIndex === index} - // inactive={index > lastIndex} - onErrorClick={(jsEvent) => { - jsEvent.stopPropagation(); - jsEvent.preventDefault(); - this.setCurrentError(e); - }} - /> - ))} - </Autoscroll> - </NoContent> - </BottomBlock.Content> - </BottomBlock> - </> - ); - } -} diff --git a/frontend/app/components/Session_/Exceptions/Exceptions.tsx b/frontend/app/components/Session_/Exceptions/Exceptions.tsx new file mode 100644 index 000000000..987d0f215 --- /dev/null +++ b/frontend/app/components/Session_/Exceptions/Exceptions.tsx @@ -0,0 +1,125 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { getRE } from 'App/utils'; +import { + NoContent, + Loader, + Input, + ErrorItem, + SlideModal, + ErrorDetails, + Link, + QuestionMarkHint, +} from 'UI'; +import { error as errorRoute } from 'App/routes'; +import Autoscroll from '../Autoscroll'; +import BottomBlock from '../BottomBlock'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; + +interface IProps { + loading: boolean; + sourcemapUploaded: boolean; + errorStack: Record<string, any>; +} + +function Exceptions({ errorStack, sourcemapUploaded, loading }: IProps) { + const { player, store } = React.useContext(PlayerContext); + const { logListNow: logs, exceptionsList: exceptions } = store.get(); + const [filter, setFilter] = React.useState(''); + const [currentError, setCurrentErrorVal] = React.useState(null); + + const onFilterChange = ({ target: { value } }: any) => setFilter(value); + const closeModal = () => setCurrentErrorVal(null); + + const filterRE = getRE(filter, 'i'); + const filtered = exceptions.filter((e: any) => filterRE.test(e.name) || filterRE.test(e.message)); + + return ( + <> + <SlideModal + title={ + currentError && ( + <div className="mb-4"> + <div className="text-xl mb-2"> + <Link to={errorRoute(currentError.errorId)}> + <span className="font-bold">{currentError.name}</span> + </Link> + <span className="ml-2 text-sm color-gray-medium">{currentError.function}</span> + </div> + <div>{currentError.message}</div> + </div> + ) + } + isDisplayed={currentError != null} + content={ + currentError && ( + <div className="px-4"> + <Loader loading={loading}> + <NoContent show={!loading && errorStack.size === 0} title="Nothing found!"> + <ErrorDetails + error={currentError} + // @ts-ignore + errorStack={errorStack} + sourcemapUploaded={sourcemapUploaded} + /> + </NoContent> + </Loader> + </div> + ) + } + onClose={closeModal} + /> + <BottomBlock> + <BottomBlock.Header> + <div className="flex items-center"> + <span className="font-semibold color-gray-medium mr-4">Exceptions</span> + </div> + + <div className={'flex items-center justify-between'}> + <Input + className="input-small" + placeholder="Filter by name or message" + icon="search" + name="filter" + onChange={onFilterChange} + height={28} + /> + <QuestionMarkHint + className={'mx-4'} + content={ + <> + <a + className="color-teal underline" + target="_blank" + href="https://docs.openreplay.com/installation/upload-sourcemaps" + > + Upload Source Maps{' '} + </a> + and see source code context obtained from stack traces in their original form. + </> + } + /> + </div> + </BottomBlock.Header> + <BottomBlock.Content> + <NoContent size="small" show={filtered.length === 0} title="No recordings found"> + <Autoscroll> + {filtered.map((e: any, index) => ( + <React.Fragment key={e.key}> + <ErrorItem onJump={() => player.jump(e.time)} error={e} /> + </React.Fragment> + ))} + </Autoscroll> + </NoContent> + </BottomBlock.Content> + </BottomBlock> + </> + ); +} + +export default connect((state: any) => ({ + errorStack: state.getIn(['sessions', 'errorStack']), + sourcemapUploaded: state.getIn(['sessions', 'sourcemapUploaded']), + loading: state.getIn(['sessions', 'fetchErrorStackList', 'loading']), +}))(observer(Exceptions)); diff --git a/frontend/app/components/Session_/Fetch/Fetch.js b/frontend/app/components/Session_/Fetch/Fetch.DEPRECATED.js similarity index 100% rename from frontend/app/components/Session_/Fetch/Fetch.js rename to frontend/app/components/Session_/Fetch/Fetch.DEPRECATED.js diff --git a/frontend/app/components/Session_/Fetch/components/Headers/Headers.tsx b/frontend/app/components/Session_/Fetch/components/Headers/Headers.tsx index c2ec31a07..17214a215 100644 --- a/frontend/app/components/Session_/Fetch/components/Headers/Headers.tsx +++ b/frontend/app/components/Session_/Fetch/components/Headers/Headers.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { NoContent, TextEllipsis } from 'UI' +import { NoContent } from 'UI' import stl from './headers.module.css' import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; diff --git a/frontend/app/components/Session_/GraphQL/GQLDetails.js b/frontend/app/components/Session_/GraphQL/GQLDetails.js index 4caba50a7..e7b00384e 100644 --- a/frontend/app/components/Session_/GraphQL/GQLDetails.js +++ b/frontend/app/components/Session_/GraphQL/GQLDetails.js @@ -1,13 +1,11 @@ import React from 'react'; -import { JSONTree, Button } from 'UI'; +import { JSONTree } from 'UI'; import cn from 'classnames'; export default class GQLDetails extends React.PureComponent { render() { const { gql: { variables, response, duration, operationKind, operationName }, - nextClick, - prevClick, first = false, last = false, } = this.props; @@ -57,15 +55,6 @@ export default class GQLDetails extends React.PureComponent { </div> </div> </div> - - <div className="flex justify-between absolute bottom-0 left-0 right-0 p-3 border-t bg-white"> - <Button variant="outline" onClick={prevClick} disabled={first}> - Prev - </Button> - <Button variant="outline" onClick={nextClick} disabled={last}> - Next - </Button> - </div> </div> ); } diff --git a/frontend/app/components/Session_/GraphQL/GraphQL.js b/frontend/app/components/Session_/GraphQL/GraphQL.js deleted file mode 100644 index 8c601dba1..000000000 --- a/frontend/app/components/Session_/GraphQL/GraphQL.js +++ /dev/null @@ -1,178 +0,0 @@ -import React from 'react'; -import { NoContent, Input, SlideModal, CloseButton, Button } from 'UI'; -import { getRE } from 'App/utils'; -import { connectPlayer, pause, jump } from 'Player'; -import BottomBlock from '../BottomBlock'; -import TimeTable from '../TimeTable'; -import GQLDetails from './GQLDetails'; -import { renderStart } from 'Components/Session_/Network/NetworkContent'; - -function renderDefaultStatus() { - return '2xx-3xx'; -} - -export function renderName(r) { - return ( - <div className="flex justify-between items-center grow-0 w-full"> - <div>{r.operationName}</div> - <Button - variant="text" - className="right-0 text-xs uppercase p-2 color-gray-500 hover:color-teal" - onClick={(e) => { - e.stopPropagation(); - jump(r.time); - }} - > - Jump - </Button> - </div> - ); -} - -@connectPlayer((state) => ({ - list: state.graphqlList, - listNow: state.graphqlListNow, - time: state.time, - livePlay: state.livePlay, -})) -export default class GraphQL extends React.PureComponent { - state = { - filter: '', - filteredList: this.props.list, - filteredListNow: this.props.listNow, - current: null, - currentIndex: 0, - showFetchDetails: false, - hasNextError: false, - hasPreviousError: false, - lastActiveItem: 0, - }; - - static filterList(list, value) { - const filterRE = getRE(value, 'i'); - - return value - ? list.filter( - (r) => - filterRE.test(r.operationKind) || - filterRE.test(r.operationName) || - filterRE.test(r.variables) - ) - : list; - } - - onFilterChange = ({ target: { value } }) => { - const { list } = this.props; - const filtered = GraphQL.filterList(list, value); - this.setState({ filter: value, filteredList: filtered, currentIndex: 0 }); - }; - - setCurrent = (item, index) => { - if (!this.props.livePlay) { - pause(); - jump(item.time); - } - this.setState({ current: item, currentIndex: index }); - }; - - closeModal = () => this.setState({ current: null, showFetchDetails: false }); - - static getDerivedStateFromProps(nextProps, prevState) { - const { list } = nextProps; - if (nextProps.time) { - const filtered = GraphQL.filterList(list, prevState.filter); - let i = 0; - filtered.forEach((item, index) => { - if (item.time <= nextProps.time) { - i = index; - } - }); - - return { - lastActiveItem: i, - }; - } - } - - render() { - const { current, currentIndex, filteredList, lastActiveItem } = this.state; - - return ( - <React.Fragment> - <SlideModal - size="middle" - right - title={ - <div className="flex justify-between"> - <h1>GraphQL</h1> - <div className="flex items-center"> - <CloseButton onClick={this.closeModal} size="18" className="ml-2" /> - </div> - </div> - } - isDisplayed={current != null} - content={ - current && ( - <GQLDetails - gql={current} - nextClick={this.nextClickHander} - prevClick={this.prevClickHander} - first={currentIndex === 0} - last={currentIndex === filteredList.length - 1} - /> - ) - } - onClose={this.closeModal} - /> - <BottomBlock> - <BottomBlock.Header> - <span className="font-semibold color-gray-medium mr-4">GraphQL</span> - <div className="flex items-center"> - <Input - // className="input-small" - placeholder="Filter by name or type" - icon="search" - iconPosition="left" - name="filter" - onChange={this.onFilterChange} - /> - </div> - </BottomBlock.Header> - <BottomBlock.Content> - <NoContent size="small" title="No recordings found" show={filteredList.length === 0}> - <TimeTable - rows={filteredList} - onRowClick={this.setCurrent} - hoverable - activeIndex={lastActiveItem} - > - {[ - { - label: 'Start', - width: 90, - render: renderStart, - }, - { - label: 'Status', - width: 70, - render: renderDefaultStatus, - }, - { - label: 'Type', - dataKey: 'operationKind', - width: 60, - }, - { - label: 'Name', - width: 240, - render: renderName, - }, - ]} - </TimeTable> - </NoContent> - </BottomBlock.Content> - </BottomBlock> - </React.Fragment> - ); - } -} diff --git a/frontend/app/components/Session_/GraphQL/GraphQL.tsx b/frontend/app/components/Session_/GraphQL/GraphQL.tsx new file mode 100644 index 000000000..6f89a5ec1 --- /dev/null +++ b/frontend/app/components/Session_/GraphQL/GraphQL.tsx @@ -0,0 +1,174 @@ +import React, { useEffect } from 'react'; +import { NoContent, Input, SlideModal, CloseButton, Button } from 'UI'; +import { getRE } from 'App/utils'; +import BottomBlock from '../BottomBlock'; +import TimeTable from '../TimeTable'; +import GQLDetails from './GQLDetails'; +import { renderStart } from 'Components/Session_/Network/NetworkContent'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; + +function renderDefaultStatus() { + return '2xx-3xx'; +} + +export function renderName(r: Record<string, any>) { + const { player } = React.useContext(PlayerContext); + + return ( + <div className="flex justify-between items-center grow-0 w-full"> + <div>{r.operationName}</div> + <Button + variant="text" + className="right-0 text-xs uppercase p-2 color-gray-500 hover:color-teal" + onClick={(e: React.MouseEvent<HTMLButtonElement>) => { + e.stopPropagation(); + player.jump(r.time); + }} + > + Jump + </Button> + </div> + ); +} + +function GraphQL() { + const { player, store } = React.useContext(PlayerContext); + + const { graphqlList: list, graphqlListNow: listNow, time, livePlay } = store.get(); + + const defaultState = { + filter: '', + filteredList: list, + filteredListNow: listNow, + // @ts-ignore + current: null, + currentIndex: 0, + showFetchDetails: false, + hasNextError: false, + hasPreviousError: false, + lastActiveItem: 0, + }; + + const [state, setState] = React.useState(defaultState); + + const filterList = (list: any, value: string) => { + const filterRE = getRE(value, 'i'); + + return value + ? list.filter( + (r: any) => + filterRE.test(r.operationKind) || + filterRE.test(r.operationName) || + filterRE.test(r.variables) + ) + : list; + }; + + const onFilterChange = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => { + const filtered = filterList(list, value); + setState((prevState) => ({ + ...prevState, + filter: value, + filteredList: filtered, + currentIndex: 0, + })); + }; + + const setCurrent = (item: any, index: number) => { + if (!livePlay) { + player.pause(); + player.jump(item.time); + } + setState((prevState) => ({ ...prevState, current: item, currentIndex: index })); + }; + + const closeModal = () => + setState((prevState) => ({ ...prevState, current: null, showFetchDetails: false })); + + useEffect(() => { + const filtered = filterList(listNow, state.filter); + if (filtered.length !== lastActiveItem) { + setState((prevState) => ({ ...prevState, lastActiveItem: listNow.length })); + } + }, [time]); + + const { current, currentIndex, filteredList, lastActiveItem } = state; + + return ( + <React.Fragment> + <SlideModal + size="middle" + right + title={ + <div className="flex justify-between"> + <h1>GraphQL</h1> + <div className="flex items-center"> + <CloseButton onClick={closeModal} size="18" className="ml-2" /> + </div> + </div> + } + isDisplayed={current != null} + content={ + current && ( + <GQLDetails + gql={current} + first={currentIndex === 0} + last={currentIndex === filteredList.length - 1} + /> + ) + } + onClose={closeModal} + /> + <BottomBlock> + <BottomBlock.Header> + <span className="font-semibold color-gray-medium mr-4">GraphQL</span> + <div className="flex items-center"> + <Input + // className="input-small" + placeholder="Filter by name or type" + icon="search" + name="filter" + onChange={onFilterChange} + /> + </div> + </BottomBlock.Header> + <BottomBlock.Content> + <NoContent size="small" title="No recordings found" show={filteredList.length === 0}> + <TimeTable + rows={filteredList} + onRowClick={setCurrent} + hoverable + activeIndex={lastActiveItem} + > + {[ + { + label: 'Start', + width: 90, + render: renderStart, + }, + { + label: 'Status', + width: 70, + render: renderDefaultStatus, + }, + { + label: 'Type', + dataKey: 'operationKind', + width: 60, + }, + { + label: 'Name', + width: 240, + render: renderName, + }, + ]} + </TimeTable> + </NoContent> + </BottomBlock.Content> + </BottomBlock> + </React.Fragment> + ); +} + +export default observer(GraphQL); diff --git a/frontend/app/components/Session_/HeaderInfo.js b/frontend/app/components/Session_/HeaderInfo.js deleted file mode 100644 index c1cb5ae14..000000000 --- a/frontend/app/components/Session_/HeaderInfo.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { Icon } from 'UI'; -import styles from './headerInfo.module.css'; - -const HeaderInfo = ({ icon, label }) => { - return ( - <div className="flex items-center mx-4"> - <Icon name={ icon } size="18" color="color-dark" /> - <div className="ml-2 mt-1 font-sm font-normal color-gray-darkest text-sm">{ label }</div> - </div> - ); -}; - -export default HeaderInfo; diff --git a/frontend/app/components/Session_/Inspector/index.js b/frontend/app/components/Session_/Inspector/index.js index f76834fee..1f13b847f 100644 --- a/frontend/app/components/Session_/Inspector/index.js +++ b/frontend/app/components/Session_/Inspector/index.js @@ -1,27 +1,27 @@ import React, { useEffect, useState, useRef } from 'react'; -import { toggleInspectorMode, markElement } from 'Player'; import ElementView from './ElementView'; import BottomBlock from '../BottomBlock'; -import stl from './inspector.module.css' +import stl from './inspector.module.css'; +import { PlayerContext } from 'App/components/Session/playerContext'; // TODO: refactor: use Layout from the Sessions and put everything there under the WebPlayer folder -// function onMount(element, setOpen) { // TODO: through the MobX -// element.setOpen = setOpen; -// } +export default function Inspector() { + const { player } = React.useContext(PlayerContext); + const toggleInspectorMode = player.toggleInspectorMode; + const markElement = player.mark; -export default function Inspector () { const [doc, setDoc] = useState(null); const [openChain, setOpenChain] = useState([]); const [selectedElement, _setSelectedElement] = useState(null); const selectedElementRef = useRef(selectedElement); - const setSelectedElement = elem => { + const setSelectedElement = (elem) => { selectedElementRef.current = elem; _setSelectedElement(elem); - } + }; - useEffect(() => { + useEffect(() => { const doc = toggleInspectorMode(true, ({ target }) => { const openChain = []; let currentTarget = target; @@ -33,9 +33,9 @@ export default function Inspector () { setSelectedElement(target); }); setDoc(doc); - setOpenChain([ doc.documentElement ]); + setOpenChain([doc.documentElement]); - const onKeyPress = e => { + const onKeyPress = (e) => { if (e.key === 'Backspace' || e.key === 'Delete') { const elem = selectedElementRef.current; if (elem !== null && elem.parentElement !== null) { @@ -43,30 +43,30 @@ export default function Inspector () { setSelectedElement(null); } } - } - window.addEventListener("keydown", onKeyPress); + }; + window.addEventListener('keydown', onKeyPress); return () => { toggleInspectorMode(false); - window.removeEventListener("keydown", onKeyPress); - } + window.removeEventListener('keydown', onKeyPress); + }; }, []); - if (!doc) return null; - return ( - <BottomBlock> + if (!doc) return null; + return ( + <BottomBlock> <BottomBlock.Content> - <div onMouseLeave={ () => markElement(null) } className={stl.wrapper}> - <ElementView - element={ doc.documentElement } + <div onMouseLeave={() => markElement(null)} className={stl.wrapper}> + <ElementView + element={doc.documentElement} level={0} context={doc.defaultView} - openChain={ openChain } - selectedElement={ selectedElement } - setSelectedElement={ setSelectedElement } - onHover={ markElement } + openChain={openChain} + selectedElement={selectedElement} + setSelectedElement={setSelectedElement} + onHover={(e) => markElement(e)} /> </div> </BottomBlock.Content> </BottomBlock> - ); -} \ No newline at end of file + ); +} diff --git a/frontend/app/components/Session_/Issues/IssueDetails.js b/frontend/app/components/Session_/Issues/IssueDetails.js index 9c50c858b..f2f8f5d0c 100644 --- a/frontend/app/components/Session_/Issues/IssueDetails.js +++ b/frontend/app/components/Session_/Issues/IssueDetails.js @@ -53,5 +53,5 @@ export default connect(state => ({ users: state.getIn(['assignments', 'users']), loading: state.getIn(['assignments', 'fetchAssignment', 'loading']), issueTypeIcons: state.getIn(['assignments', 'issueTypeIcons']), - issuesIntegration: state.getIn([ 'issues', 'list']).first() || {}, + issuesIntegration: state.getIn([ 'issues', 'list'])[0] || {}, }))(IssueDetails); diff --git a/frontend/app/components/Session_/Issues/IssueForm.js b/frontend/app/components/Session_/Issues/IssueForm.js index 43a7f382a..2010a2fe5 100644 --- a/frontend/app/components/Session_/Issues/IssueForm.js +++ b/frontend/app/components/Session_/Issues/IssueForm.js @@ -1,7 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; import { Form, Input, Button, CircularLoader, Loader } from 'UI'; -//import { } from 'Duck/issues'; import { addActivity, init, edit, fetchAssignments, fetchMeta } from 'Duck/assignments'; import Select from 'Shared/Select'; @@ -119,7 +118,6 @@ class IssueForm extends React.PureComponent { selection name="assignee" options={userOptions} - // value={ instance.assignee } fluid onChange={this.writeOption} placeholder="Select a user" @@ -153,7 +151,7 @@ class IssueForm extends React.PureComponent { <Button loading={creating} variant="primary" - disabled={!instance.validate()} + disabled={!instance.isValid} className="float-left mr-2" type="submit" > diff --git a/frontend/app/components/Session_/Issues/IssueHeader.js b/frontend/app/components/Session_/Issues/IssueHeader.js index e98288589..6d666382b 100644 --- a/frontend/app/components/Session_/Issues/IssueHeader.js +++ b/frontend/app/components/Session_/Issues/IssueHeader.js @@ -1,7 +1,5 @@ import React from 'react'; -import { Icon, Input } from 'UI'; -import ActiveIssueClose from './ActiveIssueClose'; -import stl from './issueHeader.module.css'; +import { Icon } from 'UI'; const GotoSessionLink = props => ( <a className="flex items-center absolute right-0 mr-3 cursor-pointer"> diff --git a/frontend/app/components/Session_/Issues/Issues.js b/frontend/app/components/Session_/Issues/Issues.js index 2cc309624..7de41b097 100644 --- a/frontend/app/components/Session_/Issues/Issues.js +++ b/frontend/app/components/Session_/Issues/Issues.js @@ -1,9 +1,8 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Icon, Popover, Button } from 'UI'; +import { Popover, Button } from 'UI'; import IssuesModal from './IssuesModal'; import { fetchProjects, fetchMeta } from 'Duck/assignments'; -import stl from './issues.module.css'; @connect( (state) => ({ @@ -15,9 +14,7 @@ import stl from './issues.module.css'; fetchIssueLoading: state.getIn(['assignments', 'fetchAssignment', 'loading']), fetchIssuesLoading: state.getIn(['assignments', 'fetchAssignments', 'loading']), projectsLoading: state.getIn(['assignments', 'fetchProjects', 'loading']), - issuesIntegration: state.getIn(['issues', 'list']).first() || {}, - - jiraConfig: state.getIn(['issues', 'list']).first(), + issuesIntegration: state.getIn(['issues', 'list']) || {}, issuesFetched: state.getIn(['issues', 'issuesFetched']), }), { fetchMeta, fetchProjects } @@ -58,13 +55,9 @@ class Issues extends React.Component { render() { const { sessionId, - isModalDisplayed, - projectsLoading, - metaLoading, - fetchIssuesLoading, issuesIntegration, } = this.props; - const provider = issuesIntegration.provider; + const provider = issuesIntegration.first()?.provider || ''; return ( <Popover @@ -80,13 +73,6 @@ class Issues extends React.Component { Create Issue </Button> </div> - {/* <div - className="flex items-center cursor-pointer" - disabled={!isModalDisplayed && (metaLoading || fetchIssuesLoading || projectsLoading)} - > - <Icon name={`integrations/${provider === 'jira' ? 'jira' : 'github'}`} size="16" /> - <span className="ml-2 whitespace-nowrap">Create Issue</span> - </div> */} </Popover> ); } diff --git a/frontend/app/components/Session_/LongTasks/LongTasks.js b/frontend/app/components/Session_/LongTasks/LongTasks.js deleted file mode 100644 index fd3b4cc17..000000000 --- a/frontend/app/components/Session_/LongTasks/LongTasks.js +++ /dev/null @@ -1,122 +0,0 @@ -import React from 'react'; -import { NoContent, Input, QuestionMarkHint } from 'UI' -import { getRE } from 'App/utils'; -import { connectPlayer, jump } from 'Player'; -import BottomBlock from '../BottomBlock'; -import TimeTable from '../TimeTable'; - - -const CONTEXTS = [ "unknown", "self", "same-origin-ancestor", "same-origin-descendant", "same-origin", "cross-origin-ancestor", "cross-origin-descendant", "cross-origin-unreachable", "multiple-contexts" ]; -const CONTAINER_TYPES = [ "window", "iframe", "embed", "object" ]; - -function renderContext({ context }) { - return CONTEXTS[ context ]; -} - -function renderDuration({ duration }) { - return `${ duration }ms`; -} - -function renderContainerType({ containerType }) { - return CONTAINER_TYPES[ containerType ] -} - -@connectPlayer(state => ({ - list: state.longtasksList, - time: state.time, -})) -export default class GraphQL extends React.PureComponent { - state = { - filter: "", - } - onFilterChange = ({ target: { value } }) => this.setState({ filter: value }) - - jump = ({ time }) => { - jump(time); - } - - render() { - const { list, time} = this.props; - const { filter, current } = this.state; - const filterRE = getRE(filter, 'i'); - const filtered = list - .filter(({ containerType, context, containerName = "", containerId = "", containerSrc="" }) => - filterRE.test(containerName) || - filterRE.test(containerId) || - filterRE.test(containerSrc) || - filterRE.test(CONTEXTS[ context ]) || - filterRE.test(CONTAINER_TYPES[ containerType ])); - const lastIndex = filtered.filter(item => item.time <= time).length - 1; - return ( - <BottomBlock> - <BottomBlock.Header> - <span className="font-semibold color-gray-medium mr-4">Long Tasks</span> - <div className="flex items-center"> - <Input - className="input-small mr-3" - style={{ width: "350px" }} - placeholder="Filter by Context or Container Type/Id/Src" - icon="search" - iconPosition="left" - name="filter" - onChange={ this.onFilterChange } - /> - <QuestionMarkHint - content={ - <> - <a className="color-teal underline mr-2" target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Long_Tasks_API">Learn more </a> - about Long Tasks API - </> - } - // className="mr-4" - /> - </div> - </BottomBlock.Header> - <BottomBlock.Content> - <NoContent - size="small" - title="No recordings found" - show={ filtered.length === 0} - > - <TimeTable - rows={ filtered } - onRowClick={ this.jump } - hoverable - activeIndex={lastIndex} - > - {[ - { - label: "Context", - render: renderContext, - width: 140, - }, { - label: "Container Type", - width: 110, - render: renderContainerType, - }, - // { - // label: "ID", - // width: 70, - // dataKey: "containerId" - // }, { - // label: "Name", - // width: 70, - // dataKey: "containerName" - // }, { - // label: "SRC", - // width: 70, - // dataKey: "containerSrc" - // }, - { - label: "Duration", - width: 100, - render: renderDuration, - } - ]} - </TimeTable> - </NoContent> - </BottomBlock.Content> - </BottomBlock> - ); - } -} diff --git a/frontend/app/components/Session_/LongTasks/index.js b/frontend/app/components/Session_/LongTasks/index.js deleted file mode 100644 index 521f0e2a7..000000000 --- a/frontend/app/components/Session_/LongTasks/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './LongTasks'; diff --git a/frontend/app/components/Session_/Multiview/EmptyTile.tsx b/frontend/app/components/Session_/Multiview/EmptyTile.tsx new file mode 100644 index 000000000..e92657603 --- /dev/null +++ b/frontend/app/components/Session_/Multiview/EmptyTile.tsx @@ -0,0 +1,16 @@ +import React from 'react' +import { InactiveTab } from 'App/components/Session_/Player/Controls/AssistSessionsTabs'; + +function EmptyTile({ onClick }: { onClick: () => void }) { + return ( + <div + className="border hover:bg-active-blue hover:border-borderColor-primary flex flex-col gap-2 items-center justify-center cursor-pointer" + onClick={onClick} + > + <InactiveTab classNames="!bg-gray-bg w-12" /> + Add Session + </div> + ); +} + +export default EmptyTile; diff --git a/frontend/app/components/Session_/Multiview/Multiview.tsx b/frontend/app/components/Session_/Multiview/Multiview.tsx new file mode 100644 index 000000000..098fa3267 --- /dev/null +++ b/frontend/app/components/Session_/Multiview/Multiview.tsx @@ -0,0 +1,137 @@ +import React from 'react'; +import { useStore } from 'App/mstore'; +import { BackLink } from 'UI'; +import { observer } from 'mobx-react-lite'; +import { connect } from 'react-redux'; +import { fetchSessions, customSetSessions } from 'Duck/liveSearch'; +import { useHistory, useParams } from 'react-router-dom'; +import { liveSession, assist, withSiteId, multiview } from 'App/routes'; +import AssistSessionsModal from 'App/components/Session_/Player/Controls/AssistSessionsModal'; +import { useModal } from 'App/components/Modal'; +import LivePlayer from 'App/components/Session/LivePlayer'; +import EmptyTile from './EmptyTile' +import SessionTileFooter from './SessionTileFooter' + +function Multiview({ + total, + fetchSessions, + siteId, + assistCredendials, + customSetSessions, +}: { + total: number; + customSetSessions: (data: any) => void; + fetchSessions: (filter: any) => void; + siteId: string; + assistCredendials: any; + list: Record<string, any>[]; +}) { + const { showModal, hideModal } = useModal(); + + const { assistMultiviewStore } = useStore(); + const history = useHistory(); + // @ts-ignore + const { sessionsquery } = useParams(); + + const onSessionsChange = (sessions: Record<string, any>[]) => { + const sessionIdQuery = encodeURIComponent(sessions.map((s) => s.sessionId).join(',')); + return history.replace(withSiteId(multiview(sessionIdQuery), siteId)); + }; + + React.useEffect(() => { + assistMultiviewStore.setOnChange(onSessionsChange); + + if (sessionsquery) { + const sessionIds = decodeURIComponent(sessionsquery).split(','); + // preset + assistMultiviewStore.presetSessions(sessionIds).then((data) => { + customSetSessions(data); + }); + } else { + fetchSessions({}); + } + }, []); + + const openLiveSession = (e: React.MouseEvent, sessionId: string) => { + e.stopPropagation(); + assistMultiviewStore.setActiveSession(sessionId); + history.push(withSiteId(liveSession(sessionId)+'?multi=true', siteId)); + }; + + const returnToList = () => { + assistMultiviewStore.reset() + history.push(withSiteId(assist(), siteId)); + }; + + const openListModal = () => { + showModal(<AssistSessionsModal onAdd={hideModal} />, { right: true, width: 700 }); + }; + + const replaceSession = (e: React.MouseEvent, sessionId: string) => { + e.stopPropagation(); + showModal(<AssistSessionsModal onAdd={hideModal} replaceTarget={sessionId} />, { right: true, width: 700 }); + }; + + const deleteSession = (e: React.MouseEvent, sessionId: string) => { + e.stopPropagation(); + assistMultiviewStore.removeSession(sessionId); + }; + + const emptySpace = 4 - assistMultiviewStore.sessions.length; + + const placeholder = emptySpace > 0 ? new Array(emptySpace).fill(0) : [] + + return ( + <div className="w-screen h-screen flex flex-col"> + <div className="w-full p-4 flex justify-between items-center"> + <div> + {/* @ts-ignore */} + <BackLink label="Exit to sessions list" onClick={returnToList} /> + </div> + <div>{`Watching ${assistMultiviewStore.sessions.length} of ${total} Live Sessions`}</div> + </div> + <div className="w-full h-full grid grid-cols-2 grid-rows-2"> + {assistMultiviewStore.sortedSessions.map((session: Record<string, any>) => ( + <div + key={session.key} + className="border hover:bg-active-blue hover:border-borderColor-primary relative group cursor-pointer" + > + <div onClick={(e) => openLiveSession(e, session.sessionId)} className="w-full h-full"> + {session.agentToken ? ( + <LivePlayer + isMultiview + customSession={session} + customAssistCredendials={assistCredendials} + /> + ) : ( + <div>Loading session</div> + )} + </div> + <SessionTileFooter + userDisplayName={session.userDisplayName} + sessionId={session.sessionId} + replaceSession={replaceSession} + deleteSession={deleteSession} + /> + </div> + ))} + {placeholder.map((_, i) => ( + <React.Fragment key={i}> + <EmptyTile onClick={openListModal} /> + </React.Fragment> + ))} + </div> + </div> + ); +} + +export default connect( + (state: any) => ({ + total: state.getIn(['liveSearch', 'total']), + siteId: state.getIn(['site', 'siteId']), + }), + { + fetchSessions, + customSetSessions, + } +)(observer(Multiview)); diff --git a/frontend/app/components/Session_/Multiview/SessionTileFooter.tsx b/frontend/app/components/Session_/Multiview/SessionTileFooter.tsx new file mode 100644 index 000000000..da6c36ca5 --- /dev/null +++ b/frontend/app/components/Session_/Multiview/SessionTileFooter.tsx @@ -0,0 +1,36 @@ +import React from 'react' +import { Icon } from 'UI' + +function SessionTileFooter({ + userDisplayName, + sessionId, + replaceSession, + deleteSession, +}: { + userDisplayName: string; + sessionId: string; + replaceSession: (e: any, id: string) => void; + deleteSession: (e: any, id: string) => void; +}) { + return ( + <div className="absolute z-10 cursor-default bottom-0 w-full h-8 left-0 px-4 opacity-70 bg-gray-darkest text-white flex items-center justify-between"> + <div>{userDisplayName}</div> + <div className="hidden group-hover:flex h-full items-center gap-4"> + <div + className="cursor-pointer hover:font-semibold border-l flex items-center justify-center h-full border-r border-white px-2" + onClick={(e) => replaceSession(e, sessionId)} + > + Replace Session + </div> + <div + className="cursor-pointer hover:font-semibold" + onClick={(e) => deleteSession(e, sessionId)} + > + <Icon name="trash" size={18} color="white" /> + </div> + </div> + </div> + ); +} + +export default SessionTileFooter; diff --git a/frontend/app/components/Session_/Network/Network.js b/frontend/app/components/Session_/Network/Network.js deleted file mode 100644 index f824cba09..000000000 --- a/frontend/app/components/Session_/Network/Network.js +++ /dev/null @@ -1,148 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { connectPlayer, jump, pause } from 'Player'; -import { Tooltip, Button, TextEllipsis } from 'UI'; -import { getRE } from 'App/utils'; -import { TYPES } from 'Types/session/resource'; -import stl from './network.module.css'; -import NetworkContent from './NetworkContent'; -import { connect } from 'react-redux'; -import { setTimelinePointer } from 'Duck/sessions'; - -const ALL = 'ALL'; -const XHR = 'xhr'; -const JS = 'js'; -const CSS = 'css'; -const IMG = 'img'; -const MEDIA = 'media'; -const OTHER = 'other'; - -const TAB_TO_TYPE_MAP = { - [XHR]: TYPES.XHR, - [JS]: TYPES.JS, - [CSS]: TYPES.CSS, - [IMG]: TYPES.IMG, - [MEDIA]: TYPES.MEDIA, - [OTHER]: TYPES.OTHER, -}; - -export function renderName(r) { - return ( - <div className="flex justify-between items-center grow-0 w-full"> - <Tooltip - style={{ maxWidth: '75%' }} - title={<div className={stl.popupNameContent}>{r.url}</div>} - > - <TextEllipsis>{r.name}</TextEllipsis> - </Tooltip> - </div> - ); -} - -export function renderDuration(r) { - if (!r.success) return 'x'; - - const text = `${Math.round(r.duration)}ms`; - if (!r.isRed() && !r.isYellow()) return text; - - let tooltipText; - let className = 'w-full h-full flex items-center '; - if (r.isYellow()) { - tooltipText = 'Slower than average'; - className += 'warn color-orange'; - } else { - tooltipText = 'Much slower than average'; - className += 'error color-red'; - } - - return ( - <Tooltip title={tooltipText}> - <div className={cn(className, stl.duration)}> {text} </div> - </Tooltip> - ); -} - -@connectPlayer((state) => ({ - location: state.location, - resources: state.resourceList, - domContentLoadedTime: state.domContentLoadedTime, - loadTime: state.loadTime, - // time: state.time, - playing: state.playing, - domBuildingTime: state.domBuildingTime, - fetchPresented: state.fetchList.length > 0, - listNow: state.resourceListNow, -})) -@connect( - (state) => ({ - timelinePointer: state.getIn(['sessions', 'timelinePointer']), - }), - { setTimelinePointer } -) -export default class Network extends React.PureComponent { - state = { - filter: '', - filteredList: this.props.resources, - activeTab: ALL, - currentIndex: 0, - }; - - onRowClick = (e, index) => { - // no action for direct click on network requests (so far), there is a jump button, and we don't have more information for than is already displayed in the table - }; - - onTabClick = (activeTab) => this.setState({ activeTab }); - - onFilterChange = (e, { value }) => { - const { resources } = this.props; - const filterRE = getRE(value, 'i'); - const filtered = resources.filter( - ({ type, name }) => - filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[activeTab]) - ); - - this.setState({ filter: value, filteredList: value ? filtered : resources, currentIndex: 0 }); - }; - - static getDerivedStateFromProps(nextProps, prevState) { - const { filteredList } = prevState; - if (nextProps.timelinePointer) { - const activeItem = filteredList.find((r) => r.time >= nextProps.timelinePointer.time); - return { - currentIndex: activeItem ? filteredList.indexOf(activeItem) : filteredList.length - 1, - }; - } - } - - render() { - const { location, domContentLoadedTime, loadTime, domBuildingTime, fetchPresented, listNow } = - this.props; - const { filteredList } = this.state; - const resourcesSize = filteredList.reduce( - (sum, { decodedBodySize }) => sum + (decodedBodySize || 0), - 0 - ); - const transferredSize = filteredList.reduce( - (sum, { headerSize, encodedBodySize }) => sum + (headerSize || 0) + (encodedBodySize || 0), - 0 - ); - - return ( - <React.Fragment> - <NetworkContent - // time = { time } - location={location} - resources={filteredList} - domContentLoadedTime={domContentLoadedTime} - loadTime={loadTime} - domBuildingTime={domBuildingTime} - fetchPresented={fetchPresented} - resourcesSize={resourcesSize} - transferredSize={transferredSize} - onRowClick={this.onRowClick} - currentIndex={listNow.length - 1} - /> - </React.Fragment> - ); - } -} diff --git a/frontend/app/components/Session_/Network/NetworkContent.js b/frontend/app/components/Session_/Network/NetworkContent.js index 5f335dbf3..f55f5e407 100644 --- a/frontend/app/components/Session_/Network/NetworkContent.js +++ b/frontend/app/components/Session_/Network/NetworkContent.js @@ -1,9 +1,8 @@ import React from 'react'; import cn from 'classnames'; -// import { connectPlayer } from 'Player'; -import { QuestionMarkHint, Tooltip, Tabs, Input, NoContent, Icon, Toggler, Button } from 'UI'; +import { QuestionMarkHint, Tooltip, Tabs, Input, NoContent, Icon, Toggler } from 'UI'; import { getRE } from 'App/utils'; -import { TYPES } from 'Types/session/resource'; +import { ResourceType } from 'Player'; import { formatBytes } from 'App/utils'; import { formatMs } from 'App/date'; @@ -12,7 +11,6 @@ import BottomBlock from '../BottomBlock'; import InfoLine from '../BottomBlock/InfoLine'; import stl from './network.module.css'; import { Duration } from 'luxon'; -import { jump } from 'Player'; const ALL = 'ALL'; const XHR = 'xhr'; @@ -23,12 +21,12 @@ const MEDIA = 'media'; const OTHER = 'other'; const TAB_TO_TYPE_MAP = { - [XHR]: TYPES.XHR, - [JS]: TYPES.JS, - [CSS]: TYPES.CSS, - [IMG]: TYPES.IMG, - [MEDIA]: TYPES.MEDIA, - [OTHER]: TYPES.OTHER, + [XHR]: ResourceType.XHR, + [JS]: ResourceType.SCRIPT, + [CSS]: ResourceType.CSS, + [IMG]: ResourceType.IMG, + [MEDIA]: ResourceType.MEDIA, + [OTHER]: ResourceType.OTHER, }; const TABS = [ALL, XHR, JS, CSS, IMG, MEDIA, OTHER].map((tab) => ({ text: tab, @@ -78,15 +76,16 @@ const renderXHRText = () => ( <QuestionMarkHint content={ <> - Use our{' '} + Configure{' '} <a className="color-teal underline" target="_blank" - href="https://docs.openreplay.com/plugins/fetch" + href="https://docs.openreplay.com/installation/network-options" > - Fetch plugin + Configure </a> - {' to capture HTTP requests and responses, including status codes and bodies.'} <br /> + network capturing + {' to see fetch/XHR requests and response payloads.'} <br /> We also provide{' '} <a className="color-teal underline" @@ -112,8 +111,6 @@ function renderSize(r) { content = 'Not captured'; } else { const headerSize = r.headerSize || 0; - const encodedSize = r.encodedBodySize || 0; - const transferred = headerSize + encodedSize; const showTransferred = r.headerSize != null; triggerText = formatBytes(r.decodedBodySize); @@ -138,11 +135,11 @@ export function renderDuration(r) { if (!r.success) return 'x'; const text = `${Math.floor(r.duration)}ms`; - if (!r.isRed() && !r.isYellow()) return text; + if (!r.isRed && !r.isYellow) return text; let tooltipText; let className = 'w-full h-full flex items-center '; - if (r.isYellow()) { + if (r.isYellow) { tooltipText = 'Slower than average'; className += 'warn color-orange'; } else { @@ -234,7 +231,6 @@ export default class NetworkContent extends React.PureComponent { className="input-small" placeholder="Filter by name" icon="search" - iconPosition="left" name="filter" onChange={this.onFilterChange} height={28} diff --git a/frontend/app/components/Session_/Network/index.js b/frontend/app/components/Session_/Network/index.js deleted file mode 100644 index 446e76ea6..000000000 --- a/frontend/app/components/Session_/Network/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './Network'; -export * from './Network'; diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx index de3cbdf5f..ce5edcba1 100644 --- a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -1,6 +1,5 @@ -import { connectPlayer } from 'Player'; -import { toggleBottomBlock } from 'Duck/components/player'; import React, { useEffect } from 'react'; +import { toggleBottomBlock } from 'Duck/components/player'; import BottomBlock from '../BottomBlock'; import EventRow from './components/EventRow'; import { TYPES } from 'Types/session/event'; @@ -10,40 +9,39 @@ import FeatureSelection, { HELP_MESSAGE } from './components/FeatureSelection/Fe import TimelinePointer from './components/TimelinePointer'; import VerticalPointerLine from './components/VerticalPointerLine'; import cn from 'classnames'; -// import VerticalLine from './components/VerticalLine'; import OverviewPanelContainer from './components/OverviewPanelContainer'; import { NoContent, Icon } from 'UI'; +import { observer } from 'mobx-react-lite'; +import { PlayerContext } from 'App/components/Session/playerContext'; -interface Props { - resourceList: any[]; - exceptionsList: any[]; - eventsList: any[]; - toggleBottomBlock: any; - stackEventList: any[]; - issuesList: any[]; - performanceChartData: any; - endTime: number; - fetchPresented?: boolean; -} -function OverviewPanel(props: Props) { - const { fetchPresented = false } = props; +function OverviewPanel({ issuesList }: { issuesList: Record<string, any>[] }) { + const { store } = React.useContext(PlayerContext) const [dataLoaded, setDataLoaded] = React.useState(false); const [selectedFeatures, setSelectedFeatures] = React.useState([ 'PERFORMANCE', 'ERRORS', - // 'EVENTS', 'NETWORK', ]); - const resources: any = React.useMemo(() => { const { - resourceList, - exceptionsList, - eventsList, - stackEventList, - issuesList, + endTime, performanceChartData, - } = props; + stackList: stackEventList, + eventList: eventsList, + exceptionsList, + resourceList: resourceListUnmap, + fetchList, + graphqlList, + } = store.get() + + const fetchPresented = fetchList.length > 0; + + const resourceList = resourceListUnmap + .filter((r: any) => r.isRed || r.isYellow) + .concat(fetchList.filter((i: any) => parseInt(i.status) >= 400)) + .concat(graphqlList.filter((i: any) => parseInt(i.status) >= 400)) + + const resources: any = React.useMemo(() => { return { NETWORK: resourceList, ERRORS: exceptionsList, @@ -59,26 +57,26 @@ function OverviewPanel(props: Props) { } if ( - props.resourceList.length > 0 || - props.exceptionsList.length > 0 || - props.eventsList.length > 0 || - props.stackEventList.length > 0 || - props.issuesList.length > 0 || - props.performanceChartData.length > 0 + resourceList.length > 0 || + exceptionsList.length > 0 || + eventsList.length > 0 || + stackEventList.length > 0 || + issuesList.length > 0 || + performanceChartData.length > 0 ) { setDataLoaded(true); } }, [ - props.resourceList, - props.exceptionsList, - props.eventsList, - props.stackEventList, - props.performanceChartData, + resourceList, + issuesList, + exceptionsList, + eventsList, + stackEventList, + performanceChartData, ]); return ( - <Wrapper {...props}> - <BottomBlock style={{ height: '245px' }}> + <BottomBlock style={{ height: '100%' }}> <BottomBlock.Header> <span className="font-semibold color-gray-medium mr-4">X-RAY</span> <div className="flex items-center h-20"> @@ -86,16 +84,18 @@ function OverviewPanel(props: Props) { </div> </BottomBlock.Header> <BottomBlock.Content> - <OverviewPanelContainer endTime={props.endTime}> - <TimelineScale endTime={props.endTime} /> + <OverviewPanelContainer endTime={endTime}> + <TimelineScale endTime={endTime} /> <div - style={{ width: 'calc(100vw - 1rem)', margin: '0 auto', height: '187px' }} + // style={{ width: '100%', height: '187px', overflow: 'hidden' }} + style={{ width: 'calc(100vw - 1rem)', margin: '0 auto' }} className="transition relative" > <NoContent show={selectedFeatures.length === 0} + style={{ height: '60px', minHeight: 'unset', padding: 0 }} title={ - <div className="flex items-center mt-16"> + <div className="flex items-center"> <Icon name="info-circle" className="mr-2" size="18" /> Select a debug option to visualize on timeline. </div> @@ -118,7 +118,7 @@ function OverviewPanel(props: Props) { fetchPresented={fetchPresented} /> )} - endTime={props.endTime} + endTime={endTime} message={HELP_MESSAGE[feature]} /> </div> @@ -128,32 +128,16 @@ function OverviewPanel(props: Props) { </OverviewPanelContainer> </BottomBlock.Content> </BottomBlock> - </Wrapper> ); } export default connect( (state: any) => ({ - issuesList: state.getIn(['sessions', 'current', 'issues']), + issuesList: state.getIn(['sessions', 'current']).issues, }), { toggleBottomBlock, } )( - connectPlayer((state: any) => ({ - fetchPresented: state.fetchList.length > 0, - resourceList: state.resourceList - .filter((r: any) => r.isRed() || r.isYellow()) - .concat(state.fetchList.filter((i: any) => parseInt(i.status) >= 400)) - .concat(state.graphqlList.filter((i: any) => parseInt(i.status) >= 400)), - exceptionsList: state.exceptionsList, - eventsList: state.eventList, - stackEventList: state.stackList, - performanceChartData: state.performanceChartData, - endTime: state.endTime, - }))(OverviewPanel) -); - -const Wrapper = React.memo((props: any) => { - return <>{props.children}</>; -}); + observer(OverviewPanel) +) diff --git a/frontend/app/components/Session_/OverviewPanel/components/FeatureSelection/FeatureSelection.tsx b/frontend/app/components/Session_/OverviewPanel/components/FeatureSelection/FeatureSelection.tsx index bf4599e10..8d76a3070 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/FeatureSelection/FeatureSelection.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/FeatureSelection/FeatureSelection.tsx @@ -22,7 +22,7 @@ interface Props { function FeatureSelection(props: Props) { const { list } = props; const features = [NETWORK, ERRORS, EVENTS, CLICKRAGE, PERFORMANCE]; - const disabled = list.length >= 3; + const disabled = list.length >= 5; return ( <React.Fragment> diff --git a/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/OverviewPanelContainer.tsx b/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/OverviewPanelContainer.tsx index e017fe1db..8b802953d 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/OverviewPanelContainer.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/OverviewPanelContainer/OverviewPanelContainer.tsx @@ -1,48 +1,33 @@ import React from 'react'; import VerticalLine from '../VerticalLine'; -import { connectPlayer, Controls } from 'Player'; +import { PlayerContext } from 'App/components/Session/playerContext'; + interface Props { - children: React.ReactNode; - endTime: number; + children: React.ReactNode; + endTime: number; } const OverviewPanelContainer = React.memo((props: Props) => { - const { endTime } = props; - const [mouseX, setMouseX] = React.useState(0); - const [mouseIn, setMouseIn] = React.useState(false); - const onClickTrack = (e: any) => { - const p = e.nativeEvent.offsetX / e.target.offsetWidth; - const time = Math.max(Math.round(p * endTime), 0); - if (time) { - Controls.jump(time); - } - }; + const { player } = React.useContext(PlayerContext) - // const onMouseMoveCapture = (e: any) => { - // if (!mouseIn) { - // return; - // } - // const p = e.nativeEvent.offsetX / e.target.offsetWidth; - // setMouseX(p * 100); - // }; + const { endTime } = props; + const [mouseX, setMouseX] = React.useState(0); + const [mouseIn, setMouseIn] = React.useState(false); + const onClickTrack = (e: any) => { + const p = e.nativeEvent.offsetX / e.target.offsetWidth; + const time = Math.max(Math.round(p * endTime), 0); + if (time) { + player.jump(time); + } + }; - return ( - <div - className="overflow-x-auto overflow-y-hidden bg-gray-lightest" - onClick={onClickTrack} - // onMouseMoveCapture={onMouseMoveCapture} - // onMouseOver={() => setMouseIn(true)} - // onMouseOut={() => setMouseIn(false)} - > - {mouseIn && <VerticalLine left={mouseX} className="border-gray-medium" />} - <div className="">{props.children}</div> - </div> - ); + return ( + <div className="overflow-x-auto overflow-y-hidden bg-gray-lightest" onClick={onClickTrack}> + {mouseIn && <VerticalLine left={mouseX} className="border-gray-medium" />} + <div className="">{props.children}</div> + </div> + ); }); export default OverviewPanelContainer; - -// export default connectPlayer((state: any) => ({ -// endTime: state.endTime, -// }))(OverviewPanelContainer); diff --git a/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx b/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx index 8da42f303..2e719f377 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/PerformanceGraph/PerformanceGraph.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { connectPlayer } from 'Player'; -import { AreaChart, Area, Tooltip, ResponsiveContainer } from 'recharts'; +import { AreaChart, Area, ResponsiveContainer } from 'recharts'; interface Props { list: any; diff --git a/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/StackEventModal.tsx b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/StackEventModal.tsx index 76490900a..0afbc6d30 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/StackEventModal.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/StackEventModal.tsx @@ -1,7 +1,7 @@ import React from 'react'; import JsonViewer from './components/JsonViewer'; import Sentry from './components/Sentry'; -import { OPENREPLAY, SENTRY, DATADOG, STACKDRIVER } from 'Types/session/stackEvent'; +import { SENTRY, DATADOG, STACKDRIVER } from 'Types/session/stackEvent'; interface Props { event: any; @@ -23,7 +23,7 @@ function StackEventModal(props: Props) { } }; return ( - <div className="bg-white h-screen overflow-y-auto" style={{ width: '450px' }}> + <div className="bg-white h-screen overflow-y-auto"> {renderPopupContent()} </div> ); diff --git a/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx index 5b6434794..499c38d25 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/TimelinePointer/TimelinePointer.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { Controls } from 'Player'; import { NETWORK, EXCEPTIONS } from 'Duck/components/player'; import { useModal } from 'App/components/Modal'; import { Icon, Tooltip } from 'UI'; @@ -7,6 +6,7 @@ import StackEventModal from '../StackEventModal'; import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorDetailsModal'; import FetchDetails from 'Shared/FetchDetailsModal'; import GraphQLDetailsModal from 'Shared/GraphQLDetailsModal'; +import { PlayerContext } from 'App/components/Session/playerContext'; interface Props { pointer: any; @@ -15,28 +15,30 @@ interface Props { fetchPresented?: boolean; } const TimelinePointer = React.memo((props: Props) => { + const { player } = React.useContext(PlayerContext) + const { showModal } = useModal(); const createEventClickHandler = (pointer: any, type: any) => (e: any) => { if (props.noClick) return; e.stopPropagation(); - Controls.jump(pointer.time); + player.jump(pointer.time); if (!type) { return; } if (type === 'ERRORS') { - showModal(<ErrorDetailsModal errorId={pointer.errorId} />, { right: true }); + showModal(<ErrorDetailsModal errorId={pointer.errorId} />, { right: true, width: 1200 }); } if (type === 'EVENT') { - showModal(<StackEventModal event={pointer} />, { right: true }); + showModal(<StackEventModal event={pointer} />, { right: true, width: 450 }); } if (type === NETWORK) { if (pointer.tp === 'graph_ql') { - showModal(<GraphQLDetailsModal resource={pointer} />, { right: true }); + showModal(<GraphQLDetailsModal resource={pointer} />, { right: true, width: 500 }); } else { - showModal(<FetchDetails resource={pointer} fetchPresented={props.fetchPresented} />, { right: true }); + showModal(<FetchDetails resource={pointer} fetchPresented={props.fetchPresented} />, { right: true, width: 500 }); } } // props.toggleBottomBlock(type); diff --git a/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/TimelineScale.tsx b/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/TimelineScale.tsx index 3905f4538..f8a8ec2f1 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/TimelineScale.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/TimelineScale/TimelineScale.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { connectPlayer } from 'Player'; import { millisToMinutesAndSeconds } from 'App/utils'; interface Props { @@ -17,9 +16,6 @@ function TimelineScale(props: Props) { for (var i = 0; i < part; i++) { const txt = millisToMinutesAndSeconds(i * (endTime / part)); const el = document.createElement('div'); - // el.style.height = '10px'; - // el.style.width = '1px'; - // el.style.backgroundColor = '#ccc'; el.style.position = 'absolute'; el.style.left = `${i * gap}px`; el.style.paddingTop = '1px'; @@ -38,23 +34,11 @@ function TimelineScale(props: Props) { } drawScale(scaleRef.current); - - // const resize = () => drawScale(scaleRef.current); - - // window.addEventListener('resize', resize); - // return () => { - // window.removeEventListener('resize', resize); - // }; }, [scaleRef]); return ( <div className="h-6 bg-gray-darkest w-full" ref={scaleRef}> - {/* <div ref={scaleRef} className="w-full h-10 bg-gray-300 relative"></div> */} </div> ); } export default TimelineScale; - -// export default connectPlayer((state: any) => ({ -// endTime: state.endTime, -// }))(TimelineScale); diff --git a/frontend/app/components/Session_/OverviewPanel/components/VerticalLine/VerticalLine.tsx b/frontend/app/components/Session_/OverviewPanel/components/VerticalLine/VerticalLine.tsx index 43a536f13..eb4d70e3a 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/VerticalLine/VerticalLine.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/VerticalLine/VerticalLine.tsx @@ -8,7 +8,7 @@ interface Props { width?: string; } function VerticalLine(props: Props) { - const { left, className = 'border-gray-dark', height = '221px', width = '1px' } = props; + const { left, className = 'border-gray-dark', height = '100%', width = '1px' } = props; return <div className={cn('absolute border-r border-dashed z-10', className)} style={{ left: `${left}%`, height, width }} />; } diff --git a/frontend/app/components/Session_/OverviewPanel/components/VerticalPointerLine/VerticalPointerLine.tsx b/frontend/app/components/Session_/OverviewPanel/components/VerticalPointerLine/VerticalPointerLine.tsx index 688e8364c..5f815efa6 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/VerticalPointerLine/VerticalPointerLine.tsx +++ b/frontend/app/components/Session_/OverviewPanel/components/VerticalPointerLine/VerticalPointerLine.tsx @@ -1,18 +1,16 @@ import React from 'react'; -import { connectPlayer } from 'Player'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; import VerticalLine from '../VerticalLine'; -interface Props { - time?: number; - scale?: number; -} -function VerticalPointerLine(props: Props) { - const { time, scale } = props; +function VerticalPointerLine() { + const { store } = React.useContext(PlayerContext) + + const { time, endTime } = store.get(); + const scale = 100 / endTime; + const left = time * scale; return <VerticalLine left={left} className="border-teal" />; } -export default connectPlayer((state: any) => ({ - time: state.time, - scale: 100 / state.endTime, -}))(VerticalPointerLine); +export default observer(VerticalPointerLine); diff --git a/frontend/app/components/Session_/PageInsightsPanel/PageInsightsPanel.tsx b/frontend/app/components/Session_/PageInsightsPanel/PageInsightsPanel.tsx index 03d74a247..2315ec90f 100644 --- a/frontend/app/components/Session_/PageInsightsPanel/PageInsightsPanel.tsx +++ b/frontend/app/components/Session_/PageInsightsPanel/PageInsightsPanel.tsx @@ -3,7 +3,9 @@ import { Loader, Icon } from 'UI'; import { connect } from 'react-redux'; import { fetchInsights } from 'Duck/sessions'; import SelectorsList from './components/SelectorsList/SelectorsList'; -import { markTargets, Controls as Player } from 'Player'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { compareJsonObjects } from 'App/utils'; + import Select from 'Shared/Select'; import SelectDateRange from 'Shared/SelectDateRange'; import Period from 'Types/app/period'; @@ -12,7 +14,6 @@ const JUMP_OFFSET = 1000; interface Props { filters: any; fetchInsights: (filters: Record<string, any>) => void; - urls: []; insights: any; events: Array<any>; urlOptions: Array<any>; @@ -22,8 +23,11 @@ interface Props { } function PageInsightsPanel({ filters, fetchInsights, events = [], insights, urlOptions, host, loading = true, setActiveTab }: Props) { - const [insightsFilters, setInsightsFilters] = useState(filters); + const { player: Player } = React.useContext(PlayerContext) + const markTargets = (t: any) => Player.markTargets(t) const defaultValue = urlOptions && urlOptions[0] ? urlOptions[0].value : ''; + const [insightsFilters, setInsightsFilters] = useState({ ...filters, url: host + defaultValue }); + const prevInsights = React.useRef<any>(); const period = Period({ start: insightsFilters.startDate, @@ -44,18 +48,22 @@ function PageInsightsPanel({ filters, fetchInsights, events = [], insights, urlO }, [insights]); useEffect(() => { + const changed = !compareJsonObjects(prevInsights.current, insightsFilters); + if (!changed) { return } + if (urlOptions && urlOptions[0]) { const url = insightsFilters.url ? insightsFilters.url : host + urlOptions[0].value; Player.pause(); fetchInsights({ ...insightsFilters, url }); + markTargets([]); } + prevInsights.current = insightsFilters; }, [insightsFilters]); const onPageSelect = ({ value }: any) => { const event = events.find((item) => item.url === value.value); Player.jump(event.time + JUMP_OFFSET); setInsightsFilters({ ...insightsFilters, url: host + value.value }); - markTargets([]); }; return ( @@ -97,7 +105,7 @@ function PageInsightsPanel({ filters, fetchInsights, events = [], insights, urlO } export default connect( - (state) => { + (state: any) => { const events = state.getIn(['sessions', 'visitedEvents']); return { filters: state.getIn(['sessions', 'insightFilters']), diff --git a/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx b/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx index 999dae866..39de770b7 100644 --- a/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx +++ b/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx @@ -1,9 +1,9 @@ -import React, { useState } from 'react'; +import React from 'react'; import stl from './SelectorCard.module.css'; import cn from 'classnames'; import type { MarkedTarget } from 'Player'; -import { activeTarget } from 'Player'; import { Tooltip } from 'react-tippy'; +import { PlayerContext } from 'App/components/Session/playerContext'; interface Props { index?: number; @@ -12,7 +12,11 @@ interface Props { } export default function SelectorCard({ index = 1, target, showContent }: Props) { + const { player } = React.useContext(PlayerContext) + const activeTarget = player.setActiveTarget + return ( + // @ts-ignore TODO for Alex <div className={cn(stl.wrapper, { [stl.active]: showContent })} onClick={() => activeTarget(index)}> <div className={stl.top}> {/* @ts-ignore */} diff --git a/frontend/app/components/Session_/PageInsightsPanel/components/SelectorsList/SelectorsList.tsx b/frontend/app/components/Session_/PageInsightsPanel/components/SelectorsList/SelectorsList.tsx index 14080e718..183b9754e 100644 --- a/frontend/app/components/Session_/PageInsightsPanel/components/SelectorsList/SelectorsList.tsx +++ b/frontend/app/components/Session_/PageInsightsPanel/components/SelectorsList/SelectorsList.tsx @@ -1,26 +1,22 @@ import React from 'react'; import { NoContent } from 'UI'; -import { connectPlayer } from 'Player'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; import SelectorCard from '../SelectorCard/SelectorCard'; -import type { MarkedTarget } from 'Player'; import stl from './selectorList.module.css'; -interface Props { - targets: Array<MarkedTarget>; - activeTargetIndex: number; -} +function SelectorsList() { + const { store } = React.useContext(PlayerContext) + + const { markedTargets: targets, activeTargetIndex } = store.get() -function SelectorsList({ targets, activeTargetIndex }: Props) { return ( <NoContent title="No data available." size="small" show={targets && targets.length === 0}> <div className={stl.wrapper}> - {targets && targets.map((target, index) => <SelectorCard target={target} index={index} showContent={activeTargetIndex === index} />)} + {targets && targets.map((target, index) => <React.Fragment key={index}><SelectorCard target={target} index={index} showContent={activeTargetIndex === index} /></React.Fragment>)} </div> </NoContent> ); } -export default connectPlayer((state: any) => ({ - targets: state.markedTargets, - activeTargetIndex: state.activeTargetIndex, -}))(SelectorsList); +export default observer(SelectorsList); diff --git a/frontend/app/components/Session_/Performance/Performance.tsx b/frontend/app/components/Session_/Performance/Performance.tsx index 994401141..fa4828af1 100644 --- a/frontend/app/components/Session_/Performance/Performance.tsx +++ b/frontend/app/components/Session_/Performance/Performance.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Controls as PlayerControls, connectPlayer } from 'Player'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; import { AreaChart, Area, @@ -11,10 +12,8 @@ import { Tooltip, ResponsiveContainer, ReferenceLine, - CartesianGrid, Label, } from 'recharts'; -import { Checkbox } from 'UI'; import { durationFromMsFormatted } from 'App/date'; import { formatBytes } from 'App/utils'; @@ -174,362 +173,324 @@ function addFpsMetadata(data) { }); } -@connect((state) => ({ - userDeviceHeapSize: state.getIn(['sessions', 'current', 'userDeviceHeapSize']), - userDeviceMemorySize: state.getIn(['sessions', 'current', 'userDeviceMemorySize']), -})) -export default class Performance extends React.PureComponent { - _timeTicks = generateTicks(this.props.performanceChartData); - _data = addFpsMetadata(this.props.performanceChartData); - // state = { - // totalHeap: false, - // usedHeap: true, - // fps: true, - // } - // onCheckboxClick = (e, { name, checked }) => this.setState({ [ name ]: checked }) +function Performance({ + userDeviceHeapSize, +}: { + userDeviceHeapSize: number; +}) { + const { player, store } = React.useContext(PlayerContext); + const [_timeTicks, setTicks] = React.useState<number[]>([]) + const [_data, setData] = React.useState<any[]>([]) - onDotClick = ({ index }) => { - const point = this._data[index]; + const { + performanceChartTime, + performanceChartData, + connType, + connBandwidth, + performanceAvaliability: avaliability, + } = store.get(); + + React.useState(() => { + setTicks(generateTicks(performanceChartData)); + setData(addFpsMetadata(performanceChartData)); + }) + + + const onDotClick = ({ index: pointer }: { index: number }) => { + const point = _data[pointer]; if (!!point) { - PlayerControls.jump(point.time); + player.jump(point.time); } }; - onChartClick = (e) => { + const onChartClick = (e: any) => { if (e === null) return; const { activeTooltipIndex } = e; - const point = this._data[activeTooltipIndex]; + const point = _data[activeTooltipIndex]; if (!!point) { - PlayerControls.jump(point.time); + player.jump(point.time); } }; - render() { - const { - userDeviceHeapSize, - userDeviceMemorySize, - connType, - connBandwidth, - performanceChartTime, - avaliability = {}, - } = this.props; - const { fps, cpu, heap, nodes } = avaliability; - const avaliableCount = [fps, cpu, heap, nodes].reduce((c, av) => (av ? c + 1 : c), 0); - const height = avaliableCount === 0 ? '0' : `${100 / avaliableCount}%`; + const { fps, cpu, heap, nodes } = avaliability; + const avaliableCount = [fps, cpu, heap, nodes].reduce((c, av) => (av ? c + 1 : c), 0); + const height = avaliableCount === 0 ? '0' : `${100 / avaliableCount}%`; - return ( - <BottomBlock> - <BottomBlock.Header> - <div className="flex items-center w-full"> - <div className="font-semibold color-gray-medium mr-auto">Performance</div> - <InfoLine> - <InfoLine.Point - label="Device Heap Size" - value={formatBytes(userDeviceHeapSize)} - display={true} - /> - {/* <InfoLine.Point */} - {/* label="Device Memory Size" */} - {/* value={ formatBytes(userDeviceMemorySize*1e6) } */} - {/* /> */} - <InfoLine.Point - label="Connection Type" - value={connType} - display={connType != null && connType !== 'unknown'} - /> - <InfoLine.Point - label="Connection Speed" - value={ - connBandwidth >= 1000 ? `${connBandwidth / 1000} Mbps` : `${connBandwidth} Kbps` - } - display={connBandwidth != null} - /> - </InfoLine> - </div> - </BottomBlock.Header> - <BottomBlock.Content> - {fps && ( - <ResponsiveContainer height={height}> - <AreaChart - onClick={this.onChartClick} - data={this._data} - syncId="s" - margin={{ - top: 0, - right: 0, - left: 0, - bottom: 0, - }} + return ( + <BottomBlock> + <BottomBlock.Header> + <div className="flex items-center w-full"> + <div className="font-semibold color-gray-medium mr-auto">Performance</div> + <InfoLine> + <InfoLine.Point + label="Device Heap Size" + value={formatBytes(userDeviceHeapSize)} + display={true} + /> + <InfoLine.Point + label="Connection Type" + value={connType} + display={connType != null && connType !== 'unknown'} + /> + <InfoLine.Point + label="Connection Speed" + value={ + connBandwidth >= 1000 ? `${connBandwidth / 1000} Mbps` : `${connBandwidth} Kbps` + } + display={connBandwidth != null} + /> + </InfoLine> + </div> + </BottomBlock.Header> + <BottomBlock.Content> + {fps && ( + <ResponsiveContainer height={height}> + <AreaChart + onClick={onChartClick} + data={_data} + syncId="s" + margin={{ + top: 0, + right: 0, + left: 0, + bottom: 0, + }} + > + <defs> + <Gradient id="fpsGradient" color={FPS_COLOR} /> + </defs> + <XAxis + dataKey="time" + type="number" + mirror + orientation="top" + tickLine={false} + tickFormatter={durationFromMsFormatted} + tick={{ fontSize: '12px', fill: '#333' }} + domain={[0, 'dataMax']} + ticks={_timeTicks} > - <defs> - <Gradient id="fpsGradient" color={FPS_COLOR} /> - </defs> - {/* <CartesianGrid strokeDasharray="1 1" stroke="#ddd" horizontal={ false } /> */} - {/* <CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" /> */} - <XAxis - dataKey="time" - type="number" - mirror - orientation="top" - tickLine={false} - tickFormatter={durationFromMsFormatted} - tick={{ fontSize: '12px', fill: '#333' }} - domain={[0, 'dataMax']} - ticks={this._timeTicks} - > - <Label value="FPS" position="insideTopRight" className="fill-gray-darkest" /> - </XAxis> - <YAxis axisLine={false} tick={false} mirror domain={[0, 85]} /> - {/* <YAxis */} - {/* yAxisId="r" */} - {/* axisLine={ false } */} - {/* tick={ false } */} - {/* mirror */} - {/* domain={[0, 120]} */} - {/* orientation="right" */} - {/* /> */} - <Area - dataKey="fps" - type="stepBefore" - stroke={FPS_STROKE_COLOR} - fill="url(#fpsGradient)" - dot={false} - activeDot={{ - onClick: this.onDotClick, - style: { cursor: 'pointer' }, - }} - isAnimationActive={false} - /> - <Area - dataKey="fpsLowMarker" - type="stepBefore" - stroke="none" - fill={FPS_LOW_COLOR} - activeDot={false} - isAnimationActive={false} - /> - <Area - dataKey="fpsVeryLowMarker" - type="stepBefore" - stroke="none" - fill={FPS_VERY_LOW_COLOR} - activeDot={false} - isAnimationActive={false} - /> - <Area - dataKey="hiddenScreenMarker" - type="stepBefore" - stroke="none" - fill={HIDDEN_SCREEN_COLOR} - activeDot={false} - isAnimationActive={false} - /> - {/* <Area */} - {/* yAxisId="r" */} - {/* dataKey="cpu" */} - {/* type="monotone" */} - {/* stroke={CPU_COLOR} */} - {/* fill="none" */} - {/* // fill="url(#fpsGradient)" */} - {/* dot={false} */} - {/* activeDot={{ */} - {/* onClick: this.onDotClick, */} - {/* style: { cursor: "pointer" }, */} - {/* }} */} - {/* isAnimationActive={ false } */} - {/* /> */} - <ReferenceLine x={performanceChartTime} stroke={CURSOR_COLOR} /> - <Tooltip content={FPSTooltip} filterNull={false} /> - </AreaChart> - </ResponsiveContainer> - )} - {cpu && ( - <ResponsiveContainer height={height}> - <AreaChart - onClick={this.onChartClick} - data={this._data} - syncId="s" - margin={{ - top: 0, - right: 0, - left: 0, - bottom: 0, + <Label value="FPS" position="insideTopRight" className="fill-gray-darkest" /> + </XAxis> + <YAxis axisLine={false} tick={false} mirror domain={[0, 85]} /> + <Area + dataKey="fps" + type="stepBefore" + stroke={FPS_STROKE_COLOR} + fill="url(#fpsGradient)" + dot={false} + activeDot={{ + onClick: onDotClick, + style: { cursor: 'pointer' }, }} + isAnimationActive={false} + /> + <Area + dataKey="fpsLowMarker" + type="stepBefore" + stroke="none" + fill={FPS_LOW_COLOR} + activeDot={false} + isAnimationActive={false} + /> + <Area + dataKey="fpsVeryLowMarker" + type="stepBefore" + stroke="none" + fill={FPS_VERY_LOW_COLOR} + activeDot={false} + isAnimationActive={false} + /> + <Area + dataKey="hiddenScreenMarker" + type="stepBefore" + stroke="none" + fill={HIDDEN_SCREEN_COLOR} + activeDot={false} + isAnimationActive={false} + /> + <ReferenceLine x={performanceChartTime} stroke={CURSOR_COLOR} /> + <Tooltip content={FPSTooltip} filterNull={false} /> + </AreaChart> + </ResponsiveContainer> + )} + {cpu && ( + <ResponsiveContainer height={height}> + <AreaChart + onClick={onChartClick} + data={_data} + syncId="s" + margin={{ + top: 0, + right: 0, + left: 0, + bottom: 0, + }} + > + <defs> + <Gradient id="cpuGradient" color={CPU_COLOR} /> + </defs> + {/* <CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" /> */} + <XAxis + dataKey="time" + type="number" + mirror + orientation="top" + tickLine={false} + tickFormatter={() => ''} + domain={[0, 'dataMax']} + ticks={_timeTicks} > - <defs> - <Gradient id="cpuGradient" color={CPU_COLOR} /> - </defs> - {/* <CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" /> */} - <XAxis - dataKey="time" - type="number" - mirror - orientation="top" - tickLine={false} - tickFormatter={() => ''} - domain={[0, 'dataMax']} - ticks={this._timeTicks} - > - <Label value="CPU" position="insideTopRight" className="fill-gray-darkest" /> - </XAxis> - <YAxis axisLine={false} tick={false} mirror domain={[0, 120]} orientation="right" /> - <Area - dataKey="cpu" - type="monotone" - stroke={CPU_STROKE_COLOR} - // fill="none" - fill="url(#cpuGradient)" - dot={false} - activeDot={{ - onClick: this.onDotClick, - style: { cursor: 'pointer' }, - }} - isAnimationActive={false} - /> - <Area - dataKey="hiddenScreenMarker" - type="stepBefore" - stroke="none" - fill={HIDDEN_SCREEN_COLOR} - activeDot={false} - isAnimationActive={false} - /> - <ReferenceLine x={performanceChartTime} stroke={CURSOR_COLOR} /> - <Tooltip content={CPUTooltip} filterNull={false} /> - </AreaChart> - </ResponsiveContainer> - )} + <Label value="CPU" position="insideTopRight" className="fill-gray-darkest" /> + </XAxis> + <YAxis axisLine={false} tick={false} mirror domain={[0, 120]} orientation="right" /> + <Area + dataKey="cpu" + type="monotone" + stroke={CPU_STROKE_COLOR} + fill="url(#cpuGradient)" + dot={false} + activeDot={{ + onClick: onDotClick, + style: { cursor: 'pointer' }, + }} + isAnimationActive={false} + /> + <Area + dataKey="hiddenScreenMarker" + type="stepBefore" + stroke="none" + fill={HIDDEN_SCREEN_COLOR} + activeDot={false} + isAnimationActive={false} + /> + <ReferenceLine x={performanceChartTime} stroke={CURSOR_COLOR} /> + <Tooltip content={CPUTooltip} filterNull={false} /> + </AreaChart> + </ResponsiveContainer> + )} - {heap && ( - <ResponsiveContainer height={height}> - <ComposedChart - onClick={this.onChartClick} - data={this._data} - margin={{ - top: 0, - right: 0, - left: 0, - bottom: 0, - }} - syncId="s" + {heap && ( + <ResponsiveContainer height={height}> + <ComposedChart + onClick={onChartClick} + data={_data} + margin={{ + top: 0, + right: 0, + left: 0, + bottom: 0, + }} + syncId="s" + > + <defs> + <Gradient id="usedHeapGradient" color={USED_HEAP_COLOR} /> + </defs> + <XAxis + dataKey="time" + type="number" + mirror + orientation="top" + tickLine={false} + tickFormatter={() => ''} // tick={false} + _timeTicks to cartesian array + domain={[0, 'dataMax']} + ticks={_timeTicks} > - <defs> - <Gradient id="usedHeapGradient" color={USED_HEAP_COLOR} /> - </defs> - {/* <CartesianGrid strokeDasharray="1 1" stroke="#ddd" horizontal={ false } /> */} - <XAxis - dataKey="time" - type="number" - mirror - orientation="top" - tickLine={false} - tickFormatter={() => ''} // tick={false} + this._timeTicks to cartesian array - domain={[0, 'dataMax']} - ticks={this._timeTicks} - > - <Label value="HEAP" position="insideTopRight" className="fill-gray-darkest" /> - </XAxis> - <YAxis - axisLine={false} - tickFormatter={formatBytes} - mirror - // Hack to keep only end tick - minTickGap={Number.MAX_SAFE_INTEGER} - domain={[0, (max) => max * 1.2]} - /> - <Line - type="monotone" - dataKey="totalHeap" - // fill="url(#totalHeapGradient)" - stroke={TOTAL_HEAP_STROKE_COLOR} - dot={false} - activeDot={{ - onClick: this.onDotClick, - style: { cursor: 'pointer' }, - }} - isAnimationActive={false} - /> - <Area - dataKey="usedHeap" - type="monotone" - fill="url(#usedHeapGradient)" - stroke={USED_HEAP_STROKE_COLOR} - dot={false} - activeDot={{ - onClick: this.onDotClick, - style: { cursor: 'pointer' }, - }} - isAnimationActive={false} - /> - <ReferenceLine x={performanceChartTime} stroke={CURSOR_COLOR} /> - <Tooltip content={HeapTooltip} filterNull={false} /> - </ComposedChart> - </ResponsiveContainer> - )} - {nodes && ( - <ResponsiveContainer height={height}> - <AreaChart - onClick={this.onChartClick} - data={this._data} - syncId="s" - margin={{ - top: 0, - right: 0, - left: 0, - bottom: 0, + <Label value="HEAP" position="insideTopRight" className="fill-gray-darkest" /> + </XAxis> + <YAxis + axisLine={false} + tickFormatter={formatBytes} + mirror + // Hack to keep only end tick + minTickGap={Number.MAX_SAFE_INTEGER} + domain={[0, (max: number) => max * 1.2]} + /> + <Line + type="monotone" + dataKey="totalHeap" + stroke={TOTAL_HEAP_STROKE_COLOR} + dot={false} + activeDot={{ + onClick: onDotClick, + style: { cursor: 'pointer' }, }} + isAnimationActive={false} + /> + <Area + dataKey="usedHeap" + type="monotone" + fill="url(#usedHeapGradient)" + stroke={USED_HEAP_STROKE_COLOR} + dot={false} + activeDot={{ + onClick: onDotClick, + style: { cursor: 'pointer' }, + }} + isAnimationActive={false} + /> + <ReferenceLine x={performanceChartTime} stroke={CURSOR_COLOR} /> + <Tooltip content={HeapTooltip} filterNull={false} /> + </ComposedChart> + </ResponsiveContainer> + )} + {nodes && ( + <ResponsiveContainer height={height}> + <AreaChart + onClick={onChartClick} + data={_data} + syncId="s" + margin={{ + top: 0, + right: 0, + left: 0, + bottom: 0, + }} + > + <defs> + <Gradient id="nodesGradient" color={NODES_COUNT_COLOR} /> + </defs> + <XAxis + dataKey="time" + type="number" + mirror + orientation="top" + tickLine={false} + tickFormatter={() => ''} + domain={[0, 'dataMax']} + ticks={_timeTicks} > - <defs> - <Gradient id="nodesGradient" color={NODES_COUNT_COLOR} /> - </defs> - {/* <CartesianGrid strokeDasharray="1 1" stroke="#ddd" horizontal={ false } /> */} - <XAxis - dataKey="time" - type="number" - mirror - orientation="top" - tickLine={false} - tickFormatter={() => ''} - domain={[0, 'dataMax']} - ticks={this._timeTicks} - > - <Label value="NODES" position="insideTopRight" className="fill-gray-darkest" /> - </XAxis> - <YAxis - axisLine={false} - tick={false} - mirror - orientation="right" - domain={[0, (max) => max * 1.2]} - /> - <Area - dataKey="nodesCount" - type="monotone" - stroke={NODES_COUNT_STROKE_COLOR} - // fill="none" - fill="url(#nodesGradient)" - dot={false} - activeDot={{ - onClick: this.onDotClick, - style: { cursor: 'pointer' }, - }} - isAnimationActive={false} - /> - <ReferenceLine x={performanceChartTime} stroke={CURSOR_COLOR} /> - <Tooltip content={NodesCountTooltip} filterNull={false} /> - </AreaChart> - </ResponsiveContainer> - )} - </BottomBlock.Content> - </BottomBlock> - ); - } + <Label value="NODES" position="insideTopRight" className="fill-gray-darkest" /> + </XAxis> + <YAxis + axisLine={false} + tick={false} + mirror + orientation="right" + domain={[0, (max: number) => max * 1.2]} + /> + <Area + dataKey="nodesCount" + type="monotone" + stroke={NODES_COUNT_STROKE_COLOR} + fill="url(#nodesGradient)" + dot={false} + activeDot={{ + onClick: onDotClick, + style: { cursor: 'pointer' }, + }} + isAnimationActive={false} + /> + <ReferenceLine x={performanceChartTime} stroke={CURSOR_COLOR} /> + <Tooltip content={NodesCountTooltip} filterNull={false} /> + </AreaChart> + </ResponsiveContainer> + )} + </BottomBlock.Content> + </BottomBlock> + ); } -export const ConnectedPerformance = connectPlayer((state) => ({ - performanceChartTime: state.performanceChartTime, - performanceChartData: state.performanceChartData, - connType: state.connType, - connBandwidth: state.connBandwidth, - avaliability: state.performanceAvaliability, -}))(Performance); +export const ConnectedPerformance = connect((state: any) => ({ + userDeviceHeapSize: state.getIn(['sessions', 'current']).userDeviceHeapSize, +}))(observer(Performance)); diff --git a/frontend/app/components/Session_/Performance/index.js b/frontend/app/components/Session_/Performance/index.js index e0e5ed895..967fe1364 100644 --- a/frontend/app/components/Session_/Performance/index.js +++ b/frontend/app/components/Session_/Performance/index.js @@ -1,2 +1,2 @@ -export { default } from './Performance'; -export * from './Performance'; \ No newline at end of file +export { ConnectedPerformance as default } from './Performance'; +export * from './Performance'; diff --git a/frontend/app/components/Session_/Player/Controls/AssistSessionsModal/AssistSessionsModal.tsx b/frontend/app/components/Session_/Player/Controls/AssistSessionsModal/AssistSessionsModal.tsx new file mode 100644 index 000000000..c1f192360 --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/AssistSessionsModal/AssistSessionsModal.tsx @@ -0,0 +1,175 @@ +import React from 'react'; +import { Loader, Pagination, Tooltip, Button } from 'UI'; +import { connect } from 'react-redux'; +import SessionItem from 'Shared/SessionItem'; +import { addFilterByKeyAndValue, updateCurrentPage, applyFilter } from 'Duck/liveSearch'; +import { List } from 'immutable'; +import { FilterKey } from 'App/types/filter/filterType'; +import Select from 'Shared/Select'; +import SortOrderButton from 'Shared/SortOrderButton'; +import { KEYS } from 'Types/filter/customFilter'; +import { capitalize } from 'App/utils'; +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite'; +import { fetchList as fetchMeta } from 'Duck/customField'; +import AssistSearchField from 'App/components/Assist/AssistSearchField'; +import LiveSessionSearch from 'Shared/LiveSessionSearch'; +import cn from 'classnames'; +import Session from 'App/mstore/types/session'; + +const PER_PAGE = 10; + +interface OwnProps {} +interface ConnectProps { + loading: boolean; + metaListLoading: boolean; + list: List<any>; + filter: any; + currentPage: number; + metaList: any; + sort: any; + total: number; + replaceTarget?: string; + addFilterByKeyAndValue: (key: FilterKey, value: string) => void; + updateCurrentPage: (page: number) => void; + applyFilter: (filter: any) => void; + onAdd: () => void; + fetchMeta: () => void; +} + +type Props = OwnProps & ConnectProps; + +function AssistSessionsModal(props: Props) { + const { assistMultiviewStore } = useStore(); + const { loading, list, metaList = [], filter, currentPage, total, onAdd, fetchMeta } = props; + const onUserClick = () => false; + const { filters } = filter; + const hasUserFilter = filters.map((i: any) => i.key).includes(KEYS.USERID); + + const sortOptions = metaList + .map((i: any) => ({ + label: capitalize(i), + value: i, + })) + .toJS(); + + React.useEffect(() => { + if (total === 0) { + reloadSessions(); + } + fetchMeta(); + }, []); + const reloadSessions = () => props.applyFilter({ ...filter }); + const onSortChange = ({ value }: any) => { + props.applyFilter({ sort: value.value }); + }; + const onSessionAdd = (session: Session) => { + if (props.replaceTarget) { + assistMultiviewStore.replaceSession(props.replaceTarget, session); + } else { + assistMultiviewStore.addSession(session); + } + assistMultiviewStore.fetchAgentTokenInfo(session.sessionId).then(() => onAdd()); + }; + + return ( + <div className="bg-gray-lightest box-shadow h-screen p-4"> + <div className="flex flex-col my-2 w-full gap-2 "> + <div className="flex items-center gap-2 w-full"> + <Tooltip title="Refresh" placement="top" delay={200}> + <Button + loading={loading} + className="mr-2" + variant="text" + onClick={reloadSessions} + icon="arrow-repeat" + /> + </Tooltip> + <AssistSearchField /> + </div> + <div className="flex self-end items-center gap-2" w-full> + <span className="color-gray-medium">Sort By</span> + <Tooltip title="No metadata available to sort" disabled={sortOptions.length > 0}> + <div className={cn('flex items-center', { disabled: sortOptions.length === 0 })}> + <Select + plain + right + options={sortOptions} + onChange={onSortChange} + value={sortOptions.find((i: any) => i.value === filter.sort) || sortOptions[0]} + /> + <SortOrderButton + onChange={(state: any) => props.applyFilter({ order: state })} + sortOrder={filter.order} + /> + </div> + </Tooltip> + </div> + </div> + <LiveSessionSearch /> + <div className="my-4" /> + <Loader loading={loading}> + {list.map((session) => ( + <React.Fragment key={session.sessionId}> + <div + className={cn( + 'rounded bg-white mb-2 overflow-hidden border', + assistMultiviewStore.sessions.findIndex( + (s: Record<string, any>) => s.sessionId === session.sessionId + ) !== -1 + ? 'cursor-not-allowed opacity-60' + : '' + )} + > + <SessionItem + key={session.sessionId} + session={session} + live + hasUserFilter={hasUserFilter} + onUserClick={onUserClick} + metaList={metaList} + isDisabled={ + assistMultiviewStore.sessions.findIndex( + (s: Record<string, any>) => s.sessionId === session.sessionId + ) !== -1 + } + isAdd + onClick={() => onSessionAdd(session)} + /> + </div> + </React.Fragment> + ))} + </Loader> + + {total > PER_PAGE && ( + <div className="w-full flex items-center justify-center py-6"> + <Pagination + page={currentPage} + totalPages={Math.ceil(total / PER_PAGE)} + onPageChange={(page: any) => props.updateCurrentPage(page)} + limit={PER_PAGE} + /> + </div> + )} + </div> + ); +} + +export default connect( + (state: any) => ({ + list: state.getIn(['liveSearch', 'list']), + loading: state.getIn(['liveSearch', 'fetchList', 'loading']), + metaListLoading: state.getIn(['customFields', 'fetchRequest', 'loading']), + filter: state.getIn(['liveSearch', 'instance']), + total: state.getIn(['liveSearch', 'total']), + currentPage: state.getIn(['liveSearch', 'currentPage']), + metaList: state.getIn(['customFields', 'list']).map((i: any) => i.key), + sort: state.getIn(['liveSearch', 'sort']), + }), + { + applyFilter, + addFilterByKeyAndValue, + updateCurrentPage, + fetchMeta, + } +)(observer(AssistSessionsModal)); diff --git a/frontend/app/components/Session_/Player/Controls/AssistSessionsModal/index.tsx b/frontend/app/components/Session_/Player/Controls/AssistSessionsModal/index.tsx new file mode 100644 index 000000000..1360c951f --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/AssistSessionsModal/index.tsx @@ -0,0 +1 @@ +export { default } from './AssistSessionsModal' diff --git a/frontend/app/components/Session_/Player/Controls/AssistSessionsTabs/AssistSessionsTabs.tsx b/frontend/app/components/Session_/Player/Controls/AssistSessionsTabs/AssistSessionsTabs.tsx new file mode 100644 index 000000000..07311665a --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/AssistSessionsTabs/AssistSessionsTabs.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import cn from 'classnames'; +import { Icon } from 'UI'; +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite'; +import { useHistory } from 'react-router-dom'; +import { multiview, liveSession, withSiteId } from 'App/routes'; +import { connect } from 'react-redux'; + +interface ITab { + onClick?: () => void; + classNames?: string; + children: React.ReactNode; + style?: Record<string, any>; +} + +const Tab = (props: ITab) => ( + <div + onClick={props.onClick} + className={cn('p-1 rounded flex items-center justify-center cursor-pointer', props.classNames)} + style={props.style} + > + {props.children} + </div> +); + +export const InactiveTab = React.memo((props: Omit<ITab, 'children'>) => ( + <Tab onClick={props.onClick} classNames={cn("hover:bg-gray-bg bg-gray-light", props.classNames)}> + <Icon name="plus" size="22" color="white" /> + </Tab> +)); + +const ActiveTab = React.memo((props: Omit<ITab, 'children'>) => ( + <Tab onClick={props.onClick} classNames="hover:bg-teal" style={{ background: 'rgba(57, 78, 255, 0.5)' }}> + <Icon name="play-fill-new" size="22" color="white" /> + </Tab> +)); + +const CurrentTab = React.memo(() => ( + <Tab classNames="bg-teal color-white"> + <span style={{ fontSize: '0.65rem' }}>PLAYING</span> + </Tab> +)); + +function AssistTabs({ session, siteId }: { session: Record<string, any>; siteId: string }) { + const history = useHistory(); + const { assistMultiviewStore } = useStore(); + + const placeholder = new Array(4 - assistMultiviewStore.sessions.length).fill(0); + + React.useEffect(() => { + if (assistMultiviewStore.sessions.length === 0) { + assistMultiviewStore.setDefault(session); + } + }, []); + + const openGrid = () => { + const sessionIdQuery = encodeURIComponent(assistMultiviewStore.sessions.map((s) => s.sessionId).join(',')); + return history.push(withSiteId(multiview(sessionIdQuery), siteId)); + }; + const openLiveSession = (sessionId: string) => { + assistMultiviewStore.setActiveSession(sessionId); + history.push(withSiteId(liveSession(sessionId), siteId)); + }; + + return ( + <div className="grid grid-cols-2 w-28 h-full" style={{ gap: '4px' }}> + {assistMultiviewStore.sortedSessions.map((session: { key: number, sessionId: string }) => ( + <React.Fragment key={session.key}> + {assistMultiviewStore.isActive(session.sessionId) ? ( + <CurrentTab /> + ) : ( + <ActiveTab onClick={() => openLiveSession(session.sessionId)} /> + )} + </React.Fragment> + ))} + {placeholder.map((_, i) => ( + <React.Fragment key={i}> + <InactiveTab onClick={openGrid} /> + </React.Fragment> + ))} + </div> + ); +} + +export default connect((state: any) => ({ siteId: state.getIn(['site', 'siteId']) }))( + observer(AssistTabs) +); diff --git a/frontend/app/components/Session_/Player/Controls/AssistSessionsTabs/index.tsx b/frontend/app/components/Session_/Player/Controls/AssistSessionsTabs/index.tsx new file mode 100644 index 000000000..c7668e331 --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/AssistSessionsTabs/index.tsx @@ -0,0 +1 @@ +export { default, InactiveTab } from './AssistSessionsTabs' diff --git a/frontend/app/components/Session_/Player/Controls/ControlButton.js b/frontend/app/components/Session_/Player/Controls/ControlButton.js index 3c42895b6..1d6391f1f 100644 --- a/frontend/app/components/Session_/Player/Controls/ControlButton.js +++ b/frontend/app/components/Session_/Player/Controls/ControlButton.js @@ -12,7 +12,7 @@ const ControlButton = ({ hasErrors = false, active = false, size = 20, - noLabel, + noLabel = false, labelClassName, containerClassName, noIcon, diff --git a/frontend/app/components/Session_/Player/Controls/Controls.js b/frontend/app/components/Session_/Player/Controls/Controls.js deleted file mode 100644 index 005403731..000000000 --- a/frontend/app/components/Session_/Player/Controls/Controls.js +++ /dev/null @@ -1,435 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { connect } from 'react-redux'; -import { - connectPlayer, - STORAGE_TYPES, - selectStorageType, - selectStorageListNow, -} from 'Player'; -import LiveTag from 'Shared/LiveTag'; -import { jumpToLive } from 'Player'; - -import { Icon, Tooltip } from 'UI'; -import { toggleInspectorMode } from 'Player'; -import { - fullscreenOn, - fullscreenOff, - toggleBottomBlock, - changeSkipInterval, - OVERVIEW, - CONSOLE, - NETWORK, - STACKEVENTS, - STORAGE, - PROFILER, - PERFORMANCE, - GRAPHQL, - INSPECTOR, -} from 'Duck/components/player'; -import { AssistDuration } from './Time'; -import Timeline from './Timeline'; -import ControlButton from './ControlButton'; -import PlayerControls from './components/PlayerControls'; - -import styles from './controls.module.css'; -import XRayButton from 'Shared/XRayButton'; - -const SKIP_INTERVALS = { - 2: 2e3, - 5: 5e3, - 10: 1e4, - 15: 15e3, - 20: 2e4, - 30: 3e4, - 60: 6e4, -}; - -function getStorageName(type) { - switch (type) { - case STORAGE_TYPES.REDUX: - return 'REDUX'; - case STORAGE_TYPES.MOBX: - return 'MOBX'; - case STORAGE_TYPES.VUEX: - return 'VUEX'; - case STORAGE_TYPES.NGRX: - return 'NGRX'; - case STORAGE_TYPES.ZUSTAND: - return 'ZUSTAND'; - case STORAGE_TYPES.NONE: - return 'STATE'; - } -} - -@connectPlayer((state) => ({ - time: state.time, - endTime: state.endTime, - live: state.live, - livePlay: state.livePlay, - playing: state.playing, - completed: state.completed, - skip: state.skip, - skipToIssue: state.skipToIssue, - speed: state.speed, - disabled: state.cssLoading || state.messagesLoading || state.inspectorMode || state.markedTargets, - inspectorMode: state.inspectorMode, - fullscreenDisabled: state.messagesLoading, - // logCount: state.logList.length, - logRedCount: state.logMarkedCount, - showExceptions: state.exceptionsList.length > 0, - resourceRedCount: state.resourceMarkedCount, - fetchRedCount: state.fetchMarkedCount, - showStack: state.stackList.length > 0, - stackCount: state.stackList.length, - stackRedCount: state.stackMarkedCount, - profilesCount: state.profilesList.length, - storageCount: selectStorageListNow(state).length, - storageType: selectStorageType(state), - showStorage: selectStorageType(state) !== STORAGE_TYPES.NONE, - showProfiler: state.profilesList.length > 0, - showGraphql: state.graphqlList.length > 0, - showFetch: state.fetchCount > 0, - fetchCount: state.fetchCount, - graphqlCount: state.graphqlList.length, - liveTimeTravel: state.liveTimeTravel, -})) -@connect( - (state, props) => { - const permissions = state.getIn(['user', 'account', 'permissions']) || []; - const isEnterprise = state.getIn(['user', 'account', 'edition']) === 'ee'; - return { - disabled: props.disabled || (isEnterprise && !permissions.includes('DEV_TOOLS')), - fullscreen: state.getIn(['components', 'player', 'fullscreen']), - bottomBlock: state.getIn(['components', 'player', 'bottomBlock']), - showStorage: - props.showStorage || !state.getIn(['components', 'player', 'hiddenHints', 'storage']), - showStack: props.showStack || !state.getIn(['components', 'player', 'hiddenHints', 'stack']), - closedLive: - !!state.getIn(['sessions', 'errors']) || !state.getIn(['sessions', 'current', 'live']), - skipInterval: state.getIn(['components', 'player', 'skipInterval']), - }; - }, - { - fullscreenOn, - fullscreenOff, - toggleBottomBlock, - changeSkipInterval, - } -) -export default class Controls extends React.Component { - componentDidMount() { - document.addEventListener('keydown', this.onKeyDown); - } - - componentWillUnmount() { - document.removeEventListener('keydown', this.onKeyDown); - //this.props.toggleInspectorMode(false); - } - - shouldComponentUpdate(nextProps) { - if ( - nextProps.fullscreen !== this.props.fullscreen || - nextProps.bottomBlock !== this.props.bottomBlock || - nextProps.live !== this.props.live || - nextProps.livePlay !== this.props.livePlay || - nextProps.playing !== this.props.playing || - nextProps.completed !== this.props.completed || - nextProps.skip !== this.props.skip || - nextProps.skipToIssue !== this.props.skipToIssue || - nextProps.speed !== this.props.speed || - nextProps.disabled !== this.props.disabled || - nextProps.fullscreenDisabled !== this.props.fullscreenDisabled || - // nextProps.inspectorMode !== this.props.inspectorMode || - // nextProps.logCount !== this.props.logCount || - nextProps.logRedCount !== this.props.logRedCount || - nextProps.showExceptions !== this.props.showExceptions || - nextProps.resourceRedCount !== this.props.resourceRedCount || - nextProps.fetchRedCount !== this.props.fetchRedCount || - nextProps.showStack !== this.props.showStack || - nextProps.stackCount !== this.props.stackCount || - nextProps.stackRedCount !== this.props.stackRedCount || - nextProps.profilesCount !== this.props.profilesCount || - nextProps.storageCount !== this.props.storageCount || - nextProps.storageType !== this.props.storageType || - nextProps.showStorage !== this.props.showStorage || - nextProps.showProfiler !== this.props.showProfiler || - nextProps.showGraphql !== this.props.showGraphql || - nextProps.showFetch !== this.props.showFetch || - nextProps.fetchCount !== this.props.fetchCount || - nextProps.graphqlCount !== this.props.graphqlCount || - nextProps.liveTimeTravel !== this.props.liveTimeTravel || - nextProps.skipInterval !== this.props.skipInterval - ) - return true; - return false; - } - - onKeyDown = (e) => { - if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { - return; - } - if (this.props.inspectorMode) { - if (e.key === 'Esc' || e.key === 'Escape') { - toggleInspectorMode(false); - } - } - // if (e.key === ' ') { - // document.activeElement.blur(); - // this.props.togglePlay(); - // } - if (e.key === 'Esc' || e.key === 'Escape') { - this.props.fullscreenOff(); - } - if (e.key === 'ArrowRight') { - this.forthTenSeconds(); - } - if (e.key === 'ArrowLeft') { - this.backTenSeconds(); - } - if (e.key === 'ArrowDown') { - this.props.speedDown(); - } - if (e.key === 'ArrowUp') { - this.props.speedUp(); - } - }; - - forthTenSeconds = () => { - const { time, endTime, jump, skipInterval } = this.props; - jump(Math.min(endTime, time + SKIP_INTERVALS[skipInterval])); - }; - - backTenSeconds = () => { - //shouldComponentUpdate - const { time, jump, skipInterval } = this.props; - jump(Math.max(1, time - SKIP_INTERVALS[skipInterval])); - }; - - goLive = () => this.props.jump(this.props.endTime); - - renderPlayBtn = () => { - const { completed, playing } = this.props; - let label; - let icon; - if (completed) { - icon = 'arrow-clockwise'; - label = 'Replay this session'; - } else if (playing) { - icon = 'pause-fill'; - label = 'Pause'; - } else { - icon = 'play-fill-new'; - label = 'Pause'; - label = 'Play'; - } - - return ( - <Tooltip - title={label} - className="mr-4" - > - <div - onClick={this.props.togglePlay} - className="hover-main color-main cursor-pointer rounded hover:bg-gray-light-shade" - > - <Icon name={icon} size="36" color="inherit" /> - </div> - </Tooltip> - ); - }; - - controlIcon = (icon, size, action, isBackwards, additionalClasses) => ( - <div - onClick={action} - className={cn('py-2 px-2 hover-main cursor-pointer bg-gray-lightest', additionalClasses)} - style={{ transform: isBackwards ? 'rotate(180deg)' : '' }} - > - <Icon name={icon} size={size} color="inherit" /> - </div> - ); - - render() { - const { - bottomBlock, - toggleBottomBlock, - live, - livePlay, - skip, - speed, - disabled, - logRedCount, - showExceptions, - resourceRedCount, - fetchRedCount, - showStack, - stackRedCount, - showStorage, - storageType, - showProfiler, - showGraphql, - fullscreen, - inspectorMode, - closedLive, - toggleSpeed, - toggleSkip, - liveTimeTravel, - changeSkipInterval, - skipInterval, - } = this.props; - - const toggleBottomTools = (blockName) => { - if (blockName === INSPECTOR) { - toggleInspectorMode(); - bottomBlock && toggleBottomBlock(); - } else { - toggleInspectorMode(false); - toggleBottomBlock(blockName); - } - }; - - return ( - <div className={styles.controls}> - <Timeline - live={live} - jump={this.props.jump} - liveTimeTravel={liveTimeTravel} - pause={this.props.pause} - togglePlay={this.props.togglePlay} - /> - {!fullscreen && ( - <div className={cn(styles.buttons, { '!px-5 !pt-0': live })} data-is-live={live}> - <div className="flex items-center"> - {!live && ( - <> - <PlayerControls - live={live} - skip={skip} - speed={speed} - disabled={disabled} - backTenSeconds={this.backTenSeconds} - forthTenSeconds={this.forthTenSeconds} - toggleSpeed={toggleSpeed} - toggleSkip={toggleSkip} - playButton={this.renderPlayBtn()} - controlIcon={this.controlIcon} - ref={this.speedRef} - skipIntervals={SKIP_INTERVALS} - setSkipInterval={changeSkipInterval} - currentInterval={skipInterval} - /> - <div className={cn('mx-2')} /> - <XRayButton - isActive={bottomBlock === OVERVIEW && !inspectorMode} - onClick={() => toggleBottomTools(OVERVIEW)} - /> - </> - )} - - {live && !closedLive && ( - <div className={styles.buttonsLeft}> - <LiveTag isLive={livePlay} onClick={() => (livePlay ? null : jumpToLive())} /> - <div className="font-semibold px-2"> - <AssistDuration isLivePlay={livePlay} /> - </div> - </div> - )} - </div> - - <div className="flex items-center h-full"> - <ControlButton - disabled={disabled && !inspectorMode} - onClick={() => toggleBottomTools(CONSOLE)} - active={bottomBlock === CONSOLE && !inspectorMode} - label="CONSOLE" - noIcon - labelClassName="!text-base font-semibold" - hasErrors={logRedCount > 0 || showExceptions} - containerClassName="mx-2" - /> - {!live && ( - <ControlButton - disabled={disabled && !inspectorMode} - onClick={() => toggleBottomTools(NETWORK)} - active={bottomBlock === NETWORK && !inspectorMode} - label="NETWORK" - hasErrors={resourceRedCount > 0 || fetchRedCount > 0} - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && ( - <ControlButton - disabled={disabled && !inspectorMode} - onClick={() => toggleBottomTools(PERFORMANCE)} - active={bottomBlock === PERFORMANCE && !inspectorMode} - label="PERFORMANCE" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && showGraphql && ( - <ControlButton - disabled={disabled && !inspectorMode} - onClick={() => toggleBottomTools(GRAPHQL)} - active={bottomBlock === GRAPHQL && !inspectorMode} - label="GRAPHQL" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && showStorage && ( - <ControlButton - disabled={disabled && !inspectorMode} - onClick={() => toggleBottomTools(STORAGE)} - active={bottomBlock === STORAGE && !inspectorMode} - label={getStorageName(storageType)} - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && ( - <ControlButton - disabled={disabled && !inspectorMode} - onClick={() => toggleBottomTools(STACKEVENTS)} - active={bottomBlock === STACKEVENTS && !inspectorMode} - label="EVENTS" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - hasErrors={stackRedCount > 0} - /> - )} - {!live && showProfiler && ( - <ControlButton - disabled={disabled && !inspectorMode} - onClick={() => toggleBottomTools(PROFILER)} - active={bottomBlock === PROFILER && !inspectorMode} - label="PROFILER" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && ( - <Tooltip title="Fullscreen" delay={0} position="top-end" className="mx-4"> - {this.controlIcon( - 'arrows-angle-extend', - 16, - this.props.fullscreenOn, - false, - 'rounded hover:bg-gray-light-shade color-gray-medium' - )} - </Tooltip> - )} - </div> - </div> - )} - </div> - ); - } -} diff --git a/frontend/app/components/Session_/Player/Controls/Controls.tsx b/frontend/app/components/Session_/Player/Controls/Controls.tsx new file mode 100644 index 000000000..37d8bdbef --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/Controls.tsx @@ -0,0 +1,334 @@ +import React from 'react'; +import cn from 'classnames'; +import { connect } from 'react-redux'; +import { selectStorageType, STORAGE_TYPES } from 'Player'; +import { PlayButton, PlayingState, FullScreenButton } from 'App/player-ui' + +import { Icon, Tooltip } from 'UI'; +import { + CONSOLE, + fullscreenOff, + fullscreenOn, + GRAPHQL, + INSPECTOR, + NETWORK, + OVERVIEW, + PERFORMANCE, + PROFILER, + STACKEVENTS, + STORAGE, + toggleBottomBlock, + changeSkipInterval, +} from 'Duck/components/player'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { fetchSessions } from 'Duck/liveSearch'; + +import Timeline from './Timeline'; +import ControlButton from './ControlButton'; +import PlayerControls from './components/PlayerControls'; + +import styles from './controls.module.css'; +import XRayButton from 'Shared/XRayButton'; + +const SKIP_INTERVALS = { + 2: 2e3, + 5: 5e3, + 10: 1e4, + 15: 15e3, + 20: 2e4, + 30: 3e4, + 60: 6e4, +}; + +function getStorageName(type: any) { + switch (type) { + case STORAGE_TYPES.REDUX: + return 'REDUX'; + case STORAGE_TYPES.MOBX: + return 'MOBX'; + case STORAGE_TYPES.VUEX: + return 'VUEX'; + case STORAGE_TYPES.NGRX: + return 'NGRX'; + case STORAGE_TYPES.ZUSTAND: + return 'ZUSTAND'; + case STORAGE_TYPES.NONE: + return 'STATE'; + } +} + +function Controls(props: any) { + const { player, store } = React.useContext(PlayerContext); + + const { + playing, + completed, + skip, + speed, + cssLoading, + messagesLoading, + inspectorMode, + markedTargets, + exceptionsList, + profilesList, + graphqlList, + logMarkedCountNow: logRedCount, + resourceMarkedCountNow: resourceRedCount, + stackMarkedCountNow: stackRedCount, + } = store.get(); + const { + bottomBlock, + toggleBottomBlock, + fullscreen, + changeSkipInterval, + skipInterval, + disabledRedux, + showStorageRedux, + } = props; + + const storageType = selectStorageType(store.get()); + const disabled = disabledRedux || cssLoading || messagesLoading || inspectorMode || markedTargets; + const profilesCount = profilesList.length; + const graphqlCount = graphqlList.length; + const showGraphql = graphqlCount > 0; + const showProfiler = profilesCount > 0; + const showExceptions = exceptionsList.length > 0; + const showStorage = storageType !== STORAGE_TYPES.NONE || showStorageRedux; + + const onKeyDown = (e: any) => { + if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { + return; + } + if (inspectorMode) { + if (e.key === 'Esc' || e.key === 'Escape') { + player.toggleInspectorMode(false); + } + } + if (e.key === 'Esc' || e.key === 'Escape') { + props.fullscreenOff(); + } + if (e.key === 'ArrowRight') { + forthTenSeconds(); + } + if (e.key === 'ArrowLeft') { + backTenSeconds(); + } + if (e.key === 'ArrowDown') { + player.speedDown(); + } + if (e.key === 'ArrowUp') { + player.speedUp(); + } + }; + + React.useEffect(() => { + document.addEventListener('keydown', onKeyDown.bind(this)); + return () => { + document.removeEventListener('keydown', onKeyDown.bind(this)); + }; + }, []); + + const forthTenSeconds = () => { + // @ts-ignore + player.jumpInterval(SKIP_INTERVALS[skipInterval]); + }; + + const backTenSeconds = () => { + // @ts-ignore + player.jumpInterval(-SKIP_INTERVALS[skipInterval]); + }; + + const toggleBottomTools = (blockName: number) => { + if (blockName === INSPECTOR) { + // player.toggleInspectorMode(false); + bottomBlock && toggleBottomBlock(); + } else { + // player.toggleInspectorMode(false); + toggleBottomBlock(blockName); + } + }; + + const state = completed ? PlayingState.Completed : playing ? PlayingState.Playing : PlayingState.Paused + + return ( + <div className={styles.controls}> + <Timeline /> + {!fullscreen && ( + <div className={cn(styles.buttons, '!px-2')}> + <div className="flex items-center"> + <PlayerControls + skip={skip} + speed={speed} + disabled={disabled} + backTenSeconds={backTenSeconds} + forthTenSeconds={forthTenSeconds} + toggleSpeed={() => player.toggleSpeed()} + toggleSkip={() => player.toggleSkip()} + playButton={<PlayButton state={state} togglePlay={player.togglePlay} iconSize={36} />} + skipIntervals={SKIP_INTERVALS} + setSkipInterval={changeSkipInterval} + currentInterval={skipInterval} + /> + <div className={cn('mx-2')} /> + <XRayButton + isActive={bottomBlock === OVERVIEW && !inspectorMode} + onClick={() => toggleBottomTools(OVERVIEW)} + /> + </div> + + <div className="flex items-center h-full"> + <ControlButton + disabled={disabled && !inspectorMode} + onClick={() => toggleBottomTools(CONSOLE)} + active={bottomBlock === CONSOLE && !inspectorMode} + label="CONSOLE" + noIcon + labelClassName="!text-base font-semibold" + hasErrors={logRedCount > 0 || showExceptions} + containerClassName="mx-2" + /> + + <ControlButton + disabled={disabled && !inspectorMode} + onClick={() => toggleBottomTools(NETWORK)} + active={bottomBlock === NETWORK && !inspectorMode} + label="NETWORK" + hasErrors={resourceRedCount > 0} + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + + <ControlButton + disabled={disabled && !inspectorMode} + onClick={() => toggleBottomTools(PERFORMANCE)} + active={bottomBlock === PERFORMANCE && !inspectorMode} + label="PERFORMANCE" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + + {showGraphql && ( + <ControlButton + disabled={disabled && !inspectorMode} + onClick={() => toggleBottomTools(GRAPHQL)} + active={bottomBlock === GRAPHQL && !inspectorMode} + label="GRAPHQL" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + + {showStorage && ( + <ControlButton + disabled={disabled && !inspectorMode} + onClick={() => toggleBottomTools(STORAGE)} + active={bottomBlock === STORAGE && !inspectorMode} + label={getStorageName(storageType)} + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + <ControlButton + disabled={disabled && !inspectorMode} + onClick={() => toggleBottomTools(STACKEVENTS)} + active={bottomBlock === STACKEVENTS && !inspectorMode} + label="EVENTS" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + hasErrors={stackRedCount > 0} + /> + {showProfiler && ( + <ControlButton + disabled={disabled && !inspectorMode} + onClick={() => toggleBottomTools(PROFILER)} + active={bottomBlock === PROFILER && !inspectorMode} + label="PROFILER" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + + <Tooltip title="Fullscreen" delay={0} placement="top-start" className="mx-4"> + <FullScreenButton + size={16} + onClick={props.fullscreenOn} + customClasses={'rounded hover:bg-gray-light-shade color-gray-medium'} + /> + </Tooltip> + </div> + </div> + )} + </div> + ); +} + +const ControlPlayer = observer(Controls); + +export default connect( + (state: any) => { + const permissions = state.getIn(['user', 'account', 'permissions']) || []; + const isEnterprise = state.getIn(['user', 'account', 'edition']) === 'ee'; + return { + disabledRedux: isEnterprise && !permissions.includes('DEV_TOOLS'), + fullscreen: state.getIn(['components', 'player', 'fullscreen']), + bottomBlock: state.getIn(['components', 'player', 'bottomBlock']), + showStorageRedux: !state.getIn(['components', 'player', 'hiddenHints', 'storage']), + showStackRedux: !state.getIn(['components', 'player', 'hiddenHints', 'stack']), + session: state.getIn(['sessions', 'current']), + totalAssistSessions: state.getIn(['liveSearch', 'total']), + skipInterval: state.getIn(['components', 'player', 'skipInterval']), + }; + }, + { + fullscreenOn, + fullscreenOff, + toggleBottomBlock, + fetchSessions, + changeSkipInterval, + } +)(ControlPlayer); + +// shouldComponentUpdate(nextProps) { +// if ( +// nextProps.fullscreen !== props.fullscreen || +// nextProps.bottomBlock !== props.bottomBlock || +// nextProps.live !== props.live || +// nextProps.livePlay !== props.livePlay || +// nextProps.playing !== props.playing || +// nextProps.completed !== props.completed || +// nextProps.skip !== props.skip || +// nextProps.skipToIssue !== props.skipToIssue || +// nextProps.speed !== props.speed || +// nextProps.disabled !== props.disabled || +// nextProps.fullscreenDisabled !== props.fullscreenDisabled || +// // nextProps.inspectorMode !== props.inspectorMode || +// // nextProps.logCount !== props.logCount || +// nextProps.logRedCount !== props.logRedCount || +// nextProps.showExceptions !== props.showExceptions || +// nextProps.resourceRedCount !== props.resourceRedCount || +// nextProps.fetchRedCount !== props.fetchRedCount || +// nextProps.showStack !== props.showStack || +// nextProps.stackCount !== props.stackCount || +// nextProps.stackRedCount !== props.stackRedCount || +// nextProps.profilesCount !== props.profilesCount || +// nextProps.storageCount !== props.storageCount || +// nextProps.storageType !== props.storageType || +// nextProps.showStorage !== props.showStorage || +// nextProps.showProfiler !== props.showProfiler || +// nextProps.showGraphql !== props.showGraphql || +// nextProps.showFetch !== props.showFetch || +// nextProps.fetchCount !== props.fetchCount || +// nextProps.graphqlCount !== props.graphqlCount || +// nextProps.liveTimeTravel !== props.liveTimeTravel || +// nextProps.skipInterval !== props.skipInterval +// ) +// return true; +// return false; +// } diff --git a/frontend/app/components/Session_/Player/Controls/CustomDragLayer.tsx b/frontend/app/components/Session_/Player/Controls/CustomDragLayer.tsx deleted file mode 100644 index 200c1c79f..000000000 --- a/frontend/app/components/Session_/Player/Controls/CustomDragLayer.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React, { memo } from 'react'; -import { useDragLayer } from "react-dnd"; -import Circle from './Circle' -import type { CSSProperties, FC } from 'react' - -const layerStyles: CSSProperties = { - position: "fixed", - pointerEvents: "none", - zIndex: 100, - left: 0, - top: 0, - width: "100%", - height: "100%" - }; - -const ItemTypes = { - BOX: 'box', -} - -function getItemStyles(initialOffset, currentOffset, maxX, minX) { - if (!initialOffset || !currentOffset) { - return { - display: "none" - }; - } - let { x, y } = currentOffset; - // if (isSnapToGrid) { - // x -= initialOffset.x; - // y -= initialOffset.y; - // [x, y] = [x, y]; - // x += initialOffset.x; - // y += initialOffset.y; - // } - if (x > maxX) { - x = maxX; - } - - if (x < minX) { - x = minX; - } - const transform = `translate(${x}px, ${initialOffset.y}px)`; - return { - transition: 'transform 0.1s ease-out', - transform, - WebkitTransform: transform - }; -} - -interface Props { - onDrag: (offset: { x: number, y: number } | null) => void; - maxX: number; - minX: number; -} - -const CustomDragLayer: FC<Props> = memo(function CustomDragLayer(props) { - const { - itemType, - isDragging, - item, - initialOffset, - currentOffset, - } = useDragLayer((monitor) => ({ - item: monitor.getItem(), - itemType: monitor.getItemType(), - initialOffset: monitor.getInitialSourceClientOffset(), - currentOffset: monitor.getSourceClientOffset(), - isDragging: monitor.isDragging(), - })); - - function renderItem() { - switch (itemType) { - case ItemTypes.BOX: - return <Circle />; - default: - return null; - } - } - - if (!isDragging) { - return null; - } - - if (isDragging) { - props.onDrag(currentOffset) - } - - return ( - <div style={layerStyles}> - <div - style={getItemStyles(initialOffset, currentOffset, props.maxX, props.minX)} - > - {renderItem()} - </div> - </div> - ); -}) - -export default CustomDragLayer; diff --git a/frontend/app/components/Session_/Player/Controls/Time.js b/frontend/app/components/Session_/Player/Controls/Time.js index e8221b5b8..85291009f 100644 --- a/frontend/app/components/Session_/Player/Controls/Time.js +++ b/frontend/app/components/Session_/Player/Controls/Time.js @@ -1,47 +1,15 @@ import React from 'react'; -import { Duration } from 'luxon'; -import { connectPlayer } from 'Player'; -import styles from './time.module.css'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { PlayTime } from 'App/player-ui' -const Time = ({ time, isCustom, format = 'm:ss', }) => ( - <div className={ !isCustom ? styles.time : undefined }> - { Duration.fromMillis(time).toFormat(format) } - </div> -) +const ReduxTime = observer(({ format, name, isCustom }) => { + const { store } = React.useContext(PlayerContext) + const time = store.get()[name] -Time.displayName = "Time"; - -const ReduxTime = connectPlayer((state, { name, format }) => ({ - time: state[ name ], - format, -}))(Time); - -const AssistDurationCont = connectPlayer( - state => { - const assistStart = state.assistStart; - return { - assistStart, - } - } -)(({ assistStart }) => { - const [assistDuration, setAssistDuration] = React.useState('00:00'); - React.useEffect(() => { - const interval = setInterval(() => { - setAssistDuration(Duration.fromMillis(+new Date() - assistStart).toFormat('mm:ss')); - } - , 1000); - return () => clearInterval(interval); - }, []) - return ( - <> - Elapsed {assistDuration} - </> - ) + return <PlayTime format={format} time={time} isCustom={isCustom} /> }) -const AssistDuration = React.memo(AssistDurationCont); - ReduxTime.displayName = "ReduxTime"; -export default React.memo(Time); -export { ReduxTime, AssistDuration }; +export { ReduxTime }; diff --git a/frontend/app/components/Session_/Player/Controls/TimeTracker.js b/frontend/app/components/Session_/Player/Controls/TimeTracker.js index b5f9e2de3..4775ce147 100644 --- a/frontend/app/components/Session_/Player/Controls/TimeTracker.js +++ b/frontend/app/components/Session_/Player/Controls/TimeTracker.js @@ -1,19 +1,21 @@ import React from 'react'; -import { connectPlayer } from 'Player'; -import styles from './timeTracker.module.css'; -import cn from 'classnames' +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { ProgressBar } from 'App/player-ui' -const TimeTracker = ({ time, scale, live, left }) => ( - <React.Fragment> - <span - className={ cn(styles.playedTimeline, live && left > 99 ? styles.liveTime : null) } - style={ { width: `${ time * scale }%` } } - /> - </React.Fragment> -); +const TimeTracker = ({ scale, live = false, left }) => { + const { store } = React.useContext(PlayerContext) + const time = store.get().time + + return ( + <ProgressBar + scale={scale} + live={live} + left={left} + time={time} + /> +);} TimeTracker.displayName = 'TimeTracker'; -export default connectPlayer(state => ({ - time: state.time, -}))(TimeTracker); +export default observer(TimeTracker); diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.js b/frontend/app/components/Session_/Player/Controls/Timeline.js deleted file mode 100644 index 3ff810d57..000000000 --- a/frontend/app/components/Session_/Player/Controls/Timeline.js +++ /dev/null @@ -1,240 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import { Icon } from 'UI' -import { connectPlayer, Controls, toggleTimetravel } from 'Player'; -import TimeTracker from './TimeTracker'; -import stl from './timeline.module.css'; -import { setTimelinePointer, setTimelineHoverTime } from 'Duck/sessions'; -import DraggableCircle from './DraggableCircle'; -import CustomDragLayer from './CustomDragLayer'; -import { debounce } from 'App/utils'; -import TooltipContainer from './components/TooltipContainer'; - -const BOUNDRY = 0; - -function getTimelinePosition(value, scale) { - const pos = value * scale; - - return pos > 100 ? 99 : pos; -} - -let deboucneJump = () => null; -let debounceTooltipChange = () => null; -@connectPlayer((state) => ({ - playing: state.playing, - time: state.time, - skipIntervals: state.skipIntervals, - events: state.eventList, - skip: state.skip, - skipToIssue: state.skipToIssue, - disabled: state.cssLoading || state.messagesLoading || state.markedTargets, - endTime: state.endTime, - live: state.live, - notes: state.notes, -})) -@connect( - (state) => ({ - issues: state.getIn(['sessions', 'current', 'issues']), - startedAt: state.getIn(['sessions', 'current', 'startedAt']), - tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']), - }), - { setTimelinePointer, setTimelineHoverTime } -) -export default class Timeline extends React.PureComponent { - progressRef = React.createRef(); - timelineRef = React.createRef(); - wasPlaying = false; - - seekProgress = (e) => { - const time = this.getTime(e); - this.props.jump(time); - this.hideTimeTooltip(); - }; - - loadAndSeek = async (e) => { - e.persist(); - await toggleTimetravel(); - - setTimeout(() => { - this.seekProgress(e); - }); - }; - - jumpToTime = (e) => { - if (this.props.live && !this.props.liveTimeTravel) { - this.loadAndSeek(e); - } else { - this.seekProgress(e); - } - }; - - getTime = (e, customEndTime) => { - const { endTime } = this.props; - const p = e.nativeEvent.offsetX / e.target.offsetWidth; - const targetTime = customEndTime || endTime; - const time = Math.max(Math.round(p * targetTime), 0); - - return time; - }; - - createEventClickHandler = (pointer) => (e) => { - e.stopPropagation(); - this.props.jump(pointer.time); - this.props.setTimelinePointer(pointer); - }; - - componentDidMount() { - const { issues, skipToIssue } = this.props; - const firstIssue = issues.get(0); - deboucneJump = debounce(this.props.jump, 500); - debounceTooltipChange = debounce(this.props.setTimelineHoverTime, 50); - - if (firstIssue && skipToIssue) { - this.props.jump(firstIssue.time); - } - } - - onDragEnd = () => { - const { live, liveTimeTravel } = this.props; - if (live && !liveTimeTravel) return; - - if (this.wasPlaying) { - this.props.togglePlay(); - } - }; - - onDrag = (offset) => { - const { endTime, live, liveTimeTravel } = this.props; - if (live && !liveTimeTravel) return; - - const p = (offset.x - BOUNDRY) / this.progressRef.current.offsetWidth; - const time = Math.max(Math.round(p * endTime), 0); - deboucneJump(time); - this.hideTimeTooltip(); - if (this.props.playing) { - this.wasPlaying = true; - this.props.pause(); - } - }; - - getLiveTime = (e) => { - const { startedAt } = this.props; - const duration = new Date().getTime() - startedAt; - const p = e.nativeEvent.offsetX / e.target.offsetWidth; - const time = Math.max(Math.round(p * duration), 0); - - return [time, duration]; - }; - - showTimeTooltip = (e) => { - if (e.target !== this.progressRef.current && e.target !== this.timelineRef.current) { - return this.props.tooltipVisible && this.hideTimeTooltip(); - } - - const { live } = this.props; - let timeLineTooltip; - - if (live) { - const [time, duration] = this.getLiveTime(e); - timeLineTooltip = { - time: duration - time, - offset: e.nativeEvent.offsetX, - isVisible: true, - }; - } else { - const time = this.getTime(e); - timeLineTooltip = { - time: time, - offset: e.nativeEvent.offsetX, - isVisible: true, - }; - } - - debounceTooltipChange(timeLineTooltip); - }; - - hideTimeTooltip = () => { - const timeLineTooltip = { isVisible: false }; - debounceTooltipChange(timeLineTooltip); - }; - - render() { - const { events, skip, skipIntervals, disabled, endTime, live, notes } = this.props; - - const scale = 100 / endTime; - - return ( - <div - className="flex items-center absolute w-full" - style={{ - top: '-4px', - zIndex: 100, - padding: `0 ${BOUNDRY}px`, - maxWidth: 'calc(100% - 1rem)', - left: '0.5rem', - }} - > - <div - className={stl.progress} - onClick={disabled ? null : this.jumpToTime} - ref={this.progressRef} - role="button" - onMouseMoveCapture={this.showTimeTooltip} - onMouseEnter={this.showTimeTooltip} - onMouseLeave={this.hideTimeTooltip} - > - <TooltipContainer live={live} /> - {/* custo color is live */} - <DraggableCircle - left={this.props.time * scale} - onDrop={this.onDragEnd} - live={this.props.live} - /> - <CustomDragLayer - onDrag={this.onDrag} - minX={BOUNDRY} - maxX={this.progressRef.current && this.progressRef.current.offsetWidth + BOUNDRY} - /> - <TimeTracker scale={scale} live={this.props.live} left={this.props.time * scale} /> - - {!live && skip ? - skipIntervals.map((interval) => ( - <div - key={interval.start} - className={stl.skipInterval} - style={{ - left: `${getTimelinePosition(interval.start, scale)}%`, - width: `${(interval.end - interval.start) * scale}%`, - }} - /> - )) : null} - <div className={stl.timeline} ref={this.timelineRef} /> - - {events.map((e) => ( - <div - key={e.key} - className={stl.event} - style={{ left: `${getTimelinePosition(e.time, scale)}%` }} - /> - ))} - {notes.map((note) => note.timestamp > 0 ? ( - <div - key={note.noteId} - style={{ - position: 'absolute', - background: 'white', - zIndex: 3, - pointerEvents: 'none', - height: 10, - width: 16, - left: `${getTimelinePosition(note.timestamp, scale)}%`, - }} - > - <Icon name="quotes" style={{ width: 16, height: 10 }} color="main" /> - </div> - ) : null)} - </div> - </div> - ); - } -} diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.tsx b/frontend/app/components/Session_/Player/Controls/Timeline.tsx new file mode 100644 index 000000000..7cde52a96 --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/Timeline.tsx @@ -0,0 +1,215 @@ +import React, { useEffect, useMemo, useContext, useState, useRef } from 'react'; +import { connect } from 'react-redux'; +import { Icon } from 'UI' +import TimeTracker from './TimeTracker'; +import stl from './timeline.module.css'; +import { setTimelinePointer, setTimelineHoverTime } from 'Duck/sessions'; +import DraggableCircle from './components/DraggableCircle'; +import CustomDragLayer, { OnDragCallback } from './components/CustomDragLayer'; +import { debounce } from 'App/utils'; +import TooltipContainer from './components/TooltipContainer'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { useStore } from 'App/mstore'; +import { DateTime, Duration } from 'luxon'; +import Issue from "Types/session/issue"; + +function getTimelinePosition(value: number, scale: number) { + const pos = value * scale; + + return pos > 100 ? 99 : pos; +} + +interface IProps { + issues: Issue[] + setTimelineHoverTime: (t: number) => void + startedAt: number + tooltipVisible: boolean +} + +function Timeline(props: IProps) { + const { player, store } = useContext(PlayerContext) + const [wasPlaying, setWasPlaying] = useState(false) + const { notesStore, settingsStore } = useStore(); + const { + playing, + time, + skipIntervals, + eventList: events, + skip, + skipToIssue, + ready, + endTime, + devtoolsLoading, + } = store.get() + const { issues } = props; + const notes = notesStore.sessionNotes + + const progressRef = useRef<HTMLDivElement>(null) + const timelineRef = useRef<HTMLDivElement>(null) + + + const scale = 100 / endTime; + + useEffect(() => { + const firstIssue = issues[0]; + + if (firstIssue && skipToIssue) { + player.jump(firstIssue.time); + } + }, []) + + const debouncedJump = useMemo(() => debounce(player.jump, 500), []) + const debouncedTooltipChange = useMemo(() => debounce(props.setTimelineHoverTime, 50), []) + + const onDragEnd = () => { + if (wasPlaying) { + player.togglePlay(); + } + }; + + const onDrag: OnDragCallback = (offset) => { + // @ts-ignore react mismatch + const p = (offset.x) / progressRef.current.offsetWidth; + const time = Math.max(Math.round(p * endTime), 0); + debouncedJump(time); + hideTimeTooltip(); + if (playing) { + setWasPlaying(true) + player.pause(); + } + }; + + const showTimeTooltip = (e: React.MouseEvent<HTMLDivElement>) => { + if (e.target !== progressRef.current && e.target !== timelineRef.current) { + return props.tooltipVisible && hideTimeTooltip(); + } + + const time = getTime(e); + const tz = settingsStore.sessionSettings.timezone.value + const timeStr = DateTime.fromMillis(props.startedAt + time).setZone(tz).toFormat(`hh:mm:ss a`) + const timeLineTooltip = { + time: Duration.fromMillis(time).toFormat(`mm:ss`), + timeStr, + offset: e.nativeEvent.offsetX, + isVisible: true, + }; + + debouncedTooltipChange(timeLineTooltip); + } + + const hideTimeTooltip = () => { + const timeLineTooltip = { isVisible: false }; + debouncedTooltipChange(timeLineTooltip); + }; + + const seekProgress = (e: React.MouseEvent<HTMLDivElement>) => { + const time = getTime(e); + player.jump(time); + hideTimeTooltip(); + }; + + const jumpToTime = (e: React.MouseEvent<HTMLDivElement>) => { + seekProgress(e); + }; + + const getTime = (e: React.MouseEvent<HTMLDivElement>, customEndTime?: number) => { + // @ts-ignore react mismatch + const p = e.nativeEvent.offsetX / e.target.offsetWidth; + const targetTime = customEndTime || endTime; + + return Math.max(Math.round(p * targetTime), 0); + }; + + return ( + <div + className="flex items-center absolute w-full" + style={{ + top: '-4px', + zIndex: 100, + maxWidth: 'calc(100% - 1rem)', + left: '0.5rem', + }} + > + <div + className={stl.progress} + onClick={ready ? jumpToTime : undefined } + ref={progressRef} + role="button" + onMouseMoveCapture={showTimeTooltip} + onMouseEnter={showTimeTooltip} + onMouseLeave={hideTimeTooltip} + > + <TooltipContainer /> + <DraggableCircle + left={time * scale} + onDrop={onDragEnd} + /> + <CustomDragLayer + onDrag={onDrag} + minX={0} + maxX={progressRef.current ? progressRef.current.offsetWidth : 0} + /> + <TimeTracker scale={scale} left={time * scale} /> + + {skip ? + skipIntervals.map((interval) => ( + <div + key={interval.start} + className={stl.skipInterval} + style={{ + left: `${getTimelinePosition(interval.start, scale)}%`, + width: `${(interval.end - interval.start) * scale}%`, + }} + /> + )) : null} + <div className={stl.timeline} ref={timelineRef}> + {devtoolsLoading || !ready ? <div className={stl.stripes} /> : null} + </div> + + {events.map((e) => ( + <div + /*@ts-ignore TODO */ + key={e.key} + className={stl.event} + style={{ left: `${getTimelinePosition(e.time, scale)}%` }} + /> + ))} + {/* TODO: refactor and make any sense out of this */} + + {/* {issues.map((i: Issue) => (*/} + {/* <div*/} + {/* key={i.key}*/} + {/* className={stl.redEvent}*/} + {/* style={{ left: `${getTimelinePosition(i.time, scale)}%` }}*/} + {/* />*/} + {/*))}*/} + {notes.map((note) => note.timestamp > 0 ? ( + <div + key={note.noteId} + style={{ + position: 'absolute', + background: 'white', + zIndex: 3, + pointerEvents: 'none', + height: 10, + width: 16, + left: `${getTimelinePosition(note.timestamp, scale)}%`, + }} + > + <Icon name="quotes" style={{ width: 16, height: 10 }} color="main" /> + </div> + ) : null)} + </div> + </div> + ) +} + +export default connect( + (state: any) => ({ + issues: state.getIn(['sessions', 'current']).issues || [], + startedAt: state.getIn(['sessions', 'current']).startedAt || 0, + tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']), + }), + { setTimelinePointer, setTimelineHoverTime } +)(observer(Timeline)) diff --git a/frontend/app/components/Session_/Player/Controls/Circle.tsx b/frontend/app/components/Session_/Player/Controls/components/Circle.tsx similarity index 90% rename from frontend/app/components/Session_/Player/Controls/Circle.tsx rename to frontend/app/components/Session_/Player/Controls/components/Circle.tsx index 73e1e1bb1..a658d5dd7 100644 --- a/frontend/app/components/Session_/Player/Controls/Circle.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/Circle.tsx @@ -1,5 +1,5 @@ import React, { memo, FC } from 'react'; -import styles from './timeline.module.css'; +import styles from '../timeline.module.css'; import cn from 'classnames'; interface Props { diff --git a/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx b/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx index 2fe7075f1..c51f8a54c 100644 --- a/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx @@ -7,10 +7,11 @@ import { setCreateNoteTooltip, addNote, updateNote } from 'Duck/sessions'; import stl from './styles.module.css'; import { useStore } from 'App/mstore'; import { toast } from 'react-toastify'; -import { injectNotes } from 'Player'; import { fetchList as fetchSlack } from 'Duck/integrations/slack'; +import { fetchList as fetchTeams } from 'Duck/integrations/teams'; + import Select from 'Shared/Select'; -import { TeamBadge } from 'Shared/SessionListContainer/components/Notes' +import { TeamBadge } from 'Shared/SessionListContainer/components/Notes'; import { List } from 'immutable'; interface Props { @@ -23,7 +24,9 @@ interface Props { isEdit: string; editNote: WriteNote; slackChannels: List<Record<string, any>>; + teamsChannels: List<Record<string, any>>; fetchSlack: () => void; + fetchTeams: () => void; } function CreateNote({ @@ -37,12 +40,18 @@ function CreateNote({ updateNote, slackChannels, fetchSlack, + teamsChannels, + fetchTeams, }: Props) { const [text, setText] = React.useState(''); - const [channel, setChannel] = React.useState(''); + const [slackChannel, setSlackChannel] = React.useState(''); + const [teamsChannel, setTeamsChannel] = React.useState(''); const [isPublic, setPublic] = React.useState(false); const [tag, setTag] = React.useState<iTag>(TAGS[0]); const [useTimestamp, setUseTs] = React.useState(true); + const [useSlack, setSlack] = React.useState(false); + const [useTeams, setTeams] = React.useState(false); + const inputRef = React.createRef<HTMLTextAreaElement>(); const { notesStore } = useStore(); @@ -59,13 +68,21 @@ function CreateNote({ React.useEffect(() => { if (inputRef.current && isVisible) { - fetchSlack(); inputRef.current.focus(); + if (teamsChannels.size === 0 || slackChannels.size === 0) { + fetchSlack(); + fetchTeams(); + } } }, [isVisible]); const duration = Duration.fromMillis(time).toFormat('mm:ss'); + const cleanUp = () => { + setCreateNoteTooltip({ isVisible: false, time: 0 }); + setText(''); + setTag(TAGS[0]); + } const onSubmit = () => { if (text === '') return; @@ -76,19 +93,21 @@ function CreateNote({ isPublic, }; const onSuccess = (noteId: string) => { - if (channel) { - notesStore.sendSlackNotification(noteId, channel) + if (slackChannel) { + notesStore.sendSlackNotification(noteId, slackChannel); } - } + if (teamsChannel) { + notesStore.sendMsTeamsNotification(noteId, teamsChannel); + } + }; if (isEdit) { return notesStore - .updateNote(editNote.noteId, note) + .updateNote(editNote.noteId!, note) .then((r) => { toast.success('Note updated'); - notesStore.fetchSessionNotes(sessionId).then((notes) => { - injectNotes(notes); - onSuccess(editNote.noteId) - updateNote(r); + notesStore.fetchSessionNotes(sessionId).then(() => { + onSuccess(editNote.noteId!); + updateNote(r as Note); }); }) .catch((e) => { @@ -96,20 +115,17 @@ function CreateNote({ console.error(e); }) .finally(() => { - setCreateNoteTooltip({ isVisible: false, time: 0 }); - setText(''); - setTag(undefined); + cleanUp() }); } return notesStore .addNote(sessionId, note) .then((r) => { - onSuccess(r.noteId as unknown as string) + onSuccess(r!.noteId as unknown as string); toast.success('Note added'); - notesStore.fetchSessionNotes(sessionId).then((notes) => { - injectNotes(notes); - addNote(r); + notesStore.fetchSessionNotes(sessionId).then(() => { + addNote(r as Note); }); }) .catch((e) => { @@ -117,13 +133,12 @@ function CreateNote({ console.error(e); }) .finally(() => { - setCreateNoteTooltip({ isVisible: false, time: 0 }); - setText(''); - setTag(undefined); + cleanUp() }); }; const closeTooltip = () => { + cleanUp() setCreateNoteTooltip({ isVisible: false, time: 100 }); }; @@ -133,33 +148,49 @@ function CreateNote({ setTag(tag); }; - const slackChannelsOptions = slackChannels.map(({ webhookId, name }) => ({ - value: webhookId, - label: name, - })).toJS() as unknown as { value: string, label: string }[] + const slackChannelsOptions = slackChannels + .map(({ webhookId, name }) => ({ + value: webhookId, + label: name, + })) + .toJS() as unknown as { value: string; label: string }[]; + const teamsChannelsOptions = teamsChannels + .map(({ webhookId, name }) => ({ + value: webhookId, + label: name, + })) + .toJS() as unknown as { value: string; label: string }[]; - slackChannelsOptions.unshift({ value: null, label: 'Share to slack?' }) + // @ts-ignore + slackChannelsOptions.unshift({ value: null, label: 'Pick a channel' }); + // @ts-ignore + teamsChannelsOptions.unshift({ value: null, label: 'Pick a channel' }); - const changeChannel = ({ value, name }: { value: Record<string, string>; name: string }) => { - setChannel(value.value); + const changeSlackChannel = ({ value }: { value: Record<string, string>; name: string }) => { + setSlackChannel(value.value); + }; + + const changeTeamsChannel = ({ value }: { value: Record<string, string>; name: string }) => { + setTeamsChannel(value.value); }; return ( <div className={stl.noteTooltip} style={{ - top: slackChannelsOptions.length > 0 ? -310 : 255, width: 350, left: 'calc(50% - 175px)', display: isVisible ? 'flex' : 'none', flexDirection: 'column', gap: '1rem', + bottom: '15vh', + zIndex: 110, }} onClick={(e) => e.stopPropagation()} > <div className="flex items-center bg-gray-lightest"> <Icon name="quotes" size={20} /> - <h3 className="text-xl ml-2 mr-4 font-semibold">Add Note</h3> + <h3 className="text-xl ml-2 mr-4 font-semibold">{isEdit ? 'Edit Note' : 'Add Note'}</h3> <div className="flex items-center cursor-pointer" onClick={() => setUseTs(!useTimestamp)}> <Checkbox checked={useTimestamp} /> <span className="ml-1"> {`at ${duration}`} </span> @@ -209,21 +240,50 @@ function CreateNote({ </div> {slackChannelsOptions.length > 0 ? ( - <div> - <Select - options={slackChannelsOptions} - // @ts-ignore - defaultValue - // @ts-ignore - onChange={changeChannel} - className="mr-4" - /> + <div className="flex flex-col"> + <div className="flex items-center cursor-pointer" onClick={() => setSlack(!useSlack)}> + <Checkbox checked={useSlack} /> + <span className="ml-1 mr-3"> Send to slack? </span> + </div> + + {useSlack && ( + <div> + <Select + options={slackChannelsOptions} + // @ts-ignore + defaultValue + // @ts-ignore + onChange={changeSlackChannel} + /> + </div> + )} + </div> + ) : null} + + {teamsChannelsOptions.length > 0 ? ( + <div className="flex flex-col"> + <div className="flex items-center cursor-pointer" onClick={() => setTeams(!useTeams)}> + <Checkbox checked={useTeams} /> + <span className="ml-1 mr-3"> Send to MSTeams? </span> + </div> + + {useTeams && ( + <div> + <Select + options={teamsChannelsOptions} + // @ts-ignore + defaultValue + // @ts-ignore + onChange={changeTeamsChannel} + /> + </div> + )} </div> ) : null} <div className="flex"> <Button variant="primary" className="mr-4" disabled={text === ''} onClick={onSubmit}> - Add Note + {isEdit ? 'Save Note' : 'Add Note'} </Button> <div className="flex items-center cursor-pointer" onClick={() => setPublic(!isPublic)}> <Checkbox checked={isPublic} /> @@ -235,19 +295,17 @@ function CreateNote({ } export default connect( - (state) => { + (state: any) => { const { isVisible, time = 0, isEdit, note: editNote, - // @ts-ignore } = state.getIn(['sessions', 'createNoteTooltip']); - // @ts-ignore const slackChannels = state.getIn(['slack', 'list']); - // @ts-ignore - const sessionId = state.getIn(['sessions', 'current', 'sessionId']); - return { isVisible, time, sessionId, isEdit, editNote, slackChannels }; + const teamsChannels = state.getIn(['teams', 'list']); + const sessionId = state.getIn(['sessions', 'current']).sessionId; + return { isVisible, time, sessionId, isEdit, editNote, slackChannels, teamsChannels }; }, - { setCreateNoteTooltip, addNote, updateNote, fetchSlack } + { setCreateNoteTooltip, addNote, updateNote, fetchSlack, fetchTeams } )(CreateNote); diff --git a/frontend/app/components/Session_/Player/Controls/components/CustomDragLayer.tsx b/frontend/app/components/Session_/Player/Controls/components/CustomDragLayer.tsx new file mode 100644 index 000000000..c1b91412d --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/components/CustomDragLayer.tsx @@ -0,0 +1,85 @@ +import React, { memo, useEffect } from 'react'; +import type { CSSProperties, FC } from 'react' +import { useDragLayer, XYCoord } from "react-dnd"; +import Circle from './Circle' + +const layerStyles: CSSProperties = { + position: "fixed", + pointerEvents: "none", + zIndex: 100, + left: 0, + top: 0, + width: "100%", + height: "100%" +} + + +function getItemStyles( + initialOffset: XYCoord | null, + currentOffset: XYCoord | null, + maxX: number, + minX: number, +) { + if (!initialOffset || !currentOffset) { + return { + display: "none" + } + } + let { x } = currentOffset; + if (x > maxX) { + x = maxX; + } + + if (x < minX) { + x = minX; + } + const transform = `translate(${x}px, ${initialOffset.y}px)`; + return { + transition: 'transform 0.1s ease-out', + transform, + WebkitTransform: transform + } +} + +export type OnDragCallback = (offset: XYCoord) => void + +interface Props { + onDrag: OnDragCallback + maxX: number + minX: number +} + +const CustomDragLayer: FC<Props> = memo(function CustomDragLayer({ maxX, minX, onDrag }) { + const { + isDragging, + initialOffset, + currentOffset, // might be null (why is it not captured by types?) + } = useDragLayer((monitor) => ({ + initialOffset: monitor.getInitialSourceClientOffset(), + currentOffset: monitor.getSourceClientOffset(), + isDragging: monitor.isDragging(), + })) + + useEffect(() => { + if (!isDragging || !currentOffset?.x) { + return + } + onDrag(currentOffset) + }, [isDragging, currentOffset]) + + if (!isDragging || !currentOffset) { + return null; + } + + return ( + <div style={layerStyles}> + <div + style={getItemStyles(initialOffset, currentOffset, maxX, minX)} + > + <Circle /> + </div> + </div> + ) +}) + +export default CustomDragLayer; diff --git a/frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx b/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx similarity index 76% rename from frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx rename to frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx index fb51318c0..91e5d68c7 100644 --- a/frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/DraggableCircle.tsx @@ -1,8 +1,8 @@ -import React, { memo, FC, useEffect, useRef, CSSProperties } from 'react'; +import React, { memo, FC, useEffect, CSSProperties } from 'react'; import type { DragSourceMonitor } from 'react-dnd' import { useDrag } from 'react-dnd' import { getEmptyImage } from 'react-dnd-html5-backend' -import Circle from './Circle' +import { ProgressCircle } from 'App/player-ui' function getStyles( left: number, @@ -31,37 +31,40 @@ const ItemTypes = { } interface Props { - left: number; - top: number; - onDrop?: (item, monitor) => void; + left: number + live?: boolean + onDrop?: () => void } -const DraggableCircle: FC<Props> = memo(function DraggableCircle(props) { - const { left, top, live } = props - const [{ isDragging, item }, dragRef, preview] = useDrag( +const DraggableCircle: FC<Props> = memo(function DraggableCircle({ + left, + live, + onDrop, +}) { + const [{ isDragging }, dragRef, preview] = useDrag( () => ({ type: ItemTypes.BOX, - item: { left, top }, - end: props.onDrop, + item: { left }, + end: onDrop, collect: (monitor: DragSourceMonitor) => ({ isDragging: monitor.isDragging(), item: monitor.getItem(), }), }), - [left, top], + [left], ) useEffect(() => { preview(getEmptyImage(), { captureDraggingState: true }) }, []) - + return ( <div ref={dragRef} style={getStyles(left, isDragging)} role="DraggableBox" > - <Circle isGreen={left > 99 && live} /> + <ProgressCircle isGreen={left > 99 && live} /> </div> ); }) diff --git a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx index bd1225ad4..a1562ac4d 100644 --- a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx @@ -1,13 +1,12 @@ import React from 'react'; import { Icon, Tooltip, Popover } from 'UI'; import cn from 'classnames'; -import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; import { ReduxTime } from '../Time'; // @ts-ignore import styles from '../controls.module.css'; +import { SkipButton } from 'App/player-ui' interface Props { - live: boolean; skip: boolean; speed: number; disabled: boolean; @@ -19,18 +18,10 @@ interface Props { forthTenSeconds: () => void; toggleSpeed: () => void; toggleSkip: () => void; - controlIcon: ( - icon: string, - size: number, - action: () => void, - isBackwards: boolean, - additionalClasses: string - ) => JSX.Element; } function PlayerControls(props: Props) { const { - live, skip, speed, disabled, @@ -42,13 +33,12 @@ function PlayerControls(props: Props) { skipIntervals, setSkipInterval, currentInterval, - controlIcon, } = props; const [showTooltip, setShowTooltip] = React.useState(false); - const speedRef = React.useRef(null); - const arrowBackRef = React.useRef(null); - const arrowForwardRef = React.useRef(null); - const skipRef = React.useRef<HTMLDivElement>(); + const speedRef = React.useRef<HTMLButtonElement>(null); + const arrowBackRef = React.useRef<HTMLButtonElement>(null); + const arrowForwardRef = React.useRef<HTMLButtonElement>(null); + const skipRef = React.useRef<HTMLDivElement>(null); React.useEffect(() => { const handleKeyboard = (e: KeyboardEvent) => { @@ -56,16 +46,16 @@ function PlayerControls(props: Props) { return; } if (e.key === 'ArrowRight') { - arrowForwardRef.current.focus(); + arrowForwardRef.current?.focus(); } if (e.key === 'ArrowLeft') { - arrowBackRef.current.focus(); + arrowBackRef.current?.focus(); } if (e.key === 'ArrowDown') { - speedRef.current.focus(); + speedRef.current?.focus(); } if (e.key === 'ArrowUp') { - speedRef.current.focus(); + speedRef.current?.focus(); } }; document.addEventListener('keydown', handleKeyboard); @@ -75,43 +65,43 @@ function PlayerControls(props: Props) { const toggleTooltip = () => { setShowTooltip(!showTooltip); }; - const handleClickOutside = () => { - setShowTooltip(false); - }; + return ( <div className="flex items-center"> {playButton} <div className="mx-1" /> - {!live && ( - <div className="flex items-center font-semibold text-center" style={{ minWidth: 85 }}> - {/* @ts-ignore */} - <ReduxTime isCustom name="time" format="mm:ss" /> - <span className="px-1">/</span> - {/* @ts-ignore */} - <ReduxTime isCustom name="endTime" format="mm:ss" /> - </div> - )} + + <div className="flex items-center font-semibold text-center" style={{ minWidth: 85 }}> + {/* @ts-ignore */} + <ReduxTime isCustom name="time" format="mm:ss" /> + <span className="px-1">/</span> + {/* @ts-ignore */} + <ReduxTime isCustom name="endTime" format="mm:ss" /> + </div> + <div className="rounded ml-4 bg-active-blue border border-active-blue-border flex items-stretch"> - <button - ref={arrowBackRef} - className="h-full hover:border-active-blue-border focus:border focus:border-blue border-borderColor-transparent" + {/* @ts-ignore */} + <Tooltip + anchorClassName="h-full hover:border-active-blue-border hover:bg-active-blue-border focus:border focus:border-blue border-borderColor-transparent" + title={`Rewind ${currentInterval}s`} + placement="top" > - <Tooltip title="Rewind 10s"> - {controlIcon( - 'skip-forward-fill', - 18, - backTenSeconds, - true, - 'hover:bg-active-blue-border color-main h-full flex items-center' - )} - </Tooltip> - </button> + <button + ref={arrowBackRef} + className="h-full bg-transparent" + > + <SkipButton + size={18} + onClick={backTenSeconds} + isBackwards={true} + customClasses={'hover:bg-active-blue-border color-main h-full flex items-center'} + /> + </button> + </Tooltip> <div className="p-1 border-l border-r bg-active-blue-border border-active-blue-border flex items-center"> <Popover - // open={showTooltip} - // interactive // @ts-ignore theme="nopadding" animation="none" @@ -143,31 +133,33 @@ function PlayerControls(props: Props) { )} > <div onClick={toggleTooltip} ref={skipRef} className="cursor-pointer select-none"> - {/* @ts-ignore */} <Tooltip disabled={showTooltip} title="Set default skip duration"> + {/* @ts-ignore */} {currentInterval}s </Tooltip> </div> </Popover> </div> - <button - ref={arrowForwardRef} - className="h-full hover:border-active-blue-border focus:border focus:border-blue border-borderColor-transparent" + <Tooltip + anchorClassName="h-full hover:border-active-blue-border hover:bg-active-blue-border focus:border focus:border-blue border-borderColor-transparent" + title={`Rewind ${currentInterval}s`} + placement="top" > - <Tooltip title="Forward 10s"> - {controlIcon( - 'skip-forward-fill', - 18, - forthTenSeconds, - false, - 'hover:bg-active-blue-border color-main h-full flex items-center' - )} - </Tooltip> - </button> + <button + ref={arrowForwardRef} + className="h-full bg-transparent" + > + <SkipButton + size={18} + onClick={forthTenSeconds} + customClasses={'hover:bg-active-blue-border color-main h-full flex items-center'} + /> + </button> + </Tooltip> </div> - {!live && ( + <div className="flex items-center"> <div className="mx-2" /> {/* @ts-ignore */} @@ -194,7 +186,7 @@ function PlayerControls(props: Props) { {'Skip Inactivity'} </button> </div> - )} + </div> ); } diff --git a/frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx b/frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx index 7a9f54bcf..a64cbdb0d 100644 --- a/frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx @@ -7,8 +7,7 @@ import { observer } from 'mobx-react-lite'; import { TeamBadge } from 'Shared/SessionListContainer/components/Notes'; interface Props { - userEmail: string; - note: Note; + note?: Note; notFound?: boolean; onClose: () => void; } @@ -17,7 +16,7 @@ function ReadNote(props: Props) { const { settingsStore } = useStore(); const { timezone } = settingsStore.sessionSettings; - if (props.notFound) { + if (props.notFound || props.note === undefined) { return ( <div style={{ position: 'absolute', top: '45%', left: 'calc(50% - 200px)' }}> <div @@ -58,7 +57,7 @@ function ReadNote(props: Props) { <Icon name="quotes" color="main" size={16} /> </div> <div className="ml-2"> - <div className="text-base">{props.userEmail}</div> + <div className="text-base">{props.note.userName}</div> <div className="text-disabled-text text-sm"> {formatTimeOrDate(props.note.createdAt as unknown as number, timezone)} </div> diff --git a/frontend/app/components/Session_/Player/Controls/components/TimeTooltip.tsx b/frontend/app/components/Session_/Player/Controls/components/TimeTooltip.tsx index e1be98622..e47593b97 100644 --- a/frontend/app/components/Session_/Player/Controls/components/TimeTooltip.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/TimeTooltip.tsx @@ -8,31 +8,39 @@ interface Props { time: number; offset: number; isVisible: boolean; - liveTimeTravel: boolean; + timeStr: string; } function TimeTooltip({ time, offset, isVisible, - liveTimeTravel, + timeStr, }: Props) { - const duration = Duration.fromMillis(time).toFormat(`${liveTimeTravel ? '-' : ''}mm:ss`); return ( <div className={stl.timeTooltip} style={{ - top: -30, - left: offset - 20, + top: 0, + left: offset, display: isVisible ? 'block' : 'none', + transform: 'translate(-50%, -110%)', + whiteSpace: 'nowrap', + textAlign: "center", }} > - {!time ? 'Loading' : duration} + {!time ? 'Loading' : time} + {timeStr ? ( + <> + <br /> + <span className="text-gray-light">({timeStr})</span> + </> + ) : null} </div> ); } export default connect((state) => { - const { time = 0, offset = 0, isVisible } = state.getIn(['sessions', 'timeLineTooltip']); - return { time, offset, isVisible }; + const { time = 0, offset = 0, isVisible, timeStr } = state.getIn(['sessions', 'timeLineTooltip']); + return { time, offset, isVisible, timeStr }; })(TimeTooltip); diff --git a/frontend/app/components/Session_/Player/Controls/components/TooltipContainer.tsx b/frontend/app/components/Session_/Player/Controls/components/TooltipContainer.tsx index d1b98eca6..5b14b807a 100644 --- a/frontend/app/components/Session_/Player/Controls/components/TooltipContainer.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/TooltipContainer.tsx @@ -4,12 +4,12 @@ import CreateNote from './CreateNote'; import store from 'App/store'; import { Provider } from 'react-redux'; -function TooltipContainer({ live }: { live: boolean }) { +function TooltipContainer() { return ( <Provider store={store}> <> - <TimeTooltip liveTimeTravel={live} /> + <TimeTooltip /> <CreateNote /> </> </Provider> diff --git a/frontend/app/components/Session_/Player/Controls/components/styles.module.css b/frontend/app/components/Session_/Player/Controls/components/styles.module.css index 2a34e1129..ff59d92e3 100644 --- a/frontend/app/components/Session_/Player/Controls/components/styles.module.css +++ b/frontend/app/components/Session_/Player/Controls/components/styles.module.css @@ -25,14 +25,13 @@ } .noteTooltip { - position: absolute; + position: fixed; padding: 1rem; border-radius: 0.25rem; transition-property: all; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; background: #F5F5F5; - top: -35px; color: black; cursor: default; box-shadow: 0 4px 20px 4px rgb(0 20 60 / 10%), 0 4px 80px -8px rgb(0 20 60 / 20%); diff --git a/frontend/app/components/Session_/Player/Controls/timeline.module.css b/frontend/app/components/Session_/Player/Controls/timeline.module.css index 48217119d..4c84176b5 100644 --- a/frontend/app/components/Session_/Player/Controls/timeline.module.css +++ b/frontend/app/components/Session_/Player/Controls/timeline.module.css @@ -73,6 +73,22 @@ .event.location { background: $blue; } */ +.redEvent { + position: absolute; + width: 2px; + height: 10px; + background: $red; + z-index: 3; + pointer-events: none; + /* top: 0; */ + /* bottom: 0; */ + /* &:hover { + width: 10px; + height: 10px; + margin-left: -6px; + z-index: 1; + };*/ +} .markup { position: absolute; @@ -125,6 +141,8 @@ align-items: center; } + + .clickRage { position: absolute; width: 2px; @@ -195,3 +213,22 @@ border-right: solid 5px transparent; } } + + +.stripes { + background-size: 30px 30px; + width: 100%; + height: 100%; + background-image: linear-gradient(135deg, rgba(0, 0, 0, 0.15) 25%, transparent 25%, transparent 50%, rgba(0, 0, 0, 0.15) 50%, rgba(0, 0, 0, 0.15) 75%, transparent 75%, transparent); + animation: animate-stripes 1.5s linear infinite; + animation-direction: reverse; +} + +@keyframes animate-stripes { + 0% { + background-position: 0 0; + } + 100% { + background-position: 60px 0; + } +} \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Overlay.tsx b/frontend/app/components/Session_/Player/Overlay.tsx index 7b610ded1..ff0344757 100644 --- a/frontend/app/components/Session_/Player/Overlay.tsx +++ b/frontend/app/components/Session_/Player/Overlay.tsx @@ -1,91 +1,47 @@ import React from 'react'; -import { connectPlayer } from 'Player'; -import { getStatusText } from 'Player'; -import type { MarkedTarget } from 'Player'; -import { CallingState, ConnectionStatus, RemoteControlStatus } from 'Player'; - import AutoplayTimer from './Overlay/AutoplayTimer'; import PlayIconLayer from './Overlay/PlayIconLayer'; -import LiveStatusText from './Overlay/LiveStatusText'; import Loader from './Overlay/Loader'; import ElementsMarker from './Overlay/ElementsMarker'; -import RequestingWindow, { WindowType } from 'App/components/Assist/RequestingWindow'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; interface Props { - playing: boolean, - completed: boolean, - inspectorMode: boolean, - loading: boolean, - live: boolean, - liveStatusText: string, - concetionStatus: ConnectionStatus, - autoplay: boolean, - markedTargets: MarkedTarget[] | null, - activeTargetIndex: number, - calling: CallingState, - remoteControl: RemoteControlStatus - - nextId: string, - togglePlay: () => void, + nextId?: string, closedLive?: boolean, - livePlay?: boolean, + isClickmap?: boolean, } function Overlay({ - playing, - completed, - inspectorMode, - loading, - live, - liveStatusText, - concetionStatus, - autoplay, - markedTargets, - activeTargetIndex, nextId, - togglePlay, - closedLive, - livePlay, - calling, - remoteControl, + isClickmap, }: Props) { - const showAutoplayTimer = !live && completed && autoplay && nextId - const showPlayIconLayer = !live && !markedTargets && !inspectorMode && !loading && !showAutoplayTimer; - const showLiveStatusText = live && livePlay && liveStatusText && !loading; + const { player, store } = React.useContext(PlayerContext) - const showRequestWindow = live && (calling === CallingState.Connecting || remoteControl === RemoteControlStatus.Requesting) - const requestWindowType = calling === CallingState.Connecting ? WindowType.Call : remoteControl === RemoteControlStatus.Requesting ? WindowType.Control : null + const togglePlay = () => player.togglePlay() + const { + playing, + messagesLoading, + cssLoading, + completed, + autoplay, + inspectorMode, + markedTargets, + activeTargetIndex, + } = store.get() + const loading = messagesLoading || cssLoading + + const showAutoplayTimer = completed && autoplay && nextId + const showPlayIconLayer = !isClickmap && !markedTargets && !inspectorMode && !loading && !showAutoplayTimer; return ( <> - {showRequestWindow ? <RequestingWindow type={requestWindowType} /> : null} - { showAutoplayTimer && <AutoplayTimer /> } - { showLiveStatusText && - <LiveStatusText text={liveStatusText} concetionStatus={closedLive ? ConnectionStatus.Closed : concetionStatus} /> - } - { loading ? <Loader /> : null } - { showPlayIconLayer && - <PlayIconLayer playing={playing} togglePlay={togglePlay} /> - } - { markedTargets && <ElementsMarker targets={ markedTargets } activeIndex={activeTargetIndex}/> - } + {showAutoplayTimer && <AutoplayTimer />} + {loading ? <Loader /> : null} + {showPlayIconLayer && <PlayIconLayer playing={playing} togglePlay={togglePlay} />} + {markedTargets && <ElementsMarker targets={markedTargets} activeIndex={activeTargetIndex} />} </> ); } - -export default connectPlayer(state => ({ - playing: state.playing, - loading: state.messagesLoading || state.cssLoading, - completed: state.completed, - autoplay: state.autoplay, - inspectorMode: state.inspectorMode, - live: state.live, - liveStatusText: getStatusText(state.peerConnectionStatus), - concetionStatus: state.peerConnectionStatus, - markedTargets: state.markedTargets, - activeTargetIndex: state.activeTargetIndex, - livePlay: state.livePlay, - calling: state.calling, - remoteControl: state.remoteControl, -}))(Overlay); +export default observer(Overlay); diff --git a/frontend/app/components/Session_/Player/Overlay/ElementsMarker.tsx b/frontend/app/components/Session_/Player/Overlay/ElementsMarker.tsx index 99499cac6..ce52b62e7 100644 --- a/frontend/app/components/Session_/Player/Overlay/ElementsMarker.tsx +++ b/frontend/app/components/Session_/Player/Overlay/ElementsMarker.tsx @@ -1,9 +1,16 @@ import React from 'react'; import Marker from './ElementsMarker/Marker'; +import type { MarkedTarget } from 'Player'; -export default function ElementsMarker({ targets, activeIndex }) { - return targets && targets.map(t => <Marker target={t} active={activeIndex === t.index}/>) +export default function ElementsMarker({ targets, activeIndex }: { targets: MarkedTarget[], activeIndex: number }) { + return targets ? <> + {targets.map( + t => <React.Fragment key={t.index}> + <Marker + target={t} + active={activeIndex === t.index} + /> + </React.Fragment> + )} + </> : null } - - - diff --git a/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.tsx b/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.tsx index cbb772020..a81dca109 100644 --- a/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.tsx +++ b/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.tsx @@ -2,8 +2,8 @@ import React from 'react'; import type { MarkedTarget } from 'Player'; import cn from 'classnames'; import stl from './Marker.module.css'; -import { activeTarget } from 'Player'; import { Tooltip } from 'UI'; +import { PlayerContext } from 'App/components/Session/playerContext'; interface Props { target: MarkedTarget; @@ -17,11 +17,14 @@ export default function Marker({ target, active }: Props) { width: `${target.boundingRect.width}px`, height: `${target.boundingRect.height}px`, }; + const { player } = React.useContext(PlayerContext) + return ( <div className={cn(stl.marker, { [stl.active]: active })} style={style} - onClick={() => activeTarget(target.index)} + // @ts-ignore + onClick={() => player.setActiveTarget(target.index)} > <div className={stl.index}>{target.index + 1}</div> <Tooltip open={active} delay={0} title={<div>{target.count} Clicks</div>}> diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js deleted file mode 100644 index 83748dc0d..000000000 --- a/frontend/app/components/Session_/Player/Player.js +++ /dev/null @@ -1,135 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import { findDOMNode } from 'react-dom'; -import cn from 'classnames'; -import { EscapeButton } from 'UI'; -import { hide as hideTargetDefiner } from 'Duck/components/targetDefiner'; -import { fullscreenOff } from 'Duck/components/player'; -import { - NONE, - CONSOLE, - NETWORK, - STACKEVENTS, - STORAGE, - PROFILER, - PERFORMANCE, - GRAPHQL, - EXCEPTIONS, - LONGTASKS, - INSPECTOR, - OVERVIEW, -} from 'Duck/components/player'; -import NetworkPanel from 'Shared/DevTools/NetworkPanel'; -import Console from '../Console/Console'; -import StackEvents from '../StackEvents/StackEvents'; -import Storage from '../Storage'; -import Profiler from '../Profiler'; -import { ConnectedPerformance } from '../Performance'; -import GraphQL from '../GraphQL'; -import Exceptions from '../Exceptions/Exceptions'; -import LongTasks from '../LongTasks'; -import Inspector from '../Inspector'; -import { - attach as attachPlayer, - Controls as PlayerControls, - scale as scalePlayerScreen, - connectPlayer, -} from 'Player'; -import Controls from './Controls'; -import Overlay from './Overlay'; -import stl from './player.module.css'; -import { updateLastPlayedSession } from 'Duck/sessions'; -import OverviewPanel from '../OverviewPanel'; -import ConsolePanel from 'Shared/DevTools/ConsolePanel'; -import ProfilerPanel from 'Shared/DevTools/ProfilerPanel'; -import StackEventPanel from 'Shared/DevTools/StackEventPanel'; - -@connectPlayer((state) => ({ - live: state.live, -})) -@connect( - (state) => { - const isAssist = window.location.pathname.includes('/assist/'); - return { - fullscreen: state.getIn(['components', 'player', 'fullscreen']), - nextId: state.getIn(['sessions', 'nextId']), - sessionId: state.getIn(['sessions', 'current', 'sessionId']), - closedLive: - !!state.getIn(['sessions', 'errors']) || - (isAssist && !state.getIn(['sessions', 'current', 'live'])), - }; - }, - { - hideTargetDefiner, - fullscreenOff, - updateLastPlayedSession, - } -) -export default class Player extends React.PureComponent { - screenWrapper = React.createRef(); - - componentDidUpdate(prevProps) { - if ( - [prevProps.bottomBlock, this.props.bottomBlock].includes(NONE) || - prevProps.fullscreen !== this.props.fullscreen - ) { - scalePlayerScreen(); - } - } - - componentDidMount() { - this.props.updateLastPlayedSession(this.props.sessionId); - if (this.props.closedLive) return; - - const parentElement = findDOMNode(this.screenWrapper.current); //TODO: good architecture - attachPlayer(parentElement); - } - - render() { - const { - className, - bottomBlockIsActive, - fullscreen, - fullscreenOff, - nextId, - closedLive, - bottomBlock, - activeTab, - fullView = false, - } = this.props; - - const maxWidth = activeTab ? 'calc(100vw - 270px)' : '100vw'; - return ( - <div - className={cn(className, stl.playerBody, 'flex flex-col relative', fullscreen && 'pb-2')} - data-bottom-block={bottomBlockIsActive} - > - {fullscreen && <EscapeButton onClose={fullscreenOff} />} - <div className="relative flex-1 overflow-hidden"> - <Overlay nextId={nextId} togglePlay={PlayerControls.togglePlay} closedLive={closedLive} /> - <div className={stl.screenWrapper} ref={this.screenWrapper} /> - </div> - {!fullscreen && !!bottomBlock && ( - <div style={{ maxWidth, width: '100%' }}> - {bottomBlock === OVERVIEW && <OverviewPanel />} - {bottomBlock === CONSOLE && <ConsolePanel />} - {bottomBlock === NETWORK && ( - // <Network /> - <NetworkPanel /> - )} - {/* {bottomBlock === STACKEVENTS && <StackEvents />} */} - {bottomBlock === STACKEVENTS && <StackEventPanel />} - {bottomBlock === STORAGE && <Storage />} - {bottomBlock === PROFILER && <ProfilerPanel />} - {bottomBlock === PERFORMANCE && <ConnectedPerformance />} - {bottomBlock === GRAPHQL && <GraphQL />} - {bottomBlock === EXCEPTIONS && <Exceptions />} - {bottomBlock === LONGTASKS && <LongTasks />} - {bottomBlock === INSPECTOR && <Inspector />} - </div> - )} - {!fullView && <Controls {...PlayerControls} /> } - </div> - ); - } -} diff --git a/frontend/app/components/Session_/Player/index.js b/frontend/app/components/Session_/Player/index.js deleted file mode 100644 index 2b570d433..000000000 --- a/frontend/app/components/Session_/Player/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Player'; diff --git a/frontend/app/components/Session_/PlayerBlock.js b/frontend/app/components/Session_/PlayerBlock.js deleted file mode 100644 index 54130adf5..000000000 --- a/frontend/app/components/Session_/PlayerBlock.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { connect } from 'react-redux'; -import { NONE } from 'Duck/components/player'; -import Player from './Player'; -import SubHeader from './Subheader'; - -import styles from './playerBlock.module.css'; - -@connect((state) => ({ - fullscreen: state.getIn(['components', 'player', 'fullscreen']), - bottomBlock: state.getIn(['components', 'player', 'bottomBlock']), - sessionId: state.getIn(['sessions', 'current', 'sessionId']), - disabled: state.getIn(['components', 'targetDefiner', 'inspectorMode']), - jiraConfig: state.getIn(['issues', 'list']).first(), -})) -export default class PlayerBlock extends React.PureComponent { - render() { - const { fullscreen, bottomBlock, sessionId, disabled, activeTab, jiraConfig, fullView = false } = this.props; - - return ( - <div className={cn(styles.playerBlock, 'flex flex-col overflow-x-hidden')}> - {!fullscreen && !fullView && ( - <SubHeader sessionId={sessionId} disabled={disabled} jiraConfig={jiraConfig} /> - )} - <Player - className="flex-1" - bottomBlockIsActive={!fullscreen && bottomBlock !== NONE} - // bottomBlockIsActive={ true } - bottomBlock={bottomBlock} - fullscreen={fullscreen} - activeTab={activeTab} - fullView={fullView} - /> - </div> - ); - } -} diff --git a/frontend/app/components/Session_/PlayerBlockHeader.js b/frontend/app/components/Session_/PlayerBlockHeader.js deleted file mode 100644 index f8eb6b05b..000000000 --- a/frontend/app/components/Session_/PlayerBlockHeader.js +++ /dev/null @@ -1,169 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import { withRouter } from 'react-router-dom'; -import { sessions as sessionsRoute, assist as assistRoute, liveSession as liveSessionRoute, withSiteId } from 'App/routes'; -import { Icon, BackLink, Link } from 'UI'; -import { toggleFavorite, setSessionPath } from 'Duck/sessions'; -import cn from 'classnames'; -import { connectPlayer, toggleEvents } from 'Player'; -import SessionMetaList from 'Shared/SessionItem/SessionMetaList'; -import UserCard from './EventsBlock/UserCard'; -import Tabs from 'Components/Session/Tabs'; - -import stl from './playerBlockHeader.module.css'; -import AssistActions from '../Assist/components/AssistActions'; -import AssistTabs from '../Assist/components/AssistTabs'; - -const SESSIONS_ROUTE = sessionsRoute(); -const ASSIST_ROUTE = assistRoute(); - -@connectPlayer( - (state) => ({ - width: state.width, - height: state.height, - live: state.live, - loading: state.cssLoading || state.messagesLoading, - showEvents: state.showEvents, - }), - { toggleEvents } -) -@connect( - (state, props) => { - const isAssist = window.location.pathname.includes('/assist/'); - const session = state.getIn(['sessions', 'current']); - - return { - isAssist, - session, - sessionPath: state.getIn(['sessions', 'sessionPath']), - loading: state.getIn(['sessions', 'toggleFavoriteRequest', 'loading']), - disabled: state.getIn(['components', 'targetDefiner', 'inspectorMode']) || props.loading, - local: state.getIn(['sessions', 'timezone']), - funnelRef: state.getIn(['funnels', 'navRef']), - siteId: state.getIn(['site', 'siteId']), - metaList: state.getIn(['customFields', 'list']).map((i) => i.key), - closedLive: !!state.getIn(['sessions', 'errors']) || (isAssist && !session.live), - }; - }, - { - toggleFavorite, - setSessionPath, - } -) -@withRouter -export default class PlayerBlockHeader extends React.PureComponent { - state = { - hideBack: false, - }; - - componentDidMount() { - const { location } = this.props; - const queryParams = new URLSearchParams(location.search); - this.setState({ hideBack: queryParams.has('iframe') && queryParams.get('iframe') === 'true' }); - } - - getDimension = (width, height) => { - return width && height ? ( - <div className="flex items-center"> - {width || 'x'} <Icon name="close" size="12" className="mx-1" /> {height || 'x'} - </div> - ) : ( - <span className="">Resolution N/A</span> - ); - }; - - backHandler = () => { - const { history, siteId, sessionPath, isAssist } = this.props; - if (sessionPath.pathname === history.location.pathname || sessionPath.pathname.includes('/session/') || isAssist) { - history.push(withSiteId(isAssist ? ASSIST_ROUTE : SESSIONS_ROUTE, siteId)); - } else { - history.push(sessionPath ? sessionPath.pathname + sessionPath.search : withSiteId(SESSIONS_ROUTE, siteId)); - } - }; - - toggleFavorite = () => { - const { session } = this.props; - this.props.toggleFavorite(session.sessionId); - }; - - render() { - const { - width, - height, - session, - fullscreen, - metaList, - closedLive = false, - siteId, - isAssist, - setActiveTab, - activeTab, - showEvents, - toggleEvents, - } = this.props; - // const _live = isAssist; - - const { hideBack } = this.state; - - const { sessionId, userId, userNumericHash, live, metadata, isCallActive, agentIds } = session; - let _metaList = Object.keys(metadata) - .filter((i) => metaList.includes(i)) - .map((key) => { - const value = metadata[key]; - return { label: key, value }; - }); - - const TABS = [this.props.tabs.EVENTS, this.props.tabs.HEATMAPS].map((tab) => ({ text: tab, key: tab })); - return ( - <div className={cn(stl.header, 'flex justify-between', { hidden: fullscreen })}> - <div className="flex w-full items-center"> - {!hideBack && ( - <div className="flex items-center h-full" onClick={this.backHandler}> - <BackLink label="Back" className="h-full" /> - <div className={stl.divider} /> - </div> - )} - <UserCard className="" width={width} height={height} /> - {isAssist && <AssistTabs userId={userId} userNumericHash={userNumericHash} />} - - <div className={cn('ml-auto flex items-center h-full', { hidden: closedLive })}> - {live && !isAssist && ( - <> - <div className={cn(stl.liveSwitchButton, 'pr-4')}> - <Link to={withSiteId(liveSessionRoute(sessionId), siteId)}>This Session is Now Continuing Live</Link> - </div> - {_metaList.length > 0 && <div className={stl.divider} />} - </> - )} - - {_metaList.length > 0 && ( - <div className="border-l h-full flex items-center px-2"> - <SessionMetaList className="" metaList={_metaList} maxLength={2} /> - </div> - )} - - {isAssist && <AssistActions userId={userId} isCallActive={isCallActive} agentIds={agentIds} />} - </div> - </div> - {!isAssist && ( - <div className="relative border-l" style={{ minWidth: '270px' }}> - <Tabs - tabs={TABS} - active={activeTab} - onClick={(tab) => { - if (activeTab === tab) { - setActiveTab(''); - toggleEvents(); - } else { - setActiveTab(tab); - !showEvents && toggleEvents(true); - } - }} - border={false} - /> - </div> - )} - </div> - ); - } -} diff --git a/frontend/app/components/Session_/Profiler/Profiler.js b/frontend/app/components/Session_/Profiler/Profiler.DEPRECATED.js similarity index 98% rename from frontend/app/components/Session_/Profiler/Profiler.js rename to frontend/app/components/Session_/Profiler/Profiler.DEPRECATED.js index 398560a3d..9b9e6e42c 100644 --- a/frontend/app/components/Session_/Profiler/Profiler.js +++ b/frontend/app/components/Session_/Profiler/Profiler.DEPRECATED.js @@ -33,7 +33,7 @@ export default class Profiler extends React.PureComponent { return ( <React.Fragment> - <SlideModal + <SlideModal title={ modalProfile && modalProfile.name } isDisplayed={ modalProfile !== null } content={ modalProfile && <ProfileInfo profile={ modalProfile } />} @@ -55,7 +55,7 @@ export default class Profiler extends React.PureComponent { /> </BottomBlock.Header> <BottomBlock.Content> - <TimeTable + <TimeTable rows={ filteredProfiles } onRowClick={ this.onProfileClick } hoverable diff --git a/frontend/app/components/Session_/Autoplay/Autoplay.tsx b/frontend/app/components/Session_/QueueControls/QueueControls.tsx similarity index 51% rename from frontend/app/components/Session_/Autoplay/Autoplay.tsx rename to frontend/app/components/Session_/QueueControls/QueueControls.tsx index 5f54b4a8a..cfb2f7a0c 100644 --- a/frontend/app/components/Session_/Autoplay/Autoplay.tsx +++ b/frontend/app/components/Session_/QueueControls/QueueControls.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from 'react'; import { connect } from 'react-redux'; import { setAutoplayValues } from 'Duck/sessions'; -import { session as sessionRoute } from 'App/routes'; +import { withSiteId, session as sessionRoute } from 'App/routes'; import { Link, Icon, Tooltip } from 'UI'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import cn from 'classnames'; @@ -10,18 +10,20 @@ import { fetchAutoplaySessions } from 'Duck/search'; const PER_PAGE = 10; interface Props extends RouteComponentProps { + siteId: string; previousId: string; nextId: string; defaultList: any; currentPage: number; total: number; - setAutoplayValues?: () => void; + setAutoplayValues: () => void; latestRequestTime: any; sessionIds: any; - fetchAutoplaySessions?: (page: number) => Promise<void>; + fetchAutoplaySessions: (page: number) => Promise<void>; } -function Autoplay(props: Props) { +function QueueControls(props: Props) { const { + siteId, previousId, nextId, currentPage, @@ -30,9 +32,10 @@ function Autoplay(props: Props) { latestRequestTime, match: { // @ts-ignore - params: { siteId, sessionId }, + params: { sessionId }, }, } = props; + const disabled = sessionIds.length === 0; useEffect(() => { @@ -48,43 +51,46 @@ function Autoplay(props: Props) { } }, []); + const nextHandler = () => { + props.history.push(withSiteId(sessionRoute(nextId), siteId)); + }; + + const prevHandler = () => { + props.history.push(withSiteId(sessionRoute(previousId), siteId)); + }; + return ( <div className="flex items-center"> - <Tooltip - placement="bottom" - title={<div className="whitespace-nowrap">Play Previous Session</div>} - disabled={!previousId} + <div + onClick={prevHandler} + className={cn('p-1 bg-gray-bg group rounded-full color-gray-darkest font-medium', { + 'pointer-events-none opacity-50': !previousId, + 'cursor-pointer': !!previousId, + })} > - <Link to={sessionRoute(previousId)} disabled={!previousId}> - <div - className={cn( - 'p-1 bg-gray-bg group rounded-full color-gray-darkest font-medium', - previousId && 'cursor-pointer', - !disabled && nextId && 'hover:bg-bg-blue' - )} - > - <Icon name="prev1" className="group-hover:fill-main" color="inherit" size="16" /> - </div> - </Link> - </Tooltip> - - <Tooltip - placement="bottom" - title={<div className="whitespace-nowrap">Play Next Session</div>} - disabled={!nextId} + <Tooltip + placement="bottom" + title={<div className="whitespace-nowrap">Play Previous Session</div>} + disabled={!previousId} + > + <Icon name="prev1" className="group-hover:fill-main" color="inherit" size="16" /> + </Tooltip> + </div> + <div + onClick={nextHandler} + className={cn('p-1 bg-gray-bg group ml-1 rounded-full color-gray-darkest font-medium', { + 'pointer-events-none opacity-50': !nextId, + 'cursor-pointer': !!nextId, + })} > - <Link to={sessionRoute(nextId)} disabled={!nextId}> - <div - className={cn( - 'p-1 bg-gray-bg group ml-1 rounded-full color-gray-darkest font-medium', - nextId && 'cursor-pointer', - !disabled && nextId && 'hover:bg-bg-blue' - )} - > - <Icon name="next1" className="group-hover:fill-main" color="inherit" size="16" /> - </div> - </Link> - </Tooltip> + <Tooltip + placement="bottom" + title={<div className="whitespace-nowrap">Play Next Session</div>} + disabled={!nextId} + > + <Icon name="next1" className="group-hover:fill-main" color="inherit" size="16" /> + </Tooltip> + </div> </div> ); } @@ -97,6 +103,7 @@ export default connect( total: state.getIn(['sessions', 'total']) || 0, sessionIds: state.getIn(['sessions', 'sessionIds']) || [], latestRequestTime: state.getIn(['search', 'latestRequestTime']), + siteId: state.getIn(['site', 'siteId']), }), { setAutoplayValues, fetchAutoplaySessions } -)(withRouter(Autoplay)); +)(withRouter(QueueControls)); diff --git a/frontend/app/components/Session_/QueueControls/index.ts b/frontend/app/components/Session_/QueueControls/index.ts new file mode 100644 index 000000000..0d45bd91a --- /dev/null +++ b/frontend/app/components/Session_/QueueControls/index.ts @@ -0,0 +1 @@ +export { default } from './QueueControls' diff --git a/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx b/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx new file mode 100644 index 000000000..4f7d9f0bf --- /dev/null +++ b/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx @@ -0,0 +1,137 @@ +import React from 'react'; +import { screenRecorder } from 'App/utils/screenRecorder'; +import { Tooltip } from 'react-tippy'; +import { connect } from 'react-redux'; +import { Button } from 'UI'; +import { SessionRecordingStatus } from 'Player'; +let stopRecorderCb: () => void; +import { recordingsService } from 'App/services'; +import { toast } from 'react-toastify'; +import { formatTimeOrDate } from 'App/date'; +import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { ENTERPRISE_REQUEIRED } from 'App/constants'; + +/** + * "edge" || "edg/" chromium based edge (dev or canary) + * "chrome" && window.chrome chrome + * "opr" && (!!window.opr || !!window.opera) opera + * "trident" ie + * "firefox" firefox + * "safari" safari + */ +function isSupported() { + const agent = window.navigator.userAgent.toLowerCase(); + + if (agent.includes('edge') || agent.includes('edg/')) return true; + // @ts-ignore + return agent.includes('chrome') && !!window.chrome; +} + +const supportedBrowsers = ['Chrome v91+', 'Edge v90+']; +const supportedMessage = `Supported Browsers: ${supportedBrowsers.join(', ')}`; + +function ScreenRecorder({ + siteId, + sessionId, + isEnterprise, +}: { + siteId: string; + sessionId: string; + isEnterprise: boolean; +}) { + const { player, store } = React.useContext(PlayerContext) as ILivePlayerContext; + const recordingState = store.get().recordingState; + + const [isRecording, setRecording] = React.useState(false); + + React.useEffect(() => { + return () => stopRecorderCb?.(); + }, []); + + const onSave = async (saveObj: { name: string; duration: number }, blob: Blob) => { + try { + toast.warn('Uploading the recording...'); + const { URL, key } = await recordingsService.reserveUrl(siteId, { ...saveObj, sessionId }); + const status = await recordingsService.saveFile(URL, blob); + + if (status) { + await recordingsService.confirmFile(siteId, { ...saveObj, sessionId }, key); + toast.success('Session recording uploaded'); + } + } catch (e) { + console.error(e); + toast.error("Couldn't upload the file"); + } + }; + + React.useEffect(() => { + if (!isRecording && recordingState === SessionRecordingStatus.Recording) { + void startRecording(); + } + if (isRecording && recordingState !== SessionRecordingStatus.Recording) { + stopRecordingHandler(); + } + }, [recordingState, isRecording]); + + const onStop = () => { + setRecording(false); + player.assistManager.stopRecording(); + }; + + const startRecording = async () => { + try { + // @ts-ignore + stopRecorderCb = await screenRecorder( + `${formatTimeOrDate(new Date().getTime(), undefined, true)}_${sessionId}`, + sessionId, + onSave, + onStop + ); + setRecording(true); + } catch (e) { + console.error(e); + } + }; + + const stopRecordingHandler = () => { + stopRecorderCb?.(); + onStop(); + }; + + const recordingRequest = () => { + player.assistManager.requestRecording(); + }; + + if (!isSupported() || !isEnterprise) { + return ( + <div className="p-2"> + {/* @ts-ignore */} + <Tooltip + title={isEnterprise ? supportedMessage : ENTERPRISE_REQUEIRED} + > + <Button icon="record-circle" disabled variant="text-primary"> + Record Activity + </Button> + </Tooltip> + </div> + ); + } + + return ( + <div onClick={!isRecording ? recordingRequest : stopRecordingHandler} className="p-2"> + <Button + icon={!isRecording ? 'stop-record-circle' : 'record-circle'} + variant={isRecording ? 'text-red' : 'text-primary'} + > + {isRecording ? 'Stop Recording' : 'Record Activity'} + </Button> + </div> + ); +} + +export default connect((state: any) => ({ + isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee', + siteId: state.getIn(['site', 'siteId']), + sessionId: state.getIn(['sessions', 'current']).sessionId, +}))(observer(ScreenRecorder)); diff --git a/frontend/app/components/Session_/StackEvents/StackEvents.js b/frontend/app/components/Session_/StackEvents/StackEvents.DEPRECATED.js similarity index 99% rename from frontend/app/components/Session_/StackEvents/StackEvents.js rename to frontend/app/components/Session_/StackEvents/StackEvents.DEPRECATED.js index 516a9adde..3ed7bab84 100644 --- a/frontend/app/components/Session_/StackEvents/StackEvents.js +++ b/frontend/app/components/Session_/StackEvents/StackEvents.DEPRECATED.js @@ -11,7 +11,6 @@ import { DATADOG, SENTRY, STACKDRIVER, typeList } from 'Types/session/stackEvent import { NoContent, SlideModal, Tabs, Link } from 'UI'; import Autoscroll from '../Autoscroll'; import BottomBlock from '../BottomBlock'; -import UserEvent from './UserEvent'; const ALL = 'ALL'; diff --git a/frontend/app/components/Session_/StackEvents/UserEvent/UserEvent.js b/frontend/app/components/Session_/StackEvents/UserEvent/UserEvent.js index 2dfd4d8aa..053d2c48e 100644 --- a/frontend/app/components/Session_/StackEvents/UserEvent/UserEvent.js +++ b/frontend/app/components/Session_/StackEvents/UserEvent/UserEvent.js @@ -1,12 +1,8 @@ import React from 'react'; import cn from 'classnames'; -import { OPENREPLAY, SENTRY, DATADOG, STACKDRIVER } from 'Types/session/stackEvent'; +import { OPENREPLAY } from 'Types/session/stackEvent'; import { Icon } from 'UI'; import withToggle from 'HOCs/withToggle'; -import Sentry from './Sentry'; -import JsonViewer from './JsonViewer'; -import stl from './userEvent.module.css'; -import { Duration } from 'luxon'; import JumpButton from 'Shared/DevTools/JumpButton'; // const modalSources = [ SENTRY, DATADOG ]; @@ -24,7 +20,7 @@ export default class UserEvent extends React.PureComponent { getLevelClassname() { const { userEvent } = this.props; - if (userEvent.isRed()) return 'error color-red'; + if (userEvent.isRed) return 'error color-red'; return ''; } @@ -39,7 +35,7 @@ export default class UserEvent extends React.PureComponent { message = typeof message === 'string' ? message : JSON.stringify(message); return ( <div - data-scroll-item={userEvent.isRed()} + data-scroll-item={userEvent.isRed} onClick={this.onClickDetails} className={cn( 'group flex items-center py-2 px-4 border-b cursor-pointer relative', diff --git a/frontend/app/components/Session_/Storage/DiffRow.tsx b/frontend/app/components/Session_/Storage/DiffRow.tsx index ebc54ad4d..73e7c7d37 100644 --- a/frontend/app/components/Session_/Storage/DiffRow.tsx +++ b/frontend/app/components/Session_/Storage/DiffRow.tsx @@ -2,8 +2,8 @@ import React from 'react'; import cn from 'classnames'; interface Props { - shades: Record<string, string>; - pathRoot: string; + shades?: Record<string, string>; + pathRoot?: string; path: string; diff: Record<string, any>; } diff --git a/frontend/app/components/Session_/Storage/Storage.js b/frontend/app/components/Session_/Storage/Storage.DEPRECATED.js similarity index 100% rename from frontend/app/components/Session_/Storage/Storage.js rename to frontend/app/components/Session_/Storage/Storage.DEPRECATED.js diff --git a/frontend/app/components/Session_/Storage/Storage.tsx b/frontend/app/components/Session_/Storage/Storage.tsx new file mode 100644 index 000000000..f955912e7 --- /dev/null +++ b/frontend/app/components/Session_/Storage/Storage.tsx @@ -0,0 +1,308 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { hideHint } from 'Duck/components/player'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { JSONTree, NoContent, Tooltip } from 'UI'; +import { formatMs } from 'App/date'; +import { diff } from 'deep-diff'; +import { STORAGE_TYPES, selectStorageList, selectStorageListNow, selectStorageType } from 'Player'; +import Autoscroll from '../Autoscroll'; +import BottomBlock from '../BottomBlock/index'; +import DiffRow from './DiffRow'; +import cn from 'classnames'; +import stl from './storage.module.css'; + +function getActionsName(type: string) { + switch (type) { + case STORAGE_TYPES.MOBX: + case STORAGE_TYPES.VUEX: + return 'MUTATIONS'; + default: + return 'ACTIONS'; + } +} + +interface Props { + hideHint: (args: string) => void; + hintIsHidden: boolean; +} +function Storage(props: Props) { + const lastBtnRef = React.useRef<HTMLButtonElement>(); + const [showDiffs, setShowDiffs] = React.useState(false); + const { player, store } = React.useContext(PlayerContext); + const state = store.get(); + + const listNow = selectStorageListNow(state); + const list = selectStorageList(state); + const type = selectStorageType(state); + + const focusNextButton = () => { + if (lastBtnRef.current) { + lastBtnRef.current.focus(); + } + }; + + React.useEffect(() => { + focusNextButton(); + }, []); + React.useEffect(() => { + focusNextButton(); + }, [listNow]); + + const renderDiff = (item: Record<string, any>, prevItem: Record<string, any>) => { + if (!prevItem) { + // we don't have state before first action + return <div style={{ flex: 3 }} className="p-1" />; + } + + const stateDiff = diff(prevItem.state, item.state); + + if (!stateDiff) { + return ( + <div style={{ flex: 3 }} className="flex flex-col p-2 pr-0 font-mono text-disabled-text"> + No diff + </div> + ); + } + + return ( + <div style={{ flex: 3 }} className="flex flex-col p-1 font-mono"> + {stateDiff.map((d: Record<string, any>, i: number) => renderDiffs(d, i))} + </div> + ); + }; + + const renderDiffs = (diff: Record<string, any>, i: number) => { + const path = createPath(diff); + return ( + <React.Fragment key={i}> + <DiffRow path={path} diff={diff} /> + </React.Fragment> + ); + }; + + const createPath = (diff: Record<string, any>) => { + let path: string[] = []; + + if (diff.path) { + path = path.concat(diff.path); + } + if (typeof diff.index !== 'undefined') { + path.push(diff.index); + } + + const pathStr = path.length ? path.join('.') : ''; + return pathStr; + }; + + const ensureString = (actionType: string) => { + if (typeof actionType === 'string') return actionType; + return 'UNKNOWN'; + }; + + const goNext = () => { + // , list[listNow.length]._index + player.jump(list[listNow.length].time); + }; + + const renderItem = (item: Record<string, any>, i: number, prevItem: Record<string, any>) => { + let src; + let name; + + switch (type) { + case STORAGE_TYPES.REDUX: + case STORAGE_TYPES.NGRX: + src = item.action; + name = src && src.type; + break; + case STORAGE_TYPES.VUEX: + src = item.mutation; + name = src && src.type; + break; + case STORAGE_TYPES.MOBX: + src = item.payload; + name = `@${item.type} ${src && src.type}`; + break; + case STORAGE_TYPES.ZUSTAND: + src = null; + name = item.mutation.join(''); + } + + if (src !== null && !showDiffs) { + setShowDiffs(true); + } + + return ( + <div + className={cn('flex justify-between items-start', src !== null ? 'border-b' : '')} + key={`store-${i}`} + > + {src === null ? ( + <div className="font-mono" style={{ flex: 2, marginLeft: '26.5%' }}> + {name} + </div> + ) : ( + <> + {renderDiff(item, prevItem)} + <div style={{ flex: 2 }} className="flex pl-10 pt-2"> + <JSONTree + name={ensureString(name)} + src={src} + collapsed + collapseStringsAfterLength={7} + /> + </div> + </> + )} + <div + style={{ flex: 1 }} + className="flex-1 flex gap-2 pt-2 items-center justify-end self-start" + > + {typeof item.duration === 'number' && ( + <div className="font-size-12 color-gray-medium">{formatMs(item.duration)}</div> + )} + <div className="w-12"> + {i + 1 < listNow.length && ( + <button className={stl.button} onClick={() => jump(item.time, item._index)}> + {'JUMP'} + </button> + )} + {i + 1 === listNow.length && i + 1 < list.length && ( + <button className={stl.button} ref={lastBtnRef} onClick={goNext}> + {'NEXT'} + </button> + )} + </div> + </div> + </div> + ); + }; + + const { hintIsHidden } = props; + + const showStore = type !== STORAGE_TYPES.MOBX; + return ( + <BottomBlock> + <BottomBlock.Header> + {list.length > 0 && ( + <div className="flex w-full"> + {showStore && ( + <h3 style={{ width: '25%', marginRight: 20 }} className="font-semibold"> + {'STATE'} + </h3> + )} + {showDiffs ? ( + <h3 style={{ width: '39%' }} className="font-semibold"> + DIFFS + </h3> + ) : null} + <h3 style={{ width: '30%' }} className="font-semibold"> + {getActionsName(type)} + </h3> + <h3 style={{ paddingRight: 30, marginLeft: 'auto' }} className="font-semibold"> + <Tooltip title="Time to execute">TTE</Tooltip> + </h3> + </div> + )} + </BottomBlock.Header> + <BottomBlock.Content className="flex"> + <NoContent + title="Nothing to display yet." + subtext={ + !hintIsHidden ? ( + <> + { + 'Inspect your application state while you’re replaying your users sessions. OpenReplay supports ' + } + <a + className="underline color-teal" + href="https://docs.openreplay.com/plugins/redux" + target="_blank" + > + Redux + </a> + {', '} + <a + className="underline color-teal" + href="https://docs.openreplay.com/plugins/vuex" + target="_blank" + > + VueX + </a> + {', '} + <a + className="underline color-teal" + href="https://docs.openreplay.com/plugins/pinia" + target="_blank" + > + Pinia + </a> + {', '} + <a + className="underline color-teal" + href="https://docs.openreplay.com/plugins/zustand" + target="_blank" + > + Zustand + </a> + {', '} + <a + className="underline color-teal" + href="https://docs.openreplay.com/plugins/mobx" + target="_blank" + > + MobX + </a> + {' and '} + <a + className="underline color-teal" + href="https://docs.openreplay.com/plugins/ngrx" + target="_blank" + > + NgRx + </a> + . + <br /> + <br /> + <button className="color-teal" onClick={() => props.hideHint('storage')}> + Got It! + </button> + </> + ) : null + } + size="small" + show={list.length === 0} + > + {showStore && ( + <div className="ph-10 scroll-y" style={{ width: '25%' }}> + {list.length === 0 ? ( + <div className="color-gray-light font-size-16 mt-20 text-center"> + {'Empty state.'} + </div> + ) : ( + <JSONTree collapsed={2} src={listNow.length === 0 ? list[0].state : listNow[listNow.length - 1].state} /> + )} + </div> + )} + <div className="flex" style={{ width: showStore ? '75%' : '100%' }}> + <Autoscroll className="ph-10"> + {listNow.map((item: Record<string, any>, i: number) => + renderItem(item, i, i > 0 ? listNow[i - 1] : undefined) + )} + </Autoscroll> + </div> + </NoContent> + </BottomBlock.Content> + </BottomBlock> + ); +} + +export default connect( + (state: any) => ({ + hintIsHidden: state.getIn(['components', 'player', 'hiddenHints', 'storage']), + }), + { + hideHint, + } +)(observer(Storage)); diff --git a/frontend/app/components/Session_/Storage/Storge.DEPRECATED.js b/frontend/app/components/Session_/Storage/Storge.DEPRECATED.js index 660d2057e..fc5b19416 100644 --- a/frontend/app/components/Session_/Storage/Storge.DEPRECATED.js +++ b/frontend/app/components/Session_/Storage/Storge.DEPRECATED.js @@ -11,8 +11,6 @@ import { import { JSONTree, NoContent } from 'UI'; import { formatMs } from 'App/date'; import { diff } from 'deep-diff'; -import DiffTree from './DiffTree' -import { setIn } from 'immutable'; import { jump } from 'Player'; import Autoscroll from '../Autoscroll'; import BottomBlock from '../BottomBlock/index'; diff --git a/frontend/app/components/Session_/Subheader.js b/frontend/app/components/Session_/Subheader.js index b45c49e00..8a8d4d2f6 100644 --- a/frontend/app/components/Session_/Subheader.js +++ b/frontend/app/components/Session_/Subheader.js @@ -1,119 +1,157 @@ import React from 'react'; import { Icon, Tooltip, Button } from 'UI'; -import Autoplay from './Autoplay'; +import QueueControls from './QueueControls'; import Bookmark from 'Shared/Bookmark'; import SharePopup from '../shared/SharePopup/SharePopup'; import copy from 'copy-to-clipboard'; import Issues from './Issues/Issues'; import NotePopup from './components/NotePopup'; -import { connectPlayer, pause } from 'Player'; import ItemMenu from './components/HeaderMenu'; import { useModal } from 'App/components/Modal'; import BugReportModal from './BugReport/BugReportModal'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; import AutoplayToggle from 'Shared/AutoplayToggle'; +import { connect } from 'react-redux' + +const localhostWarn = (project) => project + '_localhost_warn' function SubHeader(props) { + const localhostWarnKey = localhostWarn(props.siteId) + const defaultLocalhostWarn = localStorage.getItem(localhostWarnKey) !== '1' + const [showWarningModal, setWarning] = React.useState(defaultLocalhostWarn); + const { player, store } = React.useContext(PlayerContext); + const { + width, + height, + location: currentLocation, + fetchList, + graphqlList, + resourceList, + exceptionsList, + eventList: eventsList, + endTime, + } = store.get(); + + const mappedResourceList = resourceList + .filter((r) => r.isRed || r.isYellow) + .concat(fetchList.filter((i) => parseInt(i.status) >= 400)) + .concat(graphqlList.filter((i) => parseInt(i.status) >= 400)); + const [isCopied, setCopied] = React.useState(false); const { showModal, hideModal } = useModal(); - const isAssist = window.location.pathname.includes('/assist/'); const location = - props.currentLocation && props.currentLocation.length > 60 - ? `${props.currentLocation.slice(0, 60)}...` - : props.currentLocation; + currentLocation && currentLocation.length > 70 + ? `${currentLocation.slice(0, 25)}...${currentLocation.slice(-40)}` + : currentLocation; const showReportModal = () => { - pause(); + player.pause(); const xrayProps = { - currentLocation: props.currentLocation, - resourceList: props.resourceList, - exceptionsList: props.exceptionsList, - eventsList: props.eventsList, - endTime: props.endTime, + currentLocation: currentLocation, + resourceList: mappedResourceList, + exceptionsList: exceptionsList, + eventsList: eventsList, + endTime: endTime, }; showModal( - <BugReportModal - width={props.width} - height={props.height} - xrayProps={xrayProps} - hideModal={hideModal} - />, - { right: true } + <BugReportModal width={width} height={height} xrayProps={xrayProps} hideModal={hideModal} />, + { right: true, width: 620 } ); }; + const showWarning = + location && /(localhost)|(127.0.0.1)|(0.0.0.0)/.test(location) && showWarningModal; + const closeWarning = () => { + localStorage.setItem(localhostWarnKey, '1') + setWarning(false) + } return ( - <div className="w-full px-4 py-2 flex items-center border-b"> - {location && ( + <div className="w-full px-4 py-2 flex items-center border-b relative"> + {showWarning ? ( <div - className="flex items-center cursor-pointer color-gray-medium text-sm p-1 hover:bg-gray-light-shade rounded-md" - onClick={() => { - copy(props.currentLocation); - setCopied(true); - setTimeout(() => setCopied(false), 5000); + className="px-3 py-1 border border-gray-light drop-shadow-md rounded bg-active-blue flex items-center justify-between" + style={{ + zIndex: 999, + position: 'absolute', + left: '50%', + bottom: '-24px', + transform: 'translate(-50%, 0)', + fontWeight: 500, }} > - <Icon size="20" name="event/link" className="mr-1" /> - <Tooltip title={isCopied ? 'URL Copied to clipboard' : 'Click to copy'}> - {location} - </Tooltip> - </div> - )} - {!isAssist ? ( - <div - className="ml-auto text-sm flex items-center color-gray-medium gap-2" - style={{ width: 'max-content' }} - > - <Button icon="file-pdf" variant="text" onClick={showReportModal}> - Create Bug Report - </Button> - <NotePopup /> - <Issues sessionId={props.sessionId} /> - <SharePopup - entity="sessions" - id={props.sessionId} - showCopyLink={true} - trigger={ - <div className="relative"> - <Button icon="share-alt" variant="text" className="relative"> - Share - </Button> - </div> - } - /> - <ItemMenu - items={[ - { - key: 1, - component: <AutoplayToggle />, - }, - { - key: 2, - component: <Bookmark noMargin sessionId={props.sessionId} />, - }, - ]} - /> - - <div> - <Autoplay /> + Some assets may load incorrectly on localhost. + <a + href="https://docs.openreplay.com/en/troubleshooting/session-recordings/#testing-in-localhost" + target="_blank" + rel="noreferrer" + className="link ml-1" + > + Learn More + </a> + <div className="py-1 ml-3 cursor-pointer" onClick={closeWarning}> + <Icon name="close" size={16} color="black" /> </div> </div> ) : null} + {location && ( + <> + <div + className="flex items-center cursor-pointer color-gray-medium text-sm p-1 hover:bg-gray-light-shade rounded-md" + onClick={() => { + copy(currentLocation); + setCopied(true); + setTimeout(() => setCopied(false), 5000); + }} + > + <Icon size="20" name="event/link" className="mr-1" /> + <Tooltip title={isCopied ? 'URL Copied to clipboard' : 'Click to copy'}> + {location} + </Tooltip> + </div> + </> + )} + <div + className="ml-auto text-sm flex items-center color-gray-medium gap-2" + style={{ width: 'max-content' }} + > + <Button icon="file-pdf" variant="text" onClick={showReportModal}> + Create Bug Report + </Button> + <NotePopup /> + <Issues sessionId={props.sessionId} /> + <SharePopup + entity="sessions" + id={props.sessionId} + showCopyLink={true} + trigger={ + <div className="relative"> + <Button icon="share-alt" variant="text" className="relative"> + Share + </Button> + </div> + } + /> + <ItemMenu + items={[ + { + key: 1, + component: <AutoplayToggle />, + }, + { + key: 2, + component: <Bookmark noMargin sessionId={props.sessionId} />, + }, + ]} + /> + + <div> + <QueueControls /> + </div> + </div> </div> ); } -const SubH = connectPlayer((state) => ({ - width: state.width, - height: state.height, - currentLocation: state.location, - resourceList: state.resourceList - .filter((r) => r.isRed() || r.isYellow()) - .concat(state.fetchList.filter((i) => parseInt(i.status) >= 400)) - .concat(state.graphqlList.filter((i) => parseInt(i.status) >= 400)), - exceptionsList: state.exceptionsList, - eventsList: state.eventList, - endTime: state.endTime, -}))(SubHeader); - -export default React.memo(SubH); +export default connect((state) => ({ siteId: state.getIn(['site', 'siteId']) }))(observer(SubHeader)); diff --git a/frontend/app/components/Session_/TimeTable/TimeTable.tsx b/frontend/app/components/Session_/TimeTable/TimeTable.tsx index c6e3ea716..f4c619d56 100644 --- a/frontend/app/components/Session_/TimeTable/TimeTable.tsx +++ b/frontend/app/components/Session_/TimeTable/TimeTable.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { List, AutoSizer } from 'react-virtualized'; import cn from 'classnames'; import { Duration } from "luxon"; -import { NoContent, IconButton, Button } from 'UI'; +import { NoContent, Button } from 'UI'; import { percentOf } from 'App/utils'; import BarRow from './BarRow'; @@ -20,7 +20,7 @@ type Durationed = { type CanBeRed = { //+isRed: boolean, - isRed: () => boolean; + isRed: boolean; }; interface Row extends Timed, Durationed, CanBeRed { @@ -171,7 +171,7 @@ export default class TimeTable extends React.PureComponent<Props, State> { key={key} className={cn('border-b border-color-gray-light-shade', stl.row, { [stl.hoverable]: hoverable, - 'error color-red': !!row.isRed && row.isRed(), + 'error color-red': !!row.isRed && row.isRed, 'cursor-pointer': typeof onRowClick === 'function', [stl.activeRow]: activeIndex === index, // [stl.inactiveRow]: !activeIndex || index > activeIndex, @@ -179,9 +179,9 @@ export default class TimeTable extends React.PureComponent<Props, State> { onClick={typeof onRowClick === 'function' ? () => onRowClick(row, index) : undefined} id="table-row" > - {columns.map(({ dataKey, render, width }) => ( - <div className={stl.cell} style={{ width: `${width}px` }}> - {render ? render(row) : row[dataKey || ''] || <i className="color-gray-light">{'empty'}</i>} + {columns.map((column, key) => ( + <div key={column.label.replace(' ', '')} className={stl.cell} style={{ width: `${column.width}px` }}> + {column.render ? column.render(row) : row[column.dataKey || ''] || <i className="color-gray-light">{'empty'}</i>} </div> ))} <div className={cn('relative flex-1 flex', stl.timeBarWrapper)}> @@ -194,7 +194,7 @@ export default class TimeTable extends React.PureComponent<Props, State> { onPrevClick = () => { let prevRedIndex = -1; for (let i = this.state.firstVisibleRowIndex - 1; i >= 0; i--) { - if (this.props.rows[i].isRed()) { + if (this.props.rows[i].isRed) { prevRedIndex = i; break; } @@ -207,7 +207,7 @@ export default class TimeTable extends React.PureComponent<Props, State> { onNextClick = () => { let prevRedIndex = -1; for (let i = this.state.firstVisibleRowIndex + 1; i < this.props.rows.length; i++) { - if (this.props.rows[i].isRed()) { + if (this.props.rows[i].isRed) { prevRedIndex = i; break; } @@ -262,7 +262,7 @@ export default class TimeTable extends React.PureComponent<Props, State> { <div className={stl.headers}> <div className={stl.infoHeaders}> {columns.map(({ label, width }) => ( - <div className={stl.headerCell} style={{ width: `${width}px` }}> + <div key={label.replace(' ', '')} className={stl.headerCell} style={{ width: `${width}px` }}> {label} </div> ))} @@ -282,14 +282,15 @@ export default class TimeTable extends React.PureComponent<Props, State> { {timeColumns.map((_, index) => ( <div key={`tc-${index}`} className={stl.timeCell} /> ))} - {visibleRefLines.map(({ time, color, onClick }) => ( + {visibleRefLines.map((line, key) => ( <div - className={cn(stl.refLine, `bg-${color}`)} + key={line.time+key} + className={cn(stl.refLine, `bg-${line.color}`)} style={{ - left: `${percentOf(time - timestart, timewidth)}%`, - cursor: typeof onClick === 'function' ? 'click' : 'auto', + left: `${percentOf(line.time - timestart, timewidth)}%`, + cursor: typeof line.onClick === 'function' ? 'click' : 'auto', }} - onClick={onClick} + onClick={line.onClick} /> ))} </div> diff --git a/frontend/app/components/Session_/TimeTable/timeTable.module.css b/frontend/app/components/Session_/TimeTable/timeTable.module.css index c2412ff8d..175958f21 100644 --- a/frontend/app/components/Session_/TimeTable/timeTable.module.css +++ b/frontend/app/components/Session_/TimeTable/timeTable.module.css @@ -108,5 +108,5 @@ $offset: 10px; } .inactiveRow { - opacity: 0.5; + opacity: 0.4; } \ No newline at end of file diff --git a/frontend/app/components/Session_/components/HeaderMenu.tsx b/frontend/app/components/Session_/components/HeaderMenu.tsx index 003ca5ab3..9d70d8aab 100644 --- a/frontend/app/components/Session_/components/HeaderMenu.tsx +++ b/frontend/app/components/Session_/components/HeaderMenu.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Icon, Button } from 'UI'; +import { Button } from 'UI'; import styles from './menu.module.css'; import cn from 'classnames'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; diff --git a/frontend/app/components/Session_/components/NotePopup.tsx b/frontend/app/components/Session_/components/NotePopup.tsx index 83201fc57..ae13d7b3a 100644 --- a/frontend/app/components/Session_/components/NotePopup.tsx +++ b/frontend/app/components/Session_/components/NotePopup.tsx @@ -1,23 +1,23 @@ import React from 'react'; import { Button } from 'UI'; -import { connectPlayer, pause } from 'Player'; import { connect } from 'react-redux'; import { setCreateNoteTooltip } from 'Duck/sessions'; -import GuidePopup, { FEATURE_KEYS } from 'Shared/GuidePopup'; +import GuidePopup from 'Shared/GuidePopup'; +import { PlayerContext } from 'App/components/Session/playerContext'; function NotePopup({ setCreateNoteTooltip, - time, tooltipActive, }: { setCreateNoteTooltip: (args: any) => void; - time: number; tooltipActive: boolean; }) { + const { player, store } = React.useContext(PlayerContext) + const toggleNotePopup = () => { if (tooltipActive) return; - pause(); - setCreateNoteTooltip({ time: time, isVisible: true }); + player.pause(); + setCreateNoteTooltip({ time: store.get().time, isVisible: true }); }; React.useEffect(() => { @@ -36,14 +36,9 @@ function NotePopup({ ); } -const NotePopupPl = connectPlayer( - // @ts-ignore - (state) => ({ time: state.time }) -)(React.memo(NotePopup)); - const NotePopupComp = connect( (state: any) => ({ tooltipActive: state.getIn(['sessions', 'createNoteTooltip', 'isVisible']) }), { setCreateNoteTooltip } -)(NotePopupPl); +)(NotePopup); export default React.memo(NotePopupComp); diff --git a/frontend/app/components/Session_/session.stories.js b/frontend/app/components/Session_/session.stories.js deleted file mode 100644 index 3118f24f6..000000000 --- a/frontend/app/components/Session_/session.stories.js +++ /dev/null @@ -1,298 +0,0 @@ -import { storiesOf } from '@storybook/react'; -import { List } from 'immutable'; -import EventGroup from './EventsBlock/Event'; - -const groups = [ - { - "page": { - "key": "Location_257", - "time": 2751, - "type": "LOCATION", - "url": "/login", - "pageLoad": false, - "fcpTime": 6787, - "loadTime": 7872, - "domTime": 5821, - "referrer": "Search Engine" - }, - "events": [ - { - "sessionId": 2406625057772570, - "messageId": 76446, - "timestamp": 1586722257371, - "label": "Device Memory: 8.19GB", - "type": "CLICKRAGE", - "count": 3 - }, - { - "key": "Click_256", - "time": 13398, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_262", - "path": "", - "label": null - } - }, - { - "key": "Input_256", - "time": 13438, - "type": "INPUT", - "target": { - "key": "record_263", - "path": "", - "label": null - }, - "value": null - } - ] - }, - { - "page": { - "key": "Location_258", - "time": 15841, - "type": "LOCATION", - "url": "/1/sessions", - "pageLoad": false, - "fcpTime": null, - "loadTime": null, - "domTime": null, - "referrer": "" - }, - "events": [ - { - "key": "Click_257", - "time": 24408, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_264", - "path": "", - "label": null - } - } - ] - }, - { - "page": { - "key": "Location_259", - "time": 25019, - "type": "LOCATION", - "url": "/1/session/2303531983744788", - "pageLoad": false, - "fcpTime": null, - "loadTime": null, - "domTime": null, - "referrer": "" - }, - "events": [ - { - "key": "Click_258", - "time": 31134, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_265", - "path": "", - "label": null - } - }, - { - "key": "Click_259", - "time": 32022, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_266", - "path": "", - "label": null - } - }, - { - "key": "Click_260", - "time": 35951, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_267", - "path": "", - "label": null - } - }, - { - "key": "Click_261", - "time": 164029, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_268", - "path": "", - "label": null - } - }, - { - "key": "Click_262", - "time": 169739, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_269", - "path": "", - "label": null - } - }, - { - "key": "Click_263", - "time": 170524, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_270", - "path": "", - "label": null - } - }, - { - "key": "Click_264", - "time": 172580, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_271", - "path": "", - "label": null - } - }, - { - "key": "Click_265", - "time": 173102, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_272", - "path": "", - "label": null - } - }, - { - "key": "Click_266", - "time": 173698, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_273", - "path": "", - "label": null - } - }, - { - "key": "Click_267", - "time": 173867, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_274", - "path": "", - "label": null - } - }, - { - "key": "Click_268", - "time": 174599, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_275", - "path": "", - "label": null - } - }, - { - "key": "Click_269", - "time": 175148, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_276", - "path": "", - "label": null - } - }, - { - "key": "Click_270", - "time": 175779, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_277", - "path": "", - "label": null - } - }, - { - "key": "Click_271", - "time": 176658, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_278", - "path": "", - "label": null - } - }, - { - "key": "Click_272", - "time": 177267, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_279", - "path": "", - "label": null - } - }, - { - "key": "Click_273", - "time": 187025, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_280", - "path": "", - "label": null - } - }, - { - "key": "Click_274", - "time": 189787, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_281", - "path": "", - "label": null - } - }, - { - "key": "Click_275", - "time": 191326, - "type": "CLICK", - "targetContent": "", - "target": { - "key": "record_282", - "path": "", - "label": null - } - } - ] - } -] - -// storiesOf('Player', module) -// .add('Event Group', () => ( -// <EventGroup -// group={groups[0]} -// selectedEvents={[]} -// /> -// )) diff --git a/frontend/app/components/Signup/Signup.js b/frontend/app/components/Signup/Signup.js index 516d993c2..83a658ec1 100644 --- a/frontend/app/components/Signup/Signup.js +++ b/frontend/app/components/Signup/Signup.js @@ -5,47 +5,54 @@ import { Icon } from 'UI'; import stl from './signup.module.css'; import cn from 'classnames'; import SignupForm from './SignupForm'; - +import RegisterBg from '../../svg/register.svg'; const BulletItem = ({ text }) => ( <div className="flex items-center mb-4"> <div className="mr-3 h-8 w-8 rounded-full bg-white shadow flex items-center justify-center"> - <Icon name="check" size="26"/> + <Icon name="check" size="26" /> </div> <div>{text}</div> </div> -) +); @withPageTitle('Signup - OpenReplay') export default class Signup extends React.Component { render() { return ( - <div className="flex" style={{ height: '100vh'}}> - <div className={cn("w-6/12", stl.left)}> + <div className="flex" style={{ height: '100vh' }}> + <div className={cn('w-6/12 relative overflow-hidden', stl.left)}> <div className="px-6 pt-10"> - <img src="/assets/logo-white.svg" /> + <img src="/assets/logo-white.svg" /> </div> - <div className="color-white text-lg flex items-center px-20 pt-32"> - <div> - <div className="flex items-center text-3xl font-bold mb-6"> - OpenReplay Cloud <div className="ml-2"><Icon name="signup" size="28" color="white" /></div> - </div> - <div>OpenReplay Cloud is the hosted version of our open-source project.</div> - <div>We’ll manage hosting, scaling and upgrades.</div> - - <div className="mt-8"> - <BulletItem text="First 1K sessions free every month." /> - <BulletItem text="Pay per use, cancel anytime" /> - <BulletItem text="Community, Slack & email support" /> + <img + style={{ width: '800px', position: 'absolute', bottom: -100, left: 0 }} + src={RegisterBg} + /> + <div className="color-white text-lg flex items-center px-20 pt-32"> + <div> + <div className="flex items-center text-3xl font-bold mb-6"> + OpenReplay Cloud{' '} + <div className="ml-2"> + <Icon name="signup" size="28" color="white" /> + </div> + </div> + <div>OpenReplay Cloud is the hosted version of our open-source project.</div> + <div>We’ll manage hosting, scaling and upgrades.</div> + + <div className="mt-8"> + <BulletItem text="First 1K sessions free every month." /> + <BulletItem text="Pay per use, cancel anytime" /> + <BulletItem text="Community, Slack & email support" /> + </div> </div> </div> - </div> - </div> - <div className="w-6/12 flex items-center justify-center"> - <div className=""> - <SignupForm /> </div> - </div> - </div> + <div className="w-6/12 flex items-center justify-center"> + <div className=""> + <SignupForm /> + </div> + </div> + </div> ); } } diff --git a/frontend/app/components/Signup/SignupForm/SignupForm.js b/frontend/app/components/Signup/SignupForm/SignupForm.js index 90580973d..f110d2dae 100644 --- a/frontend/app/components/Signup/SignupForm/SignupForm.js +++ b/frontend/app/components/Signup/SignupForm/SignupForm.js @@ -1,5 +1,5 @@ import React from 'react' -import { Form, Input, Icon, Button, Link, CircularLoader } from 'UI' +import { Form, Input, Icon, Button, Link } from 'UI' import { login } from 'App/routes' import ReCAPTCHA from 'react-google-recaptcha' import stl from './signup.module.css' diff --git a/frontend/app/components/hocs/withReport.tsx b/frontend/app/components/hocs/withReport.tsx index aebd72998..bbe34bc33 100644 --- a/frontend/app/components/hocs/withReport.tsx +++ b/frontend/app/components/hocs/withReport.tsx @@ -2,7 +2,7 @@ import React, { useEffect } from 'react'; import { convertElementToImage } from 'App/utils'; import { jsPDF } from 'jspdf'; import { useStore } from 'App/mstore'; -import { observer, useObserver } from 'mobx-react-lite'; +import { useObserver } from 'mobx-react-lite'; import { connect } from 'react-redux'; import { fileNameFormat } from 'App/utils'; import { toast } from 'react-toastify'; diff --git a/frontend/app/components/hocs/withSiteIdUpdater.js b/frontend/app/components/hocs/withSiteIdUpdater.js index 1c4e038ae..0849bbd84 100644 --- a/frontend/app/components/hocs/withSiteIdUpdater.js +++ b/frontend/app/components/hocs/withSiteIdUpdater.js @@ -19,7 +19,7 @@ export default (BaseComponent) => { } componentDidUpdate(prevProps) { const { urlSiteId, siteId, location: { pathname }, history } = this.props; - const shouldUrlUpdate = urlSiteId && urlSiteId !== siteId; + const shouldUrlUpdate = urlSiteId && parseInt(urlSiteId, 10) !== parseInt(siteId, 10); if (shouldUrlUpdate) { const path = ['', siteId].concat(pathname.split('/').slice(2)).join('/'); history.push(path); @@ -37,4 +37,4 @@ export default (BaseComponent) => { } return WrapperClass -} \ No newline at end of file +} diff --git a/frontend/app/components/shared/AlertTriggersModal/AlertTriggersModal.tsx b/frontend/app/components/shared/AlertTriggersModal/AlertTriggersModal.tsx index b76666962..4f6ee030d 100644 --- a/frontend/app/components/shared/AlertTriggersModal/AlertTriggersModal.tsx +++ b/frontend/app/components/shared/AlertTriggersModal/AlertTriggersModal.tsx @@ -31,7 +31,7 @@ function AlertTriggersModal(props: Props) { }, []) return useObserver(() => ( - <div className="bg-white box-shadow h-screen overflow-y-auto" style={{ width: '450px'}}> + <div className="bg-white box-shadow h-screen overflow-y-auto"> <div className="flex items-center justify-between p-5 text-2xl"> <div>Alerts</div> { count > 0 && ( diff --git a/frontend/app/components/shared/AnimatedSVG/AnimatedSVG.tsx b/frontend/app/components/shared/AnimatedSVG/AnimatedSVG.tsx index 0ac1d1d5b..fca6dc288 100644 --- a/frontend/app/components/shared/AnimatedSVG/AnimatedSVG.tsx +++ b/frontend/app/components/shared/AnimatedSVG/AnimatedSVG.tsx @@ -16,6 +16,11 @@ import NoIssues from '../../../svg/ca-no-issues.svg'; import NoAuditTrail from '../../../svg/ca-no-audit-trail.svg'; import NoAnnouncements from '../../../svg/ca-no-announcements.svg'; import NoAlerts from '../../../svg/ca-no-alerts.svg'; +import NoNotes from '../../../svg/ca-no-notes.svg'; +import NoCards from '../../../svg/ca-no-cards.svg'; +import NoSearchResults from '../../../svg/ca-no-search-results.svg'; +import NoDashboards from '../../../svg/ca-no-dashboards.svg'; +import NoRecordings from '../../../svg/ca-no-recordings.svg'; export enum ICONS { DASHBOARD_ICON = 'dashboard-icn', @@ -35,6 +40,11 @@ export enum ICONS { NO_AUDIT_TRAIL = 'ca-no-audit-trail', NO_ANNOUNCEMENTS = 'ca-no-announcements', NO_ALERTS = 'ca-no-alerts', + NO_NOTES = 'ca-no-notes', + NO_CARDS = 'ca-no-cards', + NO_RECORDINGS = 'ca-no-recordings', + NO_SEARCH_RESULTS = 'ca-no-search-results', + NO_DASHBOARDS = 'ca-no-dashboards', } interface Props { @@ -79,6 +89,16 @@ function AnimatedSVG(props: Props) { return <img style={{ width: size + 'px' }} src={NoAnnouncements} />; case ICONS.NO_ALERTS: return <img style={{ width: size + 'px' }} src={NoAlerts} />; + case ICONS.NO_NOTES: + return <img style={{ width: size + 'px' }} src={NoNotes} />; + case ICONS.NO_CARDS: + return <img style={{ width: size + 'px' }} src={NoCards} />; + case ICONS.NO_SEARCH_RESULTS: + return <img style={{ width: size + 'px' }} src={NoSearchResults} />; + case ICONS.NO_DASHBOARDS: + return <img style={{ width: size + 'px' }} src={NoDashboards} />; + case ICONS.NO_RECORDINGS: + return <img style={{ width: size + 'px' }} src={NoRecordings} />; default: return null; } diff --git a/frontend/app/components/shared/AnnouncementModal/AnnouncementModal.tsx b/frontend/app/components/shared/AnnouncementModal/AnnouncementModal.tsx deleted file mode 100644 index 5f69de976..000000000 --- a/frontend/app/components/shared/AnnouncementModal/AnnouncementModal.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import { Button, NoContent } from 'UI'; -import { connect } from 'react-redux'; -import { fetchList, setLastRead } from 'Duck/announcements'; -import cn from 'classnames'; -import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; -import ListItem from './ListItem' - -interface Props { - unReadNotificationsCount: number; - setLastRead: Function; - list: any; -} -function AnnouncementModal(props: Props) { - const { list, unReadNotificationsCount } = props; - - // const onClear = (notification: any) => { - // console.log('onClear', notification); - // props.setViewed(notification.notificationId) - // } - - return ( - <div className="bg-white box-shadow h-screen overflow-y-auto" style={{ width: '450px'}}> - <div className="flex items-center justify-between p-5 text-2xl"> - <div>Announcements</div> - </div> - - <div className="pb-5"> - <NoContent - title={ - <div className="flex items-center justify-between"> - <AnimatedSVG name={ICONS.EMPTY_STATE} size="100" /> - </div> - } - subtext="There are no alerts to show." - // show={ !loading && unReadNotificationsCount === 0 } - size="small" - > - {list.map((item: any, i: any) => ( - <div className="border-b" key={i}> - {/* <ListItem alert={item} onClear={() => onClear(item)} loading={false} /> */} - </div> - ))} - </NoContent> - </div> - </div> - ); -} - -export default connect((state: any) => ({ - list: state.getIn(['announcements', 'list']), -}), { - fetchList, - setLastRead, -})(AnnouncementModal); \ No newline at end of file diff --git a/frontend/app/components/shared/AnnouncementModal/ListItem/ListItem.tsx b/frontend/app/components/shared/AnnouncementModal/ListItem/ListItem.tsx deleted file mode 100644 index dd777c719..000000000 --- a/frontend/app/components/shared/AnnouncementModal/ListItem/ListItem.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { Button, Label } from 'UI'; -import stl from './listItem.module.css'; - -const ListItem = ({ announcement, onButtonClick }) => { - return ( - <div className={stl.wrapper}> - <div className="flex justify-between items-center mb-3"> - <div className="text-sm">{announcement.createdAt && announcement.createdAt.toFormat('LLL dd, yyyy')}</div> - <Label><span className="capitalize">{announcement.type}</span></Label> - </div> - {announcement.imageUrl && - <img className="w-full border mb-3" src={announcement.imageUrl} /> - } - <div> - <h2 className="text-xl mb-2">{announcement.title}</h2> - <div className="mb-2 text-sm text-justify">{announcement.description}</div> - {announcement.buttonUrl && - <Button - variant="outline" - onClick={() => onButtonClick(announcement.buttonUrl) } - > - <span className="capitalize">{announcement.buttonText}</span> - </Button> - } - </div> - </div> - ) -} - -export default ListItem diff --git a/frontend/app/components/shared/AnnouncementModal/ListItem/index.ts b/frontend/app/components/shared/AnnouncementModal/ListItem/index.ts deleted file mode 100644 index 741aed270..000000000 --- a/frontend/app/components/shared/AnnouncementModal/ListItem/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ListItem'; diff --git a/frontend/app/components/shared/AnnouncementModal/ListItem/listItem.module.css b/frontend/app/components/shared/AnnouncementModal/ListItem/listItem.module.css deleted file mode 100644 index 5bc3a44c8..000000000 --- a/frontend/app/components/shared/AnnouncementModal/ListItem/listItem.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.wrapper { - background-color: white; - margin-bottom: 20px; - padding: 15px; -} \ No newline at end of file diff --git a/frontend/app/components/shared/AnnouncementModal/index.ts b/frontend/app/components/shared/AnnouncementModal/index.ts deleted file mode 100644 index b9af0fc52..000000000 --- a/frontend/app/components/shared/AnnouncementModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './AnnouncementModal'; \ No newline at end of file diff --git a/frontend/app/components/shared/AutoplayToggle/AutoplayToggle.tsx b/frontend/app/components/shared/AutoplayToggle/AutoplayToggle.tsx index 3f25fd525..55bef3709 100644 --- a/frontend/app/components/shared/AutoplayToggle/AutoplayToggle.tsx +++ b/frontend/app/components/shared/AutoplayToggle/AutoplayToggle.tsx @@ -1,16 +1,19 @@ import React from 'react'; -import { Controls as PlayerControls, connectPlayer } from 'Player'; import { Toggler } from 'UI'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; interface Props { toggleAutoplay: () => void; autoplay: boolean; } function AutoplayToggle(props: Props) { - const { autoplay } = props; + const { player, store } = React.useContext(PlayerContext) + + const { autoplay } = store.get() return ( <div - onClick={props.toggleAutoplay} + onClick={() => player.toggleAutoplay()} className="cursor-pointer flex items-center mr-2 hover:bg-gray-light-shade rounded-md p-2" > <Toggler name="sessionsLive" onChange={props.toggleAutoplay} checked={autoplay} /> @@ -19,11 +22,4 @@ function AutoplayToggle(props: Props) { ); } -export default connectPlayer( - (state: any) => ({ - autoplay: state.autoplay, - }), - { - toggleAutoplay: PlayerControls.toggleAutoplay, - } -)(AutoplayToggle); +export default observer(AutoplayToggle); diff --git a/frontend/app/components/shared/Bookmark/Bookmark.tsx b/frontend/app/components/shared/Bookmark/Bookmark.tsx index 04c88e589..d011af5f7 100644 --- a/frontend/app/components/shared/Bookmark/Bookmark.tsx +++ b/frontend/app/components/shared/Bookmark/Bookmark.tsx @@ -65,7 +65,7 @@ function Bookmark(props: Props) { export default connect( (state: any) => ({ isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee', - favorite: state.getIn(['sessions', 'current', 'favorite']), + favorite: state.getIn(['sessions', 'current']).favorite, }), { toggleFavorite } )(Bookmark); diff --git a/frontend/app/components/shared/CustomDropdownOption/CustomDropdownOption.tsx b/frontend/app/components/shared/CustomDropdownOption/CustomDropdownOption.tsx new file mode 100644 index 000000000..adf0ce221 --- /dev/null +++ b/frontend/app/components/shared/CustomDropdownOption/CustomDropdownOption.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { components, OptionProps } from 'react-select'; +import { Icon, Tooltip } from 'UI'; +import cn from 'classnames'; +import { ENTERPRISE_REQUEIRED } from 'App/constants'; + +export interface Props extends OptionProps { + icon?: string; + label: string; + description: string; + disabled?: boolean; +} +function CustomDropdownOption(props: Props) { + const { icon = '', label, description, isSelected, isFocused, disabled } = props; + return ( + <components.Option {...props} className="!p-0 mb-2"> + <Tooltip disabled={!disabled} title={ENTERPRISE_REQUEIRED} delay={0}> + <div + className={cn( + 'cursor-pointer group p-2 flex item-start border border-transparent rounded hover:!bg-active-blue !leading-0', + { 'opacity-30': disabled } + )} + > + {icon && ( + <Icon + // @ts-ignore + name={icon} + className="pt-2 mr-3" + size={18} + color={isSelected || isFocused ? 'teal' : 'gray-dark'} + /> + )} + <div className={cn('flex flex-col', { '!color-teal': isFocused || isSelected })}> + <div className="font-medium leading-0">{label}</div> + <div className="text-sm color-gray-dark">{description}</div> + </div> + </div> + </Tooltip> + </components.Option> + ); +} + +export default CustomDropdownOption; diff --git a/frontend/app/components/shared/CustomDropdownOption/index.ts b/frontend/app/components/shared/CustomDropdownOption/index.ts new file mode 100644 index 000000000..240e1246d --- /dev/null +++ b/frontend/app/components/shared/CustomDropdownOption/index.ts @@ -0,0 +1 @@ +export { default } from './CustomDropdownOption'; diff --git a/frontend/app/components/shared/CustomMetrics/CustomMetrics.tsx b/frontend/app/components/shared/CustomMetrics/CustomMetrics.tsx index 77a49a7e1..aedd4a097 100644 --- a/frontend/app/components/shared/CustomMetrics/CustomMetrics.tsx +++ b/frontend/app/components/shared/CustomMetrics/CustomMetrics.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { IconButton } from 'UI'; import { connect } from 'react-redux'; import { edit, init } from 'Duck/customMetrics'; diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx b/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx deleted file mode 100644 index 8f8aca480..000000000 --- a/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React, { useState } from 'react'; -import FilterList from 'Shared/Filters/FilterList'; -import { - edit, - updateSeries, - addSeriesFilterFilter, - removeSeriesFilterFilter, - editSeriesFilterFilter, - editSeriesFilter, -} from 'Duck/customMetrics'; -import { connect } from 'react-redux'; -import { IconButton, Icon } from 'UI'; -import FilterSelection from '../../Filters/FilterSelection'; -import SeriesName from './SeriesName'; -import cn from 'classnames'; - -interface Props { - seriesIndex: number; - series: any; - edit: typeof edit; - updateSeries: typeof updateSeries; - onRemoveSeries: (seriesIndex) => void; - canDelete?: boolean; - addSeriesFilterFilter: typeof addSeriesFilterFilter; - editSeriesFilterFilter: typeof editSeriesFilterFilter; - editSeriesFilter: typeof editSeriesFilter; - removeSeriesFilterFilter: typeof removeSeriesFilterFilter; - hideHeader?: boolean; - emptyMessage?: any; -} - -function FilterSeries(props: Props) { - const { canDelete, hideHeader = false, emptyMessage = 'Add user event or filter to define the series by clicking Add Step.' } = props; - const [expanded, setExpanded] = useState(true) - const { series, seriesIndex } = props; - - const onAddFilter = (filter) => { - filter.value = [""] - if (filter.hasOwnProperty('filters') && Array.isArray(filter.filters)) { - filter.filters = filter.filters.map(i => ({ ...i, value: [""] })) - } - props.addSeriesFilterFilter(seriesIndex, filter); - } - - const onUpdateFilter = (filterIndex, filter) => { - props.editSeriesFilterFilter(seriesIndex, filterIndex, filter); - } - - const onChangeEventsOrder = (e, { name, value }) => { - props.editSeriesFilter(seriesIndex, { eventsOrder: value }); - } - - const onRemoveFilter = (filterIndex) => { - props.removeSeriesFilterFilter(seriesIndex, filterIndex); - } - - return ( - <div className="border rounded bg-white"> - <div className={cn("border-b px-5 h-12 flex items-center relative", { 'hidden': hideHeader })}> - <div className="mr-auto"> - <SeriesName seriesIndex={seriesIndex} name={series.name} onUpdate={(name) => props.updateSeries(seriesIndex, { name }) } /> - </div> - - <div className="flex items-center cursor-pointer" > - <div onClick={props.onRemoveSeries} className={cn("ml-3", {'disabled': !canDelete})}> - <Icon name="trash" size="16" /> - </div> - - <div onClick={() => setExpanded(!expanded)} className="ml-3"> - <Icon name="chevron-down" size="16" /> - </div> - - </div> - </div> - { expanded && ( - <> - <div className="p-5"> - { series.filter.filters.size > 0 ? ( - <FilterList - filter={series.filter} - onUpdateFilter={onUpdateFilter} - onRemoveFilter={onRemoveFilter} - onChangeEventsOrder={onChangeEventsOrder} - /> - ): ( - <div className="color-gray-medium">{emptyMessage}</div> - )} - </div> - <div className="border-t h-12 flex items-center"> - <div className="-mx-4 px-6"> - <FilterSelection - filter={undefined} - onFilterClick={onAddFilter} - > - <IconButton primaryText label="ADD STEP" icon="plus" /> - </FilterSelection> - </div> - </div> - </> - )} - </div> - ); -} - -export default connect(null, { - edit, - updateSeries, - addSeriesFilterFilter, - editSeriesFilterFilter, - editSeriesFilter, - removeSeriesFilterFilter, -})(FilterSeries); \ No newline at end of file diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/SeriesName.tsx b/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/SeriesName.tsx deleted file mode 100644 index d6a69c73d..000000000 --- a/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/SeriesName.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { Icon } from 'UI'; - -interface Props { - name: string; - onUpdate: (name) => void; - seriesIndex?: number; -} -function SeriesName(props: Props) { - const { seriesIndex = 1 } = props; - const [editing, setEditing] = useState(false) - const [name, setName] = useState(props.name) - const ref = useRef<any>(null) - - const write = ({ target: { value, name } }) => { - setName(value) - } - - const onBlur = () => { - setEditing(false) - props.onUpdate(name) - } - - useEffect(() => { - if (editing) { - ref.current.focus() - } - }, [editing]) - - useEffect(() => { - setName(props.name) - }, [props.name]) - - // const { name } = props; - return ( - <div className="flex items-center"> - { editing ? ( - <input - ref={ ref } - name="name" - className="fluid border-0 -mx-2 px-2 h-8" - value={name} - // readOnly={!editing} - onChange={write} - onBlur={onBlur} - onFocus={() => setEditing(true)} - /> - ) : ( - <div className="text-base h-8 flex items-center border-transparent">{name && name.trim() === '' ? 'Series ' + (seriesIndex + 1) : name }</div> - )} - - <div className="ml-3 cursor-pointer" onClick={() => setEditing(true)}><Icon name="pencil" size="14" /></div> - </div> - ); -} - -export default SeriesName; \ No newline at end of file diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/index.ts b/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/index.ts deleted file mode 100644 index 90e63cdb6..000000000 --- a/frontend/app/components/shared/CustomMetrics/FilterSeries/SeriesName/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SeriesName'; \ No newline at end of file diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/index.ts b/frontend/app/components/shared/CustomMetrics/FilterSeries/index.ts deleted file mode 100644 index 5882e382a..000000000 --- a/frontend/app/components/shared/CustomMetrics/FilterSeries/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './FilterSeries' \ No newline at end of file diff --git a/frontend/app/components/shared/DateRangeDropdown/DateRangePopup.js b/frontend/app/components/shared/DateRangeDropdown/DateRangePopup.js index 8c353419f..88f4a8292 100644 --- a/frontend/app/components/shared/DateRangeDropdown/DateRangePopup.js +++ b/frontend/app/components/shared/DateRangeDropdown/DateRangePopup.js @@ -1,6 +1,5 @@ import React from 'react'; -import DateRangePicker from 'react-daterange-picker'; // TODO replace with other date range pickers -// import { DateRangePicker } from 'react-date-range'; +import DateRangePicker from 'react-daterange-picker' import TimePicker from 'rc-time-picker'; import { Button } from 'UI'; import { getDateRangeFromValue, getDateRangeLabel, dateRangeValues, CUSTOM_RANGE, moment, DATE_RANGE_VALUES } from 'App/dateRange'; @@ -81,12 +80,6 @@ export default class DateRangePopup extends React.PureComponent { )) } </div> - {/* <DateRangePicker - ranges={[selectionRange]} - onChange={(e) => console.log(e)} - showMonthAndYearPickers={false} - scroll={{ enabled: true }} - /> */} <DateRangePicker name="dateRangePicker" onSelect={ this.selectCustomRange } diff --git a/frontend/app/components/shared/DateRangeDropdown/index.js b/frontend/app/components/shared/DateRangeDropdown/index.js index d6e6f2f87..d5948cff3 100644 --- a/frontend/app/components/shared/DateRangeDropdown/index.js +++ b/frontend/app/components/shared/DateRangeDropdown/index.js @@ -1 +1 @@ -export { default } from './DateRangeDropdown'; +// export { default } from './DateRangeDropdown'; diff --git a/frontend/app/components/shared/DevTools/BottomBlock/Header.js b/frontend/app/components/shared/DevTools/BottomBlock/Header.js index 15dd7a0c9..f743ab3a5 100644 --- a/frontend/app/components/shared/DevTools/BottomBlock/Header.js +++ b/frontend/app/components/shared/DevTools/BottomBlock/Header.js @@ -2,7 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import cn from 'classnames'; import { closeBottomBlock } from 'Duck/components/player'; -import { Input, CloseButton } from 'UI'; +import { CloseButton } from 'UI'; import stl from './header.module.css'; const Header = ({ diff --git a/frontend/app/components/shared/DevTools/BottomBlock/tabs.js b/frontend/app/components/shared/DevTools/BottomBlock/tabs.js deleted file mode 100644 index 6addd161e..000000000 --- a/frontend/app/components/shared/DevTools/BottomBlock/tabs.js +++ /dev/null @@ -1,9 +0,0 @@ -// import { NONE, CONSOLE, NETWORK, STACKEVENTS, REDUX_STATE, PROFILER, PERFORMANCE, GRAPHQL } from 'Duck/components/player'; -// -// -// export default { -// [NONE]: { -// Component: null, -// -// } -// } \ No newline at end of file diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx index c9f040c50..21eb6c021 100644 --- a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx +++ b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx @@ -1,17 +1,18 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { connectPlayer, jump } from 'Player'; -import Log from 'Types/session/log'; +import React, { useEffect, useRef, useState, useMemo } from 'react'; +import { LogLevel, ILog } from 'Player'; import BottomBlock from '../BottomBlock'; -import { LEVEL } from 'Types/session/log'; import { Tabs, Input, Icon, NoContent } from 'UI'; import cn from 'classnames'; import ConsoleRow from '../ConsoleRow'; -import { getRE } from 'App/utils'; -import { List, CellMeasurer, CellMeasurerCache, AutoSizer } from 'react-virtualized'; -import { useObserver } from 'mobx-react-lite'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { List, CellMeasurer, AutoSizer } from 'react-virtualized'; import { useStore } from 'App/mstore'; import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorDetailsModal'; import { useModal } from 'App/components/Modal'; +import useAutoscroll, { getLastItemTime } from '../useAutoscroll'; +import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter' +import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache' const ALL = 'ALL'; const INFO = 'INFO'; @@ -19,35 +20,34 @@ const WARNINGS = 'WARNINGS'; const ERRORS = 'ERRORS'; const LEVEL_TAB = { - [LEVEL.INFO]: INFO, - [LEVEL.LOG]: INFO, - [LEVEL.WARNING]: WARNINGS, - [LEVEL.ERROR]: ERRORS, - [LEVEL.EXCEPTION]: ERRORS, -}; + [LogLevel.INFO]: INFO, + [LogLevel.LOG]: INFO, + [LogLevel.WARN]: WARNINGS, + [LogLevel.ERROR]: ERRORS, + [LogLevel.EXCEPTION]: ERRORS, +} as const const TABS = [ALL, ERRORS, WARNINGS, INFO].map((tab) => ({ text: tab, key: tab })); function renderWithNL(s = '') { if (typeof s !== 'string') return ''; - return s.split('\n').map((line, i) => <div className={cn({ 'ml-20': i !== 0 })}>{line}</div>); + return s.split('\n').map((line, i) => <div key={i + line.slice(0, 6)} className={cn({ 'ml-20': i !== 0 })}>{line}</div>); } const getIconProps = (level: any) => { switch (level) { - case LEVEL.INFO: - case LEVEL.LOG: + case LogLevel.INFO: + case LogLevel.LOG: return { name: 'console/info', color: 'blue2', }; - case LEVEL.WARN: - case LEVEL.WARNING: + case LogLevel.WARN: return { name: 'console/warning', color: 'red2', }; - case LEVEL.ERROR: + case LogLevel.ERROR: return { name: 'console/error', color: 'red', @@ -56,126 +56,52 @@ const getIconProps = (level: any) => { return null; }; + const INDEX_KEY = 'console'; -let timeOut: any = null; -const TIMEOUT_DURATION = 5000; -interface Props { - logs: any; - exceptions: any; - time: any; -} -function ConsolePanel(props: Props) { - const { logs, time } = props; - const additionalHeight = 0; - // const [activeTab, setActiveTab] = useState(ALL); - // const [filter, setFilter] = useState(''); + +function ConsolePanel() { const { sessionStore: { devTools }, - } = useStore(); - const [filteredList, setFilteredList] = useState([]); - const filter = useObserver(() => devTools[INDEX_KEY].filter); - const activeTab = useObserver(() => devTools[INDEX_KEY].activeTab); - const activeIndex = useObserver(() => devTools[INDEX_KEY].index); - const [pauseSync, setPauseSync] = useState(activeIndex > 0); - const synRef: any = useRef({}); - const { showModal, component: modalActive } = useModal(); + } = useStore() - const onTabClick = (activeTab: any) => devTools.update(INDEX_KEY, { activeTab }); - const onFilterChange = ({ target: { value } }: any) => { - devTools.update(INDEX_KEY, { filter: value }); - }; + const filter = devTools[INDEX_KEY].filter; + const activeTab = devTools[INDEX_KEY].activeTab; + // Why do we need to keep index in the store? if we could get read of it it would simplify the code + const activeIndex = devTools[INDEX_KEY].index; + const [ isDetailsModalActive, setIsDetailsModalActive ] = useState(false); + const { showModal } = useModal(); - synRef.current = { - pauseSync, + const { player, store } = React.useContext(PlayerContext) + const jump = (t: number) => player.jump(t) + + const { logList, exceptionsList, logListNow, exceptionsListNow } = store.get() + const list = useMemo(() => + logList.concat(exceptionsList).sort((a, b) => a.time - b.time), + [ logList.length, exceptionsList.length ], + ) as ILog[] + let filteredList = useRegExListFilterMemo(list, l => l.value, filter) + filteredList = useTabListFilterMemo(filteredList, l => LEVEL_TAB[l.level], ALL, activeTab) + + const onTabClick = (activeTab: any) => devTools.update(INDEX_KEY, { activeTab }) + const onFilterChange = ({ target: { value } }: any) => devTools.update(INDEX_KEY, { filter: value }) + + // AutoScroll + const [ + timeoutStartAutoscroll, + stopAutoscroll, + ] = useAutoscroll( + filteredList, + getLastItemTime(logListNow, exceptionsListNow), activeIndex, - }; - - const removePause = () => { - if (!!modalActive) return; - clearTimeout(timeOut); - timeOut = setTimeout(() => { - devTools.update(INDEX_KEY, { index: getCurrentIndex() }); - setPauseSync(false); - }, TIMEOUT_DURATION); - }; - + index => devTools.update(INDEX_KEY, { index }) + ) + const onMouseEnter = stopAutoscroll const onMouseLeave = () => { - removePause(); - }; - - useEffect(() => { - if (pauseSync) { - removePause(); - } - - return () => { - clearTimeout(timeOut); - if (!synRef.current.pauseSync) { - devTools.update(INDEX_KEY, { index: 0 }); - } - }; - }, []); - - const getCurrentIndex = () => { - return filteredList.filter((item: any) => item.time <= time).length - 1; - }; - - useEffect(() => { - const currentIndex = getCurrentIndex(); - if (currentIndex !== activeIndex && !pauseSync) { - devTools.update(INDEX_KEY, { index: currentIndex }); - } - }, [time]); - - const cache = new CellMeasurerCache({ - fixedWidth: true, - keyMapper: (index: number) => filteredList[index], - }); - const _list = React.useRef(); - - const showDetails = (log: any) => { - clearTimeout(timeOut); - showModal(<ErrorDetailsModal errorId={log.errorId} />, { right: true, onClose: removePause }); - devTools.update(INDEX_KEY, { index: filteredList.indexOf(log) }); - setPauseSync(true); - }; - - const _rowRenderer = ({ index, key, parent, style }: any) => { - const item = filteredList[index]; - - return ( - // @ts-ignore - <CellMeasurer cache={cache} columnIndex={0} key={key} rowIndex={index} parent={parent}> - {({ measure }: any) => ( - <ConsoleRow - style={style} - log={item} - jump={jump} - iconProps={getIconProps(item.level)} - renderWithNL={renderWithNL} - onClick={() => showDetails(item)} - recalcHeight={() => { - measure(); - (_list as any).current.recomputeRowHeights(index); - }} - /> - )} - </CellMeasurer> - ); - }; - - React.useMemo(() => { - const filterRE = getRE(filter, 'i'); - let list = logs; - - list = list.filter( - ({ value, level }: any) => - (!!filter ? filterRE.test(value) : true) && - (activeTab === ALL || activeTab === LEVEL_TAB[level]) - ); - setFilteredList(list); - }, [logs, filter, activeTab]); - + if (isDetailsModalActive) { return } + timeoutStartAutoscroll() + } + + const _list = useRef(null); // TODO: fix react-virtualized types & incapsulate scrollToRow logic useEffect(() => { if (_list.current) { // @ts-ignore @@ -183,10 +109,51 @@ function ConsolePanel(props: Props) { } }, [activeIndex]); + const cache = useCellMeasurerCache(filteredList) + + const showDetails = (log: any) => { + setIsDetailsModalActive(true); + showModal( + <ErrorDetailsModal errorId={log.errorId} />, + { + right: true, + width: 1200, + onClose: () => { + setIsDetailsModalActive(false) + timeoutStartAutoscroll() + } + }); + devTools.update(INDEX_KEY, { index: filteredList.indexOf(log) }); + stopAutoscroll() + } + const _rowRenderer = ({ index, key, parent, style }: any) => { + const item = filteredList[index]; + + return ( + // @ts-ignore + <CellMeasurer cache={cache} columnIndex={0} key={key} rowIndex={index} parent={parent}> + {({ measure }: any) => ( + <ConsoleRow + style={style} + log={item} + jump={jump} + iconProps={getIconProps(item.level)} + renderWithNL={renderWithNL} + onClick={() => showDetails(item)} + recalcHeight={() => { + (_list as any).current.recomputeRowHeights(index); + cache.clear(index, 0) + }} + /> + )} + </CellMeasurer> + ) + } + return ( <BottomBlock - style={{ height: 300 + additionalHeight + 'px' }} - onMouseEnter={() => setPauseSync(true)} + style={{ height: '300px' }} + onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} > {/* @ts-ignore */} @@ -199,7 +166,6 @@ function ConsolePanel(props: Props) { className="input-small h-8" placeholder="Filter by keyword" icon="search" - iconPosition="left" name="filter" height={28} onChange={onFilterChange} @@ -227,6 +193,7 @@ function ConsolePanel(props: Props) { ref={_list} deferredMeasurementCache={cache} overscanRowCount={5} + estimatedRowSize={36} rowCount={Math.ceil(filteredList.length || 1)} rowHeight={cache.rowHeight} rowRenderer={_rowRenderer} @@ -244,19 +211,4 @@ function ConsolePanel(props: Props) { ); } -export default connectPlayer((state: any) => { - const logs = state.logList; - const exceptions = state.exceptionsList; // TODO merge - const logExceptions = exceptions.map(({ time, errorId, name, projectId }: any) => - Log({ - level: LEVEL.ERROR, - value: name, - time, - errorId, - }) - ); - return { - time: state.time, - logs: logs.concat(logExceptions), - }; -})(ConsolePanel); +export default observer(ConsolePanel); diff --git a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx index 83929cbed..fc818b550 100644 --- a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx +++ b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx @@ -15,7 +15,7 @@ interface Props { function ConsoleRow(props: Props) { const { log, iconProps, jump, renderWithNL, style, recalcHeight } = props; const [expanded, setExpanded] = useState(false); - const lines = log.value.split('\n').filter((l: any) => !!l); + const lines = log.value?.split('\n').filter((l: any) => !!l) || []; const canExpand = lines.length > 1; const clickable = canExpand || !!log.errorId; @@ -30,19 +30,19 @@ function ConsoleRow(props: Props) { className={cn( 'border-b flex items-center py-2 px-4 overflow-hidden group relative select-none', { - info: !log.isYellow() && !log.isRed(), - warn: log.isYellow(), - error: log.isRed(), + info: !log.isYellow && !log.isRed, + warn: log.isYellow, + error: log.isRed, 'cursor-pointer': clickable, 'cursor-pointer underline decoration-dotted decoration-gray-200': !!log.errorId, } )} - onClick={clickable ? () => (!!log.errorId ? props.onClick() : toggleExpand()) : () => {}} + onClick={clickable ? () => (!!log.errorId ? props.onClick() : toggleExpand()) : undefined} > <div className="mr-2"> <Icon size="14" {...iconProps} /> </div> - <div key={log.key} data-scroll-item={log.isRed()}> + <div key={log.key} data-scroll-item={log.isRed}> <div className={cn('flex items-center')}> {canExpand && ( <Icon name={expanded ? 'caret-down-fill' : 'caret-right-fill'} className="mr-2" /> diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index 5d9268d51..b7207ddef 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -1,19 +1,21 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { useMemo, useState } from 'react'; +import { observer } from 'mobx-react-lite'; +import { Duration } from 'luxon'; + import { Tooltip, Tabs, Input, NoContent, Icon, Toggler } from 'UI'; -import { getRE } from 'App/utils'; -import Resource, { TYPES } from 'Types/session/resource'; +import { ResourceType } from 'Player'; import { formatBytes } from 'App/utils'; import { formatMs } from 'App/date'; - +import { useModal } from 'App/components/Modal'; +import FetchDetailsModal from 'Shared/FetchDetailsModal'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { useStore } from 'App/mstore'; +import { connect } from 'react-redux' import TimeTable from '../TimeTable'; import BottomBlock from '../BottomBlock'; import InfoLine from '../BottomBlock/InfoLine'; -import { Duration } from 'luxon'; -import { connectPlayer, jump } from 'Player'; -import { useModal } from 'App/components/Modal'; -import FetchDetailsModal from 'Shared/FetchDetailsModal'; -import { useStore } from 'App/mstore'; -import { useObserver } from 'mobx-react-lite'; +import useAutoscroll, { getLastItemTime } from '../useAutoscroll'; +import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter' const INDEX_KEY = 'network'; @@ -25,15 +27,18 @@ const IMG = 'img'; const MEDIA = 'media'; const OTHER = 'other'; -const TAB_TO_TYPE_MAP: any = { - [XHR]: TYPES.XHR, - [JS]: TYPES.JS, - [CSS]: TYPES.CSS, - [IMG]: TYPES.IMG, - [MEDIA]: TYPES.MEDIA, - [OTHER]: TYPES.OTHER, -}; -const TABS: any = [ALL, XHR, JS, CSS, IMG, MEDIA, OTHER].map((tab) => ({ +const TYPE_TO_TAB = { + [ResourceType.XHR]: XHR, + [ResourceType.FETCH]: XHR, + [ResourceType.SCRIPT]: JS, + [ResourceType.CSS]: CSS, + [ResourceType.IMG]: IMG, + [ResourceType.MEDIA]: MEDIA, + [ResourceType.OTHER]: OTHER, +} + +const TAP_KEYS = [ALL, XHR, JS, CSS, IMG, MEDIA, OTHER] as const; +const TABS = TAP_KEYS.map((tab) => ({ text: tab === 'xhr' ? 'Fetch/XHR' : tab, key: tab, })); @@ -80,8 +85,6 @@ function renderSize(r: any) { content = 'Not captured'; } else { const headerSize = r.headerSize || 0; - const encodedSize = r.encodedBodySize || 0; - const transferred = headerSize + encodedSize; const showTransferred = r.headerSize != null; triggerText = formatBytes(r.decodedBodySize); @@ -106,11 +109,11 @@ export function renderDuration(r: any) { if (!r.success) return 'x'; const text = `${Math.floor(r.duration)}ms`; - if (!r.isRed() && !r.isYellow()) return text; + if (!r.isRed && !r.isYellow) return text; let tooltipText; let className = 'w-full h-full flex items-center '; - if (r.isYellow()) { + if (r.isYellow) { tooltipText = 'Slower than average'; className += 'warn color-orange'; } else { @@ -125,121 +128,86 @@ export function renderDuration(r: any) { ); } -let timeOut: any = null; -const TIMEOUT_DURATION = 5000; +function NetworkPanel({ startedAt }: { startedAt: number }) { + const { player, store } = React.useContext(PlayerContext) -interface Props { - location: any; - resources: any; - fetchList: any; - domContentLoadedTime: any; - loadTime: any; - playing: boolean; - domBuildingTime: any; - time: any; -} -function NetworkPanel(props: Props) { - const { resources, time, domContentLoadedTime, loadTime, domBuildingTime, fetchList } = props; - const { showModal, component: modalActive } = useModal(); - const [filteredList, setFilteredList] = useState([]); + const { + domContentLoadedTime, + loadTime, + domBuildingTime, + fetchList, + resourceList, + fetchListNow, + resourceListNow, + } = store.get() + const { showModal } = useModal(); + const [sortBy, setSortBy] = useState('time'); + const [sortAscending, setSortAscending] = useState(true); const [showOnlyErrors, setShowOnlyErrors] = useState(false); - const additionalHeight = 0; - const fetchPresented = fetchList.length > 0; + + const [isDetailsModalActive, setIsDetailsModalActive] = useState(false); const { sessionStore: { devTools }, } = useStore(); - const filter = useObserver(() => devTools[INDEX_KEY].filter); - const activeTab = useObserver(() => devTools[INDEX_KEY].activeTab); - const activeIndex = useObserver(() => devTools[INDEX_KEY].index); - const [pauseSync, setPauseSync] = useState(activeIndex > 0); - const synRef: any = useRef({}); + const filter = devTools[INDEX_KEY].filter; + const activeTab = devTools[INDEX_KEY].activeTab; + const activeIndex = devTools[INDEX_KEY].index; - const onTabClick = (activeTab: any) => devTools.update(INDEX_KEY, { activeTab }); - const onFilterChange = ({ target: { value } }: any) => { - devTools.update(INDEX_KEY, { filter: value }); - }; + const list = useMemo(() => + // TODO: better merge (with body size info) - do it in player + resourceList.filter(res => !fetchList.some(ft => { + // res.url !== ft.url doesn't work on relative URLs appearing within fetchList (to-fix in player) + if (res.name !== ft.name) { return false } + if (Math.abs(res.time - ft.time) > 150) { return false } // TODO: find good epsilons + if (Math.abs(res.duration - ft.duration) > 100) { return false } + return true + })) + .concat(fetchList) + .sort((a, b) => a.time - b.time) + , [ resourceList.length, fetchList.length ]) - synRef.current = { - pauseSync, + let filteredList = useMemo(() => { + if (!showOnlyErrors) { return list } + return list.filter(it => parseInt(it.status) >= 400 || !it.success) + }, [ showOnlyErrors, list ]) + filteredList = useRegExListFilterMemo( + filteredList, + it => [ it.status, it.name, it.type ], + filter, + ) + filteredList = useTabListFilterMemo(filteredList, it => TYPE_TO_TAB[it.type], ALL, activeTab) + + const onTabClick = (activeTab: typeof TAP_KEYS[number]) => devTools.update(INDEX_KEY, { activeTab }) + const onFilterChange = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => devTools.update(INDEX_KEY, { filter: value }) + + // AutoScroll + const [ + timeoutStartAutoscroll, + stopAutoscroll, + ] = useAutoscroll( + filteredList, + getLastItemTime(fetchListNow, resourceListNow), activeIndex, - }; - - const removePause = () => { - if (!!modalActive) return; - clearTimeout(timeOut); - timeOut = setTimeout(() => { - devTools.update(INDEX_KEY, { index: getCurrentIndex() }); - setPauseSync(false); - }, TIMEOUT_DURATION); - }; - + index => devTools.update(INDEX_KEY, { index }) + ) + const onMouseEnter = stopAutoscroll const onMouseLeave = () => { - if (!!modalActive) return; - removePause(); - }; + if (isDetailsModalActive) { return } + timeoutStartAutoscroll() + } - useEffect(() => { - if (pauseSync) { - removePause(); - } - - return () => { - clearTimeout(timeOut); - if (!synRef.current.pauseSync) { - devTools.update(INDEX_KEY, { index: 0 }); - } - }; - }, []); - - const getCurrentIndex = () => { - return filteredList.filter((item: any) => item.time <= time).length - 1; - }; - - useEffect(() => { - const currentIndex = getCurrentIndex(); - if (currentIndex !== activeIndex && !pauseSync) { - devTools.update(INDEX_KEY, { index: currentIndex }); - } - }, [time]); - - const { resourcesSize, transferredSize } = useMemo(() => { - const resourcesSize = resources.reduce( - (sum: any, { decodedBodySize }: any) => sum + (decodedBodySize || 0), - 0 - ); - - const transferredSize = resources.reduce( - (sum: any, { headerSize, encodedBodySize }: any) => + const resourcesSize = useMemo(() => + resourceList.reduce( + (sum, { decodedBodySize }) => sum + (decodedBodySize || 0), + 0, + ), [ resourceList.length ]) + const transferredSize = useMemo(() => + resourceList.reduce( + (sum, { headerSize, encodedBodySize }) => sum + (headerSize || 0) + (encodedBodySize || 0), - 0 - ); - return { - resourcesSize, - transferredSize, - }; - }, [resources]); + 0, + ), [ resourceList.length ]) - useEffect(() => { - const filterRE = getRE(filter, 'i'); - let list = resources; - - fetchList.forEach( - (fetchCall: any) => - (list = list.filter((networkCall: any) => networkCall.url !== fetchCall.url)) - ); - if (fetchPresented) { - list = list.filter((i: any) => i.type !== TYPES.XHR) - } - list = list.concat(fetchList); - - list = list.filter( - ({ type, name, status, success }: any) => - (!!filter ? filterRE.test(status) || filterRE.test(name) || filterRE.test(type) : true) && - (activeTab === ALL || type === TAB_TO_TYPE_MAP[activeTab]) && - (showOnlyErrors ? parseInt(status) >= 400 || !success : true) - ); - setFilteredList(list); - }, [resources, filter, showOnlyErrors, activeTab, fetchPresented]); const referenceLines = useMemo(() => { const arr = []; @@ -258,31 +226,31 @@ function NetworkPanel(props: Props) { } return arr; - }, []); + }, [ domContentLoadedTime, loadTime ]) - const showDetailsModal = (row: any) => { - clearTimeout(timeOut); - setPauseSync(true); + const showDetailsModal = (item: any) => { + setIsDetailsModalActive(true) showModal( - <FetchDetailsModal resource={row} rows={filteredList} fetchPresented={fetchPresented} />, + <FetchDetailsModal time={item.time + startedAt} resource={item} rows={filteredList} fetchPresented={fetchList.length > 0} />, { right: true, - onClose: removePause, + width: 500, + onClose: () => { + setIsDetailsModalActive(false) + timeoutStartAutoscroll() + } } - ); - devTools.update(INDEX_KEY, { index: filteredList.indexOf(row) }); - }; - - useEffect(() => { - devTools.update(INDEX_KEY, { filter, activeTab }); - }, [filter, activeTab]); + ) + devTools.update(INDEX_KEY, { index: filteredList.indexOf(item) }) + stopAutoscroll() + } return ( <React.Fragment> <BottomBlock - style={{ height: 300 + additionalHeight + 'px' }} + style={{ height: '300px' }} className="border" - onMouseEnter={() => setPauseSync(true)} + onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} > <BottomBlock.Header> @@ -300,7 +268,6 @@ function NetworkPanel(props: Props) { className="input-small" placeholder="Filter by name, type or value" icon="search" - iconPosition="left" name="filter" onChange={onFilterChange} height={28} @@ -364,11 +331,11 @@ function NetworkPanel(props: Props) { referenceLines={referenceLines} renderPopup onRowClick={showDetailsModal} - additionalHeight={additionalHeight} + sortBy={sortBy} + sortAscending={sortAscending} onJump={(row: any) => { - setPauseSync(true); devTools.update(INDEX_KEY, { index: filteredList.indexOf(row) }); - jump(row.time); + player.jump(row.time); }} activeIndex={activeIndex} > @@ -403,7 +370,7 @@ function NetworkPanel(props: Props) { hidden: activeTab === XHR, }, { - label: 'Time', + label: 'Duration', width: 80, dataKey: 'duration', render: renderDuration, @@ -417,16 +384,6 @@ function NetworkPanel(props: Props) { ); } -export default connectPlayer((state: any) => ({ - location: state.location, - resources: state.resourceList, - domContentLoadedTime: state.domContentLoadedTime, - fetchList: state.fetchList.map((i: any) => - Resource({ ...i.toJS(), type: TYPES.XHR, time: i.time < 0 ? 0 : i.time }) - ), - loadTime: state.loadTime, - time: state.time, - playing: state.playing, - domBuildingTime: state.domBuildingTime, - } -))(NetworkPanel); +export default connect((state: any) => ({ + startedAt: state.getIn(['sessions', 'current']).startedAt, +}))(observer(NetworkPanel)); diff --git a/frontend/app/components/shared/DevTools/ProfilerModal/ProfilerModal.tsx b/frontend/app/components/shared/DevTools/ProfilerModal/ProfilerModal.tsx index a5909ada7..fb562b06f 100644 --- a/frontend/app/components/shared/DevTools/ProfilerModal/ProfilerModal.tsx +++ b/frontend/app/components/shared/DevTools/ProfilerModal/ProfilerModal.tsx @@ -9,7 +9,7 @@ function ProfilerModal(props: Props) { } = props; return ( - <div className="bg-white overflow-y-auto h-screen p-5" style={{ width: '500px' }}> + <div className="bg-white overflow-y-auto h-screen p-5"> <h5 className="mb-2 text-2xl">{name}</h5> <h5 className="py-3">{'Arguments'}</h5> <ul className="color-gray-medium"> diff --git a/frontend/app/components/shared/DevTools/ProfilerPanel/ProfilerPanel.tsx b/frontend/app/components/shared/DevTools/ProfilerPanel/ProfilerPanel.tsx index f1fa16219..0b7a8bfc1 100644 --- a/frontend/app/components/shared/DevTools/ProfilerPanel/ProfilerPanel.tsx +++ b/frontend/app/components/shared/DevTools/ProfilerPanel/ProfilerPanel.tsx @@ -1,35 +1,29 @@ -import React, { useState } from 'react'; -import { connectPlayer } from 'Player'; +import React from 'react'; +import { observer } from 'mobx-react-lite'; import { TextEllipsis, Input } from 'UI'; -import { getRE } from 'App/utils'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import useInputState from 'App/hooks/useInputState' -// import ProfileInfo from './ProfileInfo'; import TimeTable from '../TimeTable'; import BottomBlock from '../BottomBlock'; import { useModal } from 'App/components/Modal'; import ProfilerModal from '../ProfilerModal'; +import { useRegExListFilterMemo } from '../useListFilter' const renderDuration = (p: any) => `${p.duration}ms`; const renderName = (p: any) => <TextEllipsis text={p.name} />; -interface Props { - profiles: any; -} -function ProfilerPanel(props: Props) { - const { profiles } = props; +function ProfilerPanel() { + const { store } = React.useContext(PlayerContext) + + const profiles = store.get().profilesList as any[] // TODO lest internal types + const { showModal } = useModal(); - const [filter, setFilter] = useState(''); - const filtered: any = React.useMemo(() => { - const filterRE = getRE(filter, 'i'); - let list = profiles; + const [ filter, onFilterChange ] = useInputState() + const filtered = useRegExListFilterMemo(profiles, pr => pr.name, filter) - list = list.filter(({ name }: any) => (!!filter ? filterRE.test(name) : true)); - return list; - }, [filter]); - - const onFilterChange = ({ target: { value } }: any) => setFilter(value); const onRowClick = (profile: any) => { - showModal(<ProfilerModal profile={profile} />, { right: true }); + showModal(<ProfilerModal profile={profile} />, { right: true, width: 500 }); }; return ( <BottomBlock> @@ -68,8 +62,4 @@ function ProfilerPanel(props: Props) { ); } -export default connectPlayer((state: any) => { - return { - profiles: state.profilesList, - }; -})(ProfilerPanel); +export default observer(ProfilerPanel); diff --git a/frontend/app/components/shared/DevTools/StackEventModal/StackEventModal.tsx b/frontend/app/components/shared/DevTools/StackEventModal/StackEventModal.tsx index c68c0ca80..7dc5b0ebf 100644 --- a/frontend/app/components/shared/DevTools/StackEventModal/StackEventModal.tsx +++ b/frontend/app/components/shared/DevTools/StackEventModal/StackEventModal.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { DATADOG, SENTRY, STACKDRIVER, typeList } from 'Types/session/stackEvent'; +import { DATADOG, SENTRY, STACKDRIVER } from 'Types/session/stackEvent'; import JsonViewer from 'Components/Session_/StackEvents/UserEvent/JsonViewer'; import Sentry from 'Components/Session_/StackEvents/UserEvent/Sentry'; @@ -22,7 +22,7 @@ function StackEventModal(props: Props) { } }; return ( - <div className="bg-white overflow-y-auto h-screen p-5" style={{ width: '500px' }}> + <div className="bg-white overflow-y-auto h-screen p-5"> <h5 className="mb-2 text-2xl">Stack Event</h5> {renderPopupContent()} </div> diff --git a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx index d16122fb3..bfa06f7bb 100644 --- a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx +++ b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx @@ -1,105 +1,90 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { hideHint } from 'Duck/components/player'; +import React, { useEffect, useMemo, useState } from 'react'; +import { observer } from 'mobx-react-lite'; import { Tabs, Input, NoContent, Icon } from 'UI'; -import { getRE } from 'App/utils'; -import { List, CellMeasurer, CellMeasurerCache, AutoSizer } from 'react-virtualized'; - +import { List, CellMeasurer, AutoSizer } from 'react-virtualized'; +import { PlayerContext } from 'App/components/Session/playerContext'; import BottomBlock from '../BottomBlock'; -import { connectPlayer, jump } from 'Player'; import { useModal } from 'App/components/Modal'; import { useStore } from 'App/mstore'; -import { useObserver } from 'mobx-react-lite'; -import { DATADOG, SENTRY, STACKDRIVER, typeList } from 'Types/session/stackEvent'; -import { connect } from 'react-redux'; +import { typeList } from 'Types/session/stackEvent'; import StackEventRow from 'Shared/DevTools/StackEventRow'; -import StackEventModal from '../StackEventModal'; -let timeOut: any = null; -const TIMEOUT_DURATION = 5000; +import StackEventModal from '../StackEventModal'; +import useAutoscroll, { getLastItemTime } from '../useAutoscroll'; +import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter' +import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache' + const INDEX_KEY = 'stackEvent'; const ALL = 'ALL'; -const TABS = [ALL, ...typeList].map((tab) => ({ text: tab, key: tab })); +const TAB_KEYS = [ ALL, ...typeList] as const +const TABS = TAB_KEYS.map((tab) => ({ text: tab, key: tab })) + +function StackEventPanel() { + const { player, store } = React.useContext(PlayerContext) + const jump = (t: number) => player.jump(t) + const { stackList: list, stackListNow: listNow } = store.get() -interface Props { - list: any; - hideHint: any; - time: any; -} -function StackEventPanel(props: Props) { - const { list, time } = props; - const additionalHeight = 0; const { sessionStore: { devTools }, } = useStore(); - const { showModal, component: modalActive } = useModal(); - const [filteredList, setFilteredList] = useState([]); - const filter = useObserver(() => devTools[INDEX_KEY].filter); - const activeTab = useObserver(() => devTools[INDEX_KEY].activeTab); - const activeIndex = useObserver(() => devTools[INDEX_KEY].index); - const [pauseSync, setPauseSync] = useState(activeIndex > 0); - const synRef: any = useRef({}); - synRef.current = { - pauseSync, + const { showModal } = useModal(); + const [isDetailsModalActive, setIsDetailsModalActive] = useState(false) // TODO:embed that into useModal + const filter = devTools[INDEX_KEY].filter + const activeTab = devTools[INDEX_KEY].activeTab + const activeIndex = devTools[INDEX_KEY].index + + let filteredList = useRegExListFilterMemo(list, it => it.name, filter) + filteredList = useTabListFilterMemo(filteredList, it => it.source, ALL, activeTab) + + const onTabClick = (activeTab: typeof TAB_KEYS[number]) => devTools.update(INDEX_KEY, { activeTab }) + const onFilterChange = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => devTools.update(INDEX_KEY, { filter: value }) + const tabs = useMemo(() => + TABS.filter(({ key }) => key === ALL || list.some(({ source }) => key === source)), + [ list.length ], + ) + + const [ + timeoutStartAutoscroll, + stopAutoscroll, + ] = useAutoscroll( + filteredList, + getLastItemTime(listNow), activeIndex, - }; - const _list = React.useRef(); - - const onTabClick = (activeTab: any) => devTools.update(INDEX_KEY, { activeTab }); - const onFilterChange = ({ target: { value } }: any) => { - devTools.update(INDEX_KEY, { filter: value }); - }; - - const getCurrentIndex = () => { - return filteredList.filter((item: any) => item.time <= time).length - 1; - }; - - const removePause = () => { - if (!!modalActive) return; - clearTimeout(timeOut); - timeOut = setTimeout(() => { - devTools.update(INDEX_KEY, { index: getCurrentIndex() }); - setPauseSync(false); - }, TIMEOUT_DURATION); - }; - - useEffect(() => { - const currentIndex = getCurrentIndex(); - if (currentIndex !== activeIndex && !pauseSync) { - devTools.update(INDEX_KEY, { index: currentIndex }); - } - }, [time]); - + index => devTools.update(INDEX_KEY, { index }) + ) + const onMouseEnter = stopAutoscroll const onMouseLeave = () => { - removePause(); - }; + if (isDetailsModalActive) { return } + timeoutStartAutoscroll() + } - React.useMemo(() => { - const filterRE = getRE(filter, 'i'); - let list = props.list; - - list = list.filter( - ({ name, source }: any) => - (!!filter ? filterRE.test(name) : true) && (activeTab === ALL || activeTab === source) - ); - - setFilteredList(list); - }, [filter, activeTab]); - - const tabs = useMemo(() => { - return TABS.filter(({ key }) => key === ALL || list.some(({ source }: any) => key === source)); - }, []); - - const cache = new CellMeasurerCache({ - fixedWidth: true, - keyMapper: (index: number) => filteredList[index], - }); + const cache = useCellMeasurerCache(filteredList) const showDetails = (item: any) => { - clearTimeout(timeOut); - showModal(<StackEventModal event={item} />, { right: true, onClose: removePause }); - devTools.update(INDEX_KEY, { index: filteredList.indexOf(item) }); - setPauseSync(true); - }; + setIsDetailsModalActive(true) + showModal( + <StackEventModal event={item} />, + { + right: true, + width: 500, + onClose: () => { + setIsDetailsModalActive(false) + timeoutStartAutoscroll() + } + } + ) + devTools.update(INDEX_KEY, { index: filteredList.indexOf(item) }) + stopAutoscroll() + } + + const _list = React.useRef() + useEffect(() => { + if (_list.current) { + // @ts-ignore + _list.current.scrollToRow(activeIndex) + } + }, [ activeIndex ]) + const _rowRenderer = ({ index, key, parent, style }: any) => { const item = filteredList[index]; @@ -114,7 +99,7 @@ function StackEventPanel(props: Props) { key={item.key} event={item} onJump={() => { - setPauseSync(true); + stopAutoscroll() devTools.update(INDEX_KEY, { index: filteredList.indexOf(item) }); jump(item.time); }} @@ -123,19 +108,12 @@ function StackEventPanel(props: Props) { )} </CellMeasurer> ); - }; - - useEffect(() => { - if (_list.current) { - // @ts-ignore - _list.current.scrollToRow(activeIndex); - } - }, [activeIndex]); + } return ( <BottomBlock - style={{ height: 300 + additionalHeight + 'px' }} - onMouseEnter={() => setPauseSync(true)} + style={{ height: '300px' }} + onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} > <BottomBlock.Header> @@ -147,7 +125,6 @@ function StackEventPanel(props: Props) { className="input-small h-8" placeholder="Filter by keyword" icon="search" - iconPosition="left" name="filter" height={28} onChange={onFilterChange} @@ -186,16 +163,4 @@ function StackEventPanel(props: Props) { ); } -export default connect( - (state: any) => ({ - hintIsHidden: - state.getIn(['components', 'player', 'hiddenHints', 'stack']) || - !state.getIn(['site', 'list']).some((s: any) => s.stackIntegrations), - }), - { hideHint } -)( - connectPlayer((state: any) => ({ - list: state.stackList, - time: state.time, - }))(StackEventPanel) -); +export default observer(StackEventPanel) diff --git a/frontend/app/components/shared/DevTools/StackEventRow/StackEventRow.tsx b/frontend/app/components/shared/DevTools/StackEventRow/StackEventRow.tsx index 0d2eeb554..1246ca1da 100644 --- a/frontend/app/components/shared/DevTools/StackEventRow/StackEventRow.tsx +++ b/frontend/app/components/shared/DevTools/StackEventRow/StackEventRow.tsx @@ -2,7 +2,7 @@ import React from 'react'; import JumpButton from '../JumpButton'; import { Icon } from 'UI'; import cn from 'classnames'; -import { OPENREPLAY, SENTRY, DATADOG, STACKDRIVER } from 'Types/session/stackEvent'; +import { OPENREPLAY } from 'Types/session/stackEvent'; interface Props { event: any; @@ -28,7 +28,7 @@ function StackEventRow(props: Props) { return ( <div style={style} - data-scroll-item={event.isRed()} + data-scroll-item={event.isRed} onClick={props.onClick} className={cn( 'group flex items-center py-2 px-4 border-b cursor-pointer relative', diff --git a/frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx b/frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx index f283eb7ac..eaaebb754 100644 --- a/frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx +++ b/frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx @@ -1,4 +1,3 @@ -import { Tooltip } from 'UI'; import { percentOf } from 'App/utils'; import styles from './barRow.module.css'; import tableStyles from './timeTable.module.css'; diff --git a/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx b/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx index 8271a6561..3ff1ca54d 100644 --- a/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx +++ b/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { List, AutoSizer } from 'react-virtualized'; import cn from 'classnames'; import { Duration } from 'luxon'; -import { NoContent, Icon, Button } from 'UI'; +import { NoContent, Button } from 'UI'; import { percentOf } from 'App/utils'; import BarRow from './BarRow'; @@ -21,7 +21,7 @@ type Durationed = { type CanBeRed = { //+isRed: boolean, - isRed: () => boolean; + isRed: boolean; }; interface Row extends Timed, Durationed, CanBeRed { @@ -211,10 +211,10 @@ export default class TimeTable extends React.PureComponent<Props, State> { stl.row, { [stl.hoverable]: hoverable, - 'error color-red': !!row.isRed && row.isRed(), + 'error color-red': row.isRed, 'cursor-pointer': typeof onRowClick === 'function', [stl.activeRow]: activeIndex === index, - // [stl.inactiveRow]: !activeIndex || index > activeIndex, + [stl.inactiveRow]: !activeIndex || index > activeIndex, } )} onClick={typeof onRowClick === 'function' ? () => onRowClick(row, index) : undefined} @@ -222,8 +222,8 @@ export default class TimeTable extends React.PureComponent<Props, State> { > {columns .filter((i: any) => !i.hidden) - .map(({ dataKey, render, width }) => ( - <div className={stl.cell} style={{ width: `${width}px` }}> + .map(({ dataKey, render, width, label }) => ( + <div key={parseInt(label.replace(' ', '')+dataKey, 36)} className={stl.cell} style={{ width: `${width}px` }}> {render ? render(row) : row[dataKey || ''] || <i className="color-gray-light">{'empty'}</i>} @@ -240,7 +240,7 @@ export default class TimeTable extends React.PureComponent<Props, State> { onPrevClick = () => { let prevRedIndex = -1; for (let i = this.state.firstVisibleRowIndex - 1; i >= 0; i--) { - if (this.props.rows[i].isRed()) { + if (this.props.rows[i].isRed) { prevRedIndex = i; break; } @@ -253,7 +253,7 @@ export default class TimeTable extends React.PureComponent<Props, State> { onNextClick = () => { let prevRedIndex = -1; for (let i = this.state.firstVisibleRowIndex + 1; i < this.props.rows.length; i++) { - if (this.props.rows[i].isRed()) { + if (this.props.rows[i].isRed) { prevRedIndex = i; break; } @@ -327,6 +327,7 @@ export default class TimeTable extends React.PureComponent<Props, State> { <div className={stl.infoHeaders}> {columns.map(({ label, width, dataKey, onClick = null }) => ( <div + key={parseInt(label.replace(' ', ''), 36)} className={cn(stl.headerCell, 'flex items-center select-none', { 'cursor-pointer': typeof onClick === 'function', })} @@ -354,6 +355,7 @@ export default class TimeTable extends React.PureComponent<Props, State> { ))} {visibleRefLines.map(({ time, color, onClick }) => ( <div + key={time} className={cn(stl.refLine, `bg-${color}`)} style={{ left: `${percentOf(time - timestart, timewidth)}%`, diff --git a/frontend/app/components/shared/DevTools/useAutoscroll.ts b/frontend/app/components/shared/DevTools/useAutoscroll.ts new file mode 100644 index 000000000..5225f59ea --- /dev/null +++ b/frontend/app/components/shared/DevTools/useAutoscroll.ts @@ -0,0 +1,64 @@ +import { useEffect, useState, useMemo } from 'react' +import { Timed } from 'Player' +import useLatestRef from 'App/hooks/useLatestRef' +import useCancelableTimeout from 'App/hooks/useCancelableTimeout' + +const TIMEOUT_DURATION = 5000; + +export function getLastItemTime(...lists: Timed[][]) { + return Math.max(...lists.map(l => l.length ? l[l.length-1].time : 0)) +} + +function useAutoupdate<T>( + savedValue: T, + actualValue: T, + resetValue: T, + updadteValue: (value: T) => void, +) { + const [ autoupdate, setAutoupdate ] = useState(savedValue === resetValue) + + const [ timeoutStartAutoupdate, stopAutoupdate ] = useCancelableTimeout( + () => setAutoupdate(true), + () => setAutoupdate(false), + TIMEOUT_DURATION, + ) + useEffect(() => { + if (autoupdate && actualValue !== savedValue) { + updadteValue(actualValue) + } + }, [ autoupdate, actualValue ]) + + const autoScrollRef = useLatestRef(autoupdate) + useEffect(() => { + if (!autoupdate) { + timeoutStartAutoupdate() + } + return () => { + if (autoScrollRef.current) { + updadteValue(resetValue) + } + } + }, []) + + return [ timeoutStartAutoupdate, stopAutoupdate ] +} + +// That might be simplified by removing index from devTools[INDEX_KEY] store... +export default function useAutoscroll( + filteredList: Timed[], + time: number, + savedIndex: number, + updadteIndex: (index: number) => void, +) { + const filteredIndexNow = useMemo(() => { + // Should use findLastIndex here + for (let i=0; i < filteredList.length; i++) { + if (filteredList[i].time > time) { + return i-1 + } + } + return filteredList.length + }, [ time, filteredList ]) + + return useAutoupdate(savedIndex, filteredIndexNow, 0, updadteIndex) +} \ No newline at end of file diff --git a/frontend/app/components/shared/DevTools/useListFilter.ts b/frontend/app/components/shared/DevTools/useListFilter.ts new file mode 100644 index 000000000..8f526c524 --- /dev/null +++ b/frontend/app/components/shared/DevTools/useListFilter.ts @@ -0,0 +1,34 @@ +import { useMemo } from 'react' +import { getRE } from 'App/utils' + + +// TODO: merge with utils/filterList (use logic of string getter like here instead of using callback) +export function useRegExListFilterMemo<T>( + list: T[], + filterBy: (it: T) => string | string[], + reText: string, +) { + return useMemo(() => { + if (!reText) { return list } + const re = getRE(reText, 'i') + return list.filter(it => { + const strs = filterBy(it) + return Array.isArray(strs) + ? strs.some(s => re.test(s)) + : re.test(strs) + }) + }, [ list, list.length, reText ]) +} + + +export function useTabListFilterMemo<T, Tab=string>( + list: T[], + itemToTab: (it: T) => Tab, + commonTab: Tab, + currentTab: Tab, +) { + return useMemo(() => { + if (currentTab === commonTab) { return list } + return list.filter(it => itemToTab(it) === currentTab) + }, [ list, list.length, currentTab ]) +} \ No newline at end of file diff --git a/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.tsx b/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.tsx index bcee5f5b9..09a08ae16 100644 --- a/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.tsx +++ b/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.tsx @@ -1,13 +1,14 @@ import React, { useEffect, useState } from 'react'; import FetchBasicDetails from './components/FetchBasicDetails'; import { Button } from 'UI'; -import FetchPluginMessage from './components/FetchPluginMessage'; -import { TYPES } from 'Types/session/resource'; +import { ResourceType } from 'Player'; import FetchTabs from './components/FetchTabs/FetchTabs'; import { useStore } from 'App/mstore'; +import { DateTime } from 'luxon'; interface Props { resource: any; + time?: number; rows?: any; fetchPresented?: boolean; } @@ -16,9 +17,10 @@ function FetchDetailsModal(props: Props) { const [resource, setResource] = useState(props.resource); const [first, setFirst] = useState(false); const [last, setLast] = useState(false); - const isXHR = resource.type === TYPES.XHR || resource.type === TYPES.FETCH; + const isXHR = resource.type === ResourceType.XHR || resource.type === ResourceType.FETCH const { sessionStore: { devTools }, + settingsStore: { sessionSettings: { timezone }}, } = useStore(); useEffect(() => { @@ -47,9 +49,8 @@ function FetchDetailsModal(props: Props) { return ( <div className="bg-white p-5 h-screen overflow-y-auto" style={{ width: '500px' }}> <h5 className="mb-2 text-2xl">Network Request</h5> - <FetchBasicDetails resource={resource} /> + <FetchBasicDetails resource={resource} timestamp={props.time ? DateTime.fromMillis(props.time).setZone(timezone.value).toFormat(`hh:mm:ss a`) : undefined} /> - {isXHR && !fetchPresented && <FetchPluginMessage />} {isXHR && <FetchTabs resource={resource} />} {rows && rows.length > 0 && ( diff --git a/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx b/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx index 49e16c00f..94aadfb1d 100644 --- a/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx +++ b/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx @@ -5,8 +5,9 @@ import cn from 'classnames'; interface Props { resource: any; + timestamp?: string; } -function FetchBasicDetails({ resource }: Props) { +function FetchBasicDetails({ resource, timestamp }: Props) { const _duration = parseInt(resource.duration); const text = useMemo(() => { if (resource.url.length > 50) { @@ -69,12 +70,22 @@ function FetchBasicDetails({ resource }: Props) { {!!_duration && ( <div className="flex items-center py-1"> - <div className="font-medium">Time</div> + <div className="font-medium">Duration</div> <div className="rounded bg-active-blue px-2 py-1 ml-2 whitespace-nowrap overflow-hidden text-clip"> {_duration} ms </div> </div> )} + + {timestamp && ( + <div className="flex items-center py-1"> + <div className="font-medium">Time</div> + <div className="rounded bg-active-blue px-2 py-1 ml-2 whitespace-nowrap overflow-hidden text-clip"> + {timestamp} + </div> + </div> + + )} </div> ); } diff --git a/frontend/app/components/shared/FetchDetailsModal/components/FetchPluginMessage/FetchPluginMessage.tsx b/frontend/app/components/shared/FetchDetailsModal/components/FetchPluginMessage/FetchPluginMessage.tsx deleted file mode 100644 index 8699b8cb6..000000000 --- a/frontend/app/components/shared/FetchDetailsModal/components/FetchPluginMessage/FetchPluginMessage.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { Icon } from 'UI'; - -function FetchPluginMessage() { - return ( - <div className="bg-active-blue rounded p-3 mt-4"> - <div className="mb-2 flex items-center"> - <Icon name="lightbulb" size="18" /> - <span className="ml-2 font-medium">Get more out of network requests</span> - </div> - <ul className="list-disc ml-5"> - <li> - Integrate{' '} - <a href="https://docs.openreplay.com/plugins/fetch" className="link" target="_blank"> - Fetch plugin - </a>{' '} - to capture fetch payloads. - </li> - <li> - Find a detailed{' '} - <a href="https://www.youtube.com/watch?v=YFCKstPZzZg" className="link" target="_blank"> - video tutorial - </a>{' '} - to understand practical example of how to use fetch plugin. - </li> - </ul> - </div> - ); -} - -export default FetchPluginMessage; diff --git a/frontend/app/components/shared/FetchDetailsModal/components/FetchPluginMessage/index.ts b/frontend/app/components/shared/FetchDetailsModal/components/FetchPluginMessage/index.ts deleted file mode 100644 index df224dbbf..000000000 --- a/frontend/app/components/shared/FetchDetailsModal/components/FetchPluginMessage/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './FetchPluginMessage'; diff --git a/frontend/app/components/shared/FetchDetailsModal/components/FetchTabs/FetchTabs.tsx b/frontend/app/components/shared/FetchDetailsModal/components/FetchTabs/FetchTabs.tsx index 837a61ec6..c6495227b 100644 --- a/frontend/app/components/shared/FetchDetailsModal/components/FetchTabs/FetchTabs.tsx +++ b/frontend/app/components/shared/FetchDetailsModal/components/FetchTabs/FetchTabs.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import logger from 'App/logger' import Headers from '../Headers'; import { JSONTree, Tabs, NoContent } from 'UI'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; @@ -8,65 +9,104 @@ const REQUEST = 'REQUEST'; const RESPONSE = 'RESPONSE'; const TABS = [HEADERS, REQUEST, RESPONSE].map((tab) => ({ text: tab, key: tab })); -interface Props { - resource: any; +function parseRequestResponse( + r: string, + setHeaders: (hs: Record<string, string> | null) => void, + setJSONBody: (body: Record<string, string> | null) => void, + setStringBody: (body: string) => void, +) { + try { + if (!r) { + setHeaders(null); + setJSONBody(null); + setStringBody(''); + return; + } + const json = JSON.parse(r) + const hs = json.headers + const bd = json.body as string + + if (typeof hs === "object") { + setHeaders(hs); + } else { + setHeaders(null); + } + if (!bd) { + setJSONBody(null) + setStringBody('') + } + try { + const jBody = JSON.parse(bd) + if (typeof jBody === "object" && jBody != null) { + setJSONBody(jBody) + } else { + setStringBody(bd) + } + } catch { + setStringBody(bd) + } + } catch(e) { logger.error("Error decoding payload json:", e, r)} } -function FetchTabs(props: Props) { - const { resource } = props; + + +interface Props { + resource: { request: string, response: string }; +} +function FetchTabs({ resource }: Props) { const [activeTab, setActiveTab] = useState(HEADERS); const onTabClick = (tab: string) => setActiveTab(tab); - const [jsonPayload, setJsonPayload] = useState(null); - const [jsonResponse, setJsonResponse] = useState(null); - const [requestHeaders, setRequestHeaders] = useState(null); - const [responseHeaders, setResponseHeaders] = useState(null); + const [jsonRequest, setJsonRequest] = useState<Object | null>(null); + const [jsonResponse, setJsonResponse] = useState<Object | null>(null); + const [stringRequest, setStringRequest] = useState<string>(''); + const [stringResponse, setStringResponse ] = useState<string>(''); + const [requestHeaders, setRequestHeaders] = useState<Record<string,string> | null>(null); + const [responseHeaders, setResponseHeaders] = useState<Record<string,string> | null>(null); useEffect(() => { - const { payload, response } = resource; - - try { - let jsonPayload = typeof payload === 'string' ? JSON.parse(payload) : payload; - let requestHeaders = jsonPayload.headers; - jsonPayload.body = - typeof jsonPayload.body === 'string' ? JSON.parse(jsonPayload.body) : jsonPayload.body; - delete jsonPayload.headers; - setJsonPayload(jsonPayload); - setRequestHeaders(requestHeaders); - } catch (e) {} - - try { - let jsonResponse = typeof response === 'string' ? JSON.parse(response) : response; - let responseHeaders = jsonResponse.headers; - jsonResponse.body = - typeof jsonResponse.body === 'string' ? JSON.parse(jsonResponse.body) : jsonResponse.body; - delete jsonResponse.headers; - setJsonResponse(jsonResponse); - setResponseHeaders(responseHeaders); - } catch (e) {} - }, [resource, activeTab]); + const { request, response } = resource; + parseRequestResponse( + request, + setRequestHeaders, + setJsonRequest, + setStringRequest, + ) + parseRequestResponse( + response, + setResponseHeaders, + setJsonResponse, + setStringResponse, + ) + }, [resource]); const renderActiveTab = () => { - const { payload, response } = resource; switch (activeTab) { case REQUEST: return ( <NoContent title={ <div className="flex flex-col items-center justify-center"> - <AnimatedSVG name={ICONS.NO_RESULTS} size="170" /> - <div className="mt-6 text-2xl">Body is Empty.</div> + <AnimatedSVG name={ICONS.NO_RESULTS} size={170} /> + <div className="mt-6 text-2xl"> + Body is Empty or not captured. + {' '} + <a href="https://docs.openreplay.com/installation/network-options" className="link" target="_blank"> + Configure + </a> + {' '} + network capturing to get more out of fetch/XHR requests. + </div> </div> } size="small" - show={!payload} + show={!jsonRequest && !stringRequest} // animatedIcon="no-results" > <div> <div className="mt-6"> - {jsonPayload === undefined ? ( - <div className="ml-3 break-words my-3"> {payload} </div> - ) : ( - <JSONTree src={jsonPayload} collapsed={false} enableClipboard /> - )} + { jsonRequest + ? <JSONTree src={jsonRequest} collapsed={false} enableClipboard /> + : <div className="ml-3 break-words my-3"> {stringRequest} </div> + } </div> <div className="divider" /> </div> @@ -77,21 +117,28 @@ function FetchTabs(props: Props) { <NoContent title={ <div className="flex flex-col items-center justify-center"> - <AnimatedSVG name={ICONS.NO_RESULTS} size="170" /> - <div className="mt-6 text-2xl">Body is Empty.</div> + <AnimatedSVG name={ICONS.NO_RESULTS} size={170} /> + <div className="mt-6 text-2xl"> + Body is Empty or not captured. + {' '} + <a href="https://docs.openreplay.com/installation/network-options" className="link" target="_blank"> + Configure + </a> + {' '} + network capturing to get more out of fetch/XHR requests. + </div> </div> } size="small" - show={!response} + show={!jsonResponse && !stringResponse} // animatedIcon="no-results" > <div> <div className="mt-6"> - {jsonResponse === undefined ? ( - <div className="ml-3 break-words my-3"> {response} </div> - ) : ( - <JSONTree src={jsonResponse} collapsed={false} enableClipboard /> - )} + { jsonResponse + ? <JSONTree src={jsonResponse} collapsed={false} enableClipboard /> + : <div className="ml-3 break-words my-3"> {stringResponse} </div> + } </div> <div className="divider" /> </div> diff --git a/frontend/app/components/shared/FetchDetailsModal/components/Headers/Headers.tsx b/frontend/app/components/shared/FetchDetailsModal/components/Headers/Headers.tsx index 78c4a5e64..dbfdf81a8 100644 --- a/frontend/app/components/shared/FetchDetailsModal/components/Headers/Headers.tsx +++ b/frontend/app/components/shared/FetchDetailsModal/components/Headers/Headers.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import { NoContent, TextEllipsis } from 'UI'; +import { NoContent } from 'UI'; import stl from './headers.module.css'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; interface Props { - requestHeaders: any; - responseHeaders: any; + requestHeaders: Record<string,string> + responseHeaders: Record<string,string> } function Headers(props: Props) { return ( @@ -21,7 +21,7 @@ function Headers(props: Props) { show={!props.requestHeaders && !props.responseHeaders} // animatedIcon="no-results" > - {props.requestHeaders && ( + {props.requestHeaders && Object.values(props.requestHeaders).length > 0 && ( <> <div className="mb-4 mt-4"> <div className="my-2 font-medium">Request Headers</div> @@ -36,7 +36,7 @@ function Headers(props: Props) { </> )} - {props.responseHeaders && ( + {props.responseHeaders && Object.values(props.responseHeaders).length > 0 && ( <div className="mt-4"> <div className="my-2 font-medium">Response Headers</div> {Object.keys(props.responseHeaders).map((h) => ( diff --git a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx index a616d8853..6f058fb86 100644 --- a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx +++ b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx @@ -115,6 +115,7 @@ interface Props { onSelect: (e: any, item: any) => void; value: any; icon?: string; + hideOrText?: boolean } function FilterAutoComplete(props: Props) { @@ -128,6 +129,7 @@ function FilterAutoComplete(props: Props) { endpoint = '', params = {}, value = '', + hideOrText = false, } = props; const [loading, setLoading] = useState(false); const [options, setOptions] = useState<any>([]); @@ -145,17 +147,17 @@ function FilterAutoComplete(props: Props) { new APIClient() [method?.toLocaleLowerCase()](endpoint, { ...params, q: inputValue }) .then((response: any) => { - if (response.ok) { return response.json(); - } - throw new Error(response.statusText); }) .then(({ data }: any) => { const _options = data.map((i: any) => ({ value: i.value, label: i.value })) || []; setOptions(_options); callback(_options); setLoading(false); - }); + }) + .catch((e) => { + throw new Error(e); + }) }; const debouncedLoadOptions = React.useCallback(debounce(loadOptions, 1000), [params]); @@ -240,7 +242,7 @@ function FilterAutoComplete(props: Props) { </div> </div> - {!showOrButton && <div className="ml-3">or</div>} + {!showOrButton && !hideOrText && <div className="ml-3">or</div>} </div> ); } diff --git a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx index 4b27e4a93..60dd9b3a4 100644 --- a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx +++ b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx @@ -2,7 +2,7 @@ import React from 'react'; import FilterOperator from '../FilterOperator'; import FilterSelection from '../FilterSelection'; import FilterValue from '../FilterValue'; -import { Icon } from 'UI'; +import { Button } from 'UI'; import FilterSource from '../FilterSource'; import { FilterKey, FilterType } from 'App/types/filter/filterType'; import SubFilterItem from '../SubFilterItem'; @@ -14,9 +14,11 @@ interface Props { onRemoveFilter: () => void; isFilter?: boolean; saveRequestPayloads?: boolean; + disableDelete?: boolean; + excludeFilterKeys?: Array<string>; } function FilterItem(props: Props) { - const { isFilter = false, filterIndex, filter, saveRequestPayloads } = props; + const { isFilter = false, filterIndex, filter, saveRequestPayloads, disableDelete = false, excludeFilterKeys = [] } = props; const canShowValues = !(filter.operator === 'isAny' || filter.operator === 'onAny' || filter.operator === 'isUndefined'); const isSubFilter = filter.type === FilterType.SUB_FILTERS; @@ -28,11 +30,11 @@ function FilterItem(props: Props) { }); }; - const onOperatorChange = (e: any, { name, value }: any) => { + const onOperatorChange = (e: any, { value }: any) => { props.onUpdate({ ...filter, operator: value }); }; - const onSourceOperatorChange = (e: any, { name, value }: any) => { + const onSourceOperatorChange = (e: any, { value }: any) => { props.onUpdate({ ...filter, sourceOperator: value }); }; @@ -49,14 +51,14 @@ function FilterItem(props: Props) { }; return ( - <div className="flex items-center hover:bg-active-blue -mx-5 px-5 py-2"> + <div className="flex items-center hover:bg-active-blue -mx-5 px-5"> <div className="flex items-start w-full"> {!isFilter && ( <div className="mt-1 flex-shrink-0 border w-6 h-6 text-xs flex items-center justify-center rounded-full bg-gray-light-shade mr-2"> <span>{filterIndex + 1}</span> </div> )} - <FilterSelection filter={filter} onFilterClick={replaceFilter} /> + <FilterSelection filter={filter} onFilterClick={replaceFilter} excludeFilterKeys={excludeFilterKeys} disabled={disableDelete} /> {/* Filter with Source */} {filter.hasSource && ( @@ -102,10 +104,8 @@ function FilterItem(props: Props) { </div> )} </div> - <div className="flex flex-shrink-0 self-start mt-1 ml-auto px-2"> - <div className="cursor-pointer p-1" onClick={props.onRemoveFilter}> - <Icon name="trash" size="14" /> - </div> + <div className="flex flex-shrink-0 self-start ml-auto"> + <Button disabled={disableDelete} variant="text" icon="trash" onClick={props.onRemoveFilter} size="small" iconSize={14} /> </div> </div> ); diff --git a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx index ab53a4214..e4b919bd1 100644 --- a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx +++ b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useEffect } from 'react'; import FilterItem from '../FilterItem'; import { SegmentSelection, Tooltip } from 'UI'; import { List } from 'immutable'; @@ -12,13 +12,17 @@ interface Props { hideEventsOrder?: boolean; observeChanges?: () => void; saveRequestPayloads?: boolean; + supportsEmpty?: boolean + excludeFilterKeys?: Array<string> } function FilterList(props: Props) { - const { observeChanges = () => {}, filter, hideEventsOrder = false, saveRequestPayloads } = props; + const { observeChanges = () => {}, filter, hideEventsOrder = false, saveRequestPayloads, supportsEmpty = true, excludeFilterKeys = [] } = props; const filters = List(filter.filters); + const eventsOrderSupport = filter.eventsOrderSupport; const hasEvents = filters.filter((i: any) => i.isEvent).size > 0; const hasFilters = filters.filter((i: any) => !i.isEvent).size > 0; let rowIndex = 0; + const cannotDeleteFilter = hasEvents && !supportsEmpty; useEffect(observeChanges, [filters]); @@ -48,13 +52,13 @@ function FilterList(props: Props) { <SegmentSelection primary name="eventsOrder" - extraSmall={true} + size="small" onSelect={props.onChangeEventsOrder} value={{ value: filter.eventsOrder }} list={[ - { name: 'THEN', value: 'then' }, - { name: 'AND', value: 'and' }, - { name: 'OR', value: 'or' }, + { name: 'THEN', value: 'then', disabled: eventsOrderSupport && !eventsOrderSupport.includes('then') }, + { name: 'AND', value: 'and', disabled: eventsOrderSupport && !eventsOrderSupport.includes('and')}, + { name: 'OR', value: 'or', disabled: eventsOrderSupport && !eventsOrderSupport.includes('or')}, ]} /> </div> @@ -69,6 +73,8 @@ function FilterList(props: Props) { onUpdate={(filter) => props.onUpdateFilter(filterIndex, filter)} onRemoveFilter={() => onRemoveFilter(filterIndex)} saveRequestPayloads={saveRequestPayloads} + disableDelete={cannotDeleteFilter} + excludeFilterKeys={excludeFilterKeys} /> ) : null )} @@ -89,6 +95,7 @@ function FilterList(props: Props) { filter={filter} onUpdate={(filter) => props.onUpdateFilter(filterIndex, filter)} onRemoveFilter={() => onRemoveFilter(filterIndex)} + excludeFilterKeys={excludeFilterKeys} /> ) : null )} diff --git a/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx b/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx index 421a12837..4e3656d20 100644 --- a/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx +++ b/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx @@ -4,11 +4,29 @@ import { connect } from 'react-redux'; import cn from 'classnames'; import stl from './FilterModal.module.css'; import { filtersMap } from 'Types/filter/newFilter'; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; + +function filterJson( + jsonObj: Record<string, any>, + excludeKeys: string[] = [] +): Record<string, any> { + let filtered: Record<string, any> = {}; + + for (const key in jsonObj) { + const arr = jsonObj[key].filter((i: any) => !excludeKeys.includes(i.key)); + if (arr.length) { + filtered[key] = arr; + } + } + + return filtered; +} export const getMatchingEntries = (searchQuery: string, filters: Record<string, any>) => { const matchingCategories: string[] = []; const matchingFilters: Record<string, any> = {}; const lowerCaseQuery = searchQuery.toLowerCase(); + if (lowerCaseQuery.length === 0) return { matchingCategories: Object.keys(filters), matchingFilters: filters, @@ -32,12 +50,13 @@ export const getMatchingEntries = (searchQuery: string, filters: Record<string, interface Props { filters: any, - onFilterClick?: (filter) => void, + onFilterClick?: (filter: any) => void, filterSearchList: any, // metaOptions: any, isMainSearch?: boolean, fetchingFilterSearchList: boolean, searchQuery?: string, + excludeFilterKeys?: Array<string> } function FilterModal(props: Props) { const { @@ -47,6 +66,7 @@ function FilterModal(props: Props) { isMainSearch = false, fetchingFilterSearchList, searchQuery = '', + excludeFilterKeys = [] } = props; const showSearchList = isMainSearch && searchQuery.length > 0; @@ -56,12 +76,11 @@ function FilterModal(props: Props) { onFilterClick(_filter); } - const { matchingCategories, matchingFilters } = getMatchingEntries(searchQuery, filters); + const { matchingCategories, matchingFilters } = getMatchingEntries(searchQuery, filterJson(filters, excludeFilterKeys)); const isResultEmpty = (!filterSearchList || Object.keys(filterSearchList).length === 0) && matchingCategories.length === 0 && Object.keys(matchingFilters).length === 0 - // console.log(matchingFilters) return ( <div className={stl.wrapper} style={{ width: '480px', maxHeight: '380px', overflowY: 'auto'}}> <div className={searchQuery && !isResultEmpty ? 'mb-6' : ''} style={{ columns: matchingCategories.length > 1 ? 'auto 200px' : 1 }}> @@ -86,8 +105,8 @@ function FilterModal(props: Props) { <Loader size="small" loading={fetchingFilterSearchList}> <div className="-mx-6 px-6"> {isResultEmpty && !fetchingFilterSearchList ? ( - <div className="flex items-center"> - <Icon className="color-gray-medium" name="binoculars" size="24" /> + <div className="flex items-center flex-col"> + <AnimatedSVG name={ICONS.NO_SEARCH_RESULTS} size={180} /> <div className="color-gray-medium font-medium px-3"> No Suggestions Found </div> </div> ) : Object.keys(filterSearchList).map((key, index) => { diff --git a/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx b/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx index d4e20d322..e78904936 100644 --- a/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx +++ b/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx @@ -3,7 +3,8 @@ import FilterModal from '../FilterModal'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; import { Icon } from 'UI'; import { connect } from 'react-redux'; -import { assist as assistRoute, isRoute } from "App/routes"; +import { assist as assistRoute, isRoute } from 'App/routes'; +import cn from 'classnames'; const ASSIST_ROUTE = assistRoute(); @@ -14,40 +15,54 @@ interface Props { onFilterClick: (filter: any) => void; children?: any; isLive?: boolean; + excludeFilterKeys?: Array<string> + disabled?: boolean } function FilterSelection(props: Props) { - const { filter, onFilterClick, children } = props; + const { filter, onFilterClick, children, excludeFilterKeys = [], disabled = false } = props; const [showModal, setShowModal] = useState(false); return ( <div className="relative flex-shrink-0"> <OutsideClickDetectingDiv className="relative" - onClickOutside={ () => setTimeout(function() { - setShowModal(false) - }, 200)} + onClickOutside={() => + setTimeout(function () { + setShowModal(false); + }, 200) + } > - { children ? React.cloneElement(children, { onClick: (e) => { - e.stopPropagation(); - e.preventDefault(); - setShowModal(true); - }}) : ( + {children ? ( + React.cloneElement(children, { + onClick: (e) => { + e.stopPropagation(); + e.preventDefault(); + setShowModal(true); + }, + disabled: disabled + }) + ) : ( <div - className="rounded py-1 px-3 flex items-center cursor-pointer bg-gray-lightest text-ellipsis hover:bg-gray-light-shade" + className={cn("rounded py-1 px-3 flex items-center cursor-pointer bg-gray-lightest text-ellipsis hover:bg-gray-light-shade", { 'opacity-50 pointer-events-none' : disabled })} style={{ width: '150px', height: '26px', border: 'solid thin #e9e9e9' }} onClick={() => setShowModal(true)} > - <div className="overflow-hidden whitespace-nowrap text-ellipsis mr-auto truncate" style={{ textOverflow: 'ellipsis'}}>{filter.label}</div> + <div + className="overflow-hidden whitespace-nowrap text-ellipsis mr-auto truncate" + style={{ textOverflow: 'ellipsis' }} + > + {filter.label} + </div> <Icon name="chevron-down" size="14" /> </div> - ) } + )} </OutsideClickDetectingDiv> {showModal && ( <div className="absolute left-0 border shadow rounded bg-white z-50"> <FilterModal isLive={isRoute(ASSIST_ROUTE, window.location.pathname)} onFilterClick={onFilterClick} - // filters={isRoute(ASSIST_ROUTE, window.location.pathname) ? props.filterListLive : props.filterList } + excludeFilterKeys={excludeFilterKeys} /> </div> )} @@ -55,8 +70,11 @@ function FilterSelection(props: Props) { ); } -export default connect((state: any) => ({ - filterList: state.getIn([ 'search', 'filterList' ]), - filterListLive: state.getIn([ 'search', 'filterListLive' ]), - isLive: state.getIn([ 'sessions', 'activeTab' ]).type === 'live', -}), { })(FilterSelection); \ No newline at end of file +export default connect( + (state: any) => ({ + filterList: state.getIn(['search', 'filterList']), + filterListLive: state.getIn(['search', 'filterListLive']), + isLive: state.getIn(['sessions', 'activeTab']).type === 'live', + }), + {} +)(FilterSelection); diff --git a/frontend/app/components/shared/Filters/FilterSource/FilterSource.tsx b/frontend/app/components/shared/Filters/FilterSource/FilterSource.tsx index eed1e6e1d..07ca61ec3 100644 --- a/frontend/app/components/shared/Filters/FilterSource/FilterSource.tsx +++ b/frontend/app/components/shared/Filters/FilterSource/FilterSource.tsx @@ -1,7 +1,6 @@ import { FilterType } from 'App/types/filter/filterType'; import React, { useState, useEffect } from 'react'; import stl from './FilterSource.module.css'; -import { debounce } from 'App/utils'; import cn from 'classnames'; interface Props { @@ -10,17 +9,12 @@ interface Props { } function FilterSource(props: Props) { const { filter } = props; - const [value, setValue] = useState(filter.source[0] || ''); - const debounceUpdate: any = React.useCallback(debounce(props.onUpdate, 1000), [props.onUpdate]); + const [value, setValue] = useState(filter.source && filter.source[0] ? filter.source[0] : ''); useEffect(() => { - setValue(filter.source[0] || ''); + setValue(filter.source && filter.source[0] ? filter.source[0] : ''); }, [filter]); - useEffect(() => { - debounceUpdate({ ...filter, source: [value] }); - }, [value]); - const write = ({ target: { value, name } }: any) => setValue(value); const renderFiled = () => { diff --git a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx index 7daab8acb..8984899ea 100644 --- a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx +++ b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx @@ -49,9 +49,9 @@ function FilterValue(props: Props) { setDurationValues({ ...durationValues, ...newValues }); }; - const handleBlur = (e: any) => { + const handleBlur = () => { if (filter.type === FilterType.DURATION) { - const { maxDuration, minDuration, key } = filter; + const { maxDuration, minDuration } = filter; if (maxDuration || minDuration) return; if (maxDuration !== durationValues.maxDuration || minDuration !== durationValues.minDuration) { props.onUpdate({ ...filter, value: [durationValues.minDuration, durationValues.maxDuration] }); diff --git a/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.module.css b/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.module.css index 010042bf3..6e34010b3 100644 --- a/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.module.css +++ b/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.module.css @@ -6,6 +6,7 @@ align-items: center; height: 26px; width: 100%; + /* z-index: 3; TODO this has to be fixed in clickmaps @Nikita */ & .right { height: 24px; diff --git a/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.tsx b/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.tsx index 28e1d23a1..b51519b83 100644 --- a/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.tsx +++ b/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.tsx @@ -3,7 +3,6 @@ import { Icon } from 'UI'; import stl from './FilterValueDropdown.module.css'; import Select from 'Shared/Select'; - const dropdownStyles = { control: (provided: any) => { const obj = { @@ -15,7 +14,7 @@ const dropdownStyles = { minHeight: '26px', borderRadius: '3px', boxShadow: 'none !important', - } + }; return obj; }, valueContainer: (provided: any) => ({ @@ -24,7 +23,7 @@ const dropdownStyles = { width: 'fit-content', alignItems: 'center', height: '26px', - padding: '0 3px' + padding: '0 3px', }), // placeholder: (provided: any) => ({ // ...provided, @@ -39,14 +38,14 @@ const dropdownStyles = { whiteSpace: 'nowrap', }), menu: (provided: any, state: any) => ({ - ...provided, - top: 20, - left: 0, - minWidth: 'fit-content', - overflow: 'hidden', + ...provided, + top: 20, + left: 0, + minWidth: 'fit-content', + overflow: 'hidden', }), container: (provided: any) => ({ - ...provided, + ...provided, width: '100%', }), input: (provided: any) => ({ @@ -54,30 +53,29 @@ const dropdownStyles = { height: '22px', '& input:focus': { border: 'none !important', - } + }, }), - singleValue: (provided: any, state: { isDisabled: any; }) => { + singleValue: (provided: any, state: { isDisabled: any }) => { const opacity = state.isDisabled ? 0.5 : 1; const transition = 'opacity 300ms'; return { - ...provided, opacity, transition, + ...provided, + opacity, + transition, display: 'flex', alignItems: 'center', height: '20px', }; - } -} + }, +}; interface Props { - // filter: any; // event/filter - // options: any[]; - placeholder?: string + placeholder?: string; value: string; onChange: (value: any) => void; className?: string; options: any[]; search?: boolean; - // multiple?: boolean; showCloseButton?: boolean; showOrButton?: boolean; onRemoveValue?: () => void; @@ -85,34 +83,46 @@ interface Props { isMultilple?: boolean; } function FilterValueDropdown(props: Props) { - const { placeholder = 'Select', isMultilple = true, search = false, options, onChange, value, className = '', showCloseButton = true, showOrButton = true } = props; - // const options = [] + const { + placeholder = 'Select', + isMultilple = true, + search = false, + options, + onChange, + value, + showCloseButton = true, + showOrButton = true, + } = props; return ( <div className="relative flex items-center w-full"> - <div className={stl.wrapper}> + <div className={stl.wrapper}> <Select isSearchable={search} - // className={ cn(stl.operatorDropdown, className, "filterDropdown") } - options={ options } + options={options} name="issue_type" - defaultValue={ value } - onChange={ (value: any) => onChange(value.value) } + value={value ? options.find((item) => item.value === value) : null} + onChange={(value: any) => onChange(value.value)} placeholder={placeholder} styles={dropdownStyles} /> - <div - className={stl.right} - // onClick={showOrButton ? onRemoveValue : onAddValue} - > - { showCloseButton && <div onClick={props.onRemoveValue}><Icon name="close" size="12" /></div> } - { showOrButton && <div onClick={props.onAddValue} className="color-teal"><span className="px-1">or</span></div> } + <div className={stl.right}> + {showCloseButton && ( + <div onClick={props.onRemoveValue}> + <Icon name="close" size="12" /> + </div> + )} + {showOrButton && ( + <div onClick={props.onAddValue} className="color-teal"> + <span className="px-1">or</span> + </div> + )} </div> </div> - { !showOrButton && isMultilple && <div className="ml-3">or</div> } + {!showOrButton && isMultilple && <div className="ml-3">or</div>} </div> ); } -export default FilterValueDropdown; \ No newline at end of file +export default FilterValueDropdown; diff --git a/frontend/app/components/shared/Filters/LiveFilterModal/LiveFilterModal.tsx b/frontend/app/components/shared/Filters/LiveFilterModal/LiveFilterModal.tsx index db7913093..0716d3dd6 100644 --- a/frontend/app/components/shared/Filters/LiveFilterModal/LiveFilterModal.tsx +++ b/frontend/app/components/shared/Filters/LiveFilterModal/LiveFilterModal.tsx @@ -39,7 +39,6 @@ function LiveFilterModal(props: Props) { const isResultEmpty = (!filterSearchList || Object.keys(filterSearchList).filter(i => filtersMap[i].isLive).length === 0) && matchingCategories.length === 0 && matchingFilters.length === 0 - getMatchingEntries return ( <div className={stl.wrapper} style={{ width: '490px', maxHeight: '400px', overflowY: 'auto'}}> <div className=""> diff --git a/frontend/app/components/shared/GraphQLDetailsModal/GraphQLDetailsModal.tsx b/frontend/app/components/shared/GraphQLDetailsModal/GraphQLDetailsModal.tsx index d64a91b5d..ad4d91be9 100644 --- a/frontend/app/components/shared/GraphQLDetailsModal/GraphQLDetailsModal.tsx +++ b/frontend/app/components/shared/GraphQLDetailsModal/GraphQLDetailsModal.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { JSONTree, Button } from 'UI'; +import { JSONTree } from 'UI'; import cn from 'classnames'; interface Props { diff --git a/frontend/app/components/shared/GuidePopup/GuidePopup.tsx b/frontend/app/components/shared/GuidePopup/GuidePopup.tsx index 52739144d..336eb25d5 100644 --- a/frontend/app/components/shared/GuidePopup/GuidePopup.tsx +++ b/frontend/app/components/shared/GuidePopup/GuidePopup.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; -import { Controls as Player } from 'Player'; import { Tooltip } from 'UI'; import { INDEXES } from 'App/constants/zindex'; +import { PlayerContext } from 'App/components/Session/playerContext'; export const FEATURE_KEYS = { XRAY: 'featureViewed', @@ -16,6 +16,8 @@ interface IProps { } export default function GuidePopup({ children, title, description }: IProps) { + const { player: Player } = React.useContext(PlayerContext) + const [showGuide, setShowGuide] = useState(!localStorage.getItem(FEATURE_KEYS.NOTES)); useEffect(() => { if (!showGuide) { diff --git a/frontend/app/components/shared/Insights/SankeyChart/SankeyChart.stories.tsx b/frontend/app/components/shared/Insights/SankeyChart/SankeyChart.stories.tsx new file mode 100644 index 000000000..dfb9949f8 --- /dev/null +++ b/frontend/app/components/shared/Insights/SankeyChart/SankeyChart.stories.tsx @@ -0,0 +1,28 @@ +import SankeyChart, { SankeyChartData } from './SankeyChart'; +import React from 'react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; + +const data: SankeyChartData = { + nodes: [ + { name: 'Home Page' }, + { name: 'Dashboard' }, + { name: 'Preferences' }, + { name: 'Billing' }, + ], + links: [ + { source: 0, target: 1, value: 100 }, + { source: 1, target: 2, value: 50 }, + { source: 1, target: 3, value: 50 }, + { source: 2, target: 3, value: 10 }, + ], +}; + +export default { + title: 'Dashboad/Cards/SankeyChart', + component: SankeyChart, +} as ComponentMeta<typeof SankeyChart>; + +const Template: ComponentStory<typeof SankeyChart> = (args: any) => <SankeyChart {...args} />; + +export const Simple = Template.bind({}); +Simple.args = { data }; diff --git a/frontend/app/components/shared/Insights/SankeyChart/SankeyChart.tsx b/frontend/app/components/shared/Insights/SankeyChart/SankeyChart.tsx new file mode 100644 index 000000000..511ca12e8 --- /dev/null +++ b/frontend/app/components/shared/Insights/SankeyChart/SankeyChart.tsx @@ -0,0 +1,134 @@ +import React from 'react'; +import { Sankey, Tooltip, Rectangle, Layer, ResponsiveContainer } from 'recharts'; + +type Node = { + name: string; +} + +type Link = { + source: number; + target: number; + value: number; +} + +export interface SankeyChartData { + links: Link[]; + nodes: Node[]; +} +interface Props { + data: SankeyChartData; + nodePadding?: number; + nodeWidth?: number; +} +function SankeyChart(props: Props) { + const { data, nodePadding = 50, nodeWidth = 10 } = props; + return ( + <div className="rounded border shadow"> + <div className="text-lg p-3 border-b bg-gray-lightest">Sankey Chart</div> + <div className=""> + <ResponsiveContainer height={500} width="100%"> + <Sankey + width={960} + height={500} + data={data} + // node={{ stroke: '#77c878', strokeWidth: 0 }} + node={<CustomNodeComponent />} + nodePadding={nodePadding} + nodeWidth={nodeWidth} + margin={{ + left: 10, + right: 100, + top: 10, + bottom: 10, + }} + link={<CustomLinkComponent />} + > + <defs> + <linearGradient id={'linkGradient'}> + <stop offset="0%" stopColor="rgba(0, 136, 254, 0.5)" /> + <stop offset="100%" stopColor="rgba(0, 197, 159, 0.3)" /> + </linearGradient> + </defs> + <Tooltip content={<CustomTooltip />} /> + </Sankey> + </ResponsiveContainer> + </div> + </div> + ); +} + +export default SankeyChart; + +const CustomTooltip = (props: any) => { + return <div className="rounded bg-white border p-0 px-1 text-sm">test</div>; + // if (active && payload && payload.length) { + // return ( + // <div className="custom-tooltip"> + // <p className="label">{`${label} : ${payload[0].value}`}</p> + // <p className="intro">{getIntroOfPage(label)}</p> + // <p className="desc">Anything you want can be displayed here.</p> + // </div> + // ); + // } + + return null; +}; + +function CustomNodeComponent({ x, y, width, height, index, payload, containerWidth }: any) { + const isOut = x + width + 6 > containerWidth; + return ( + <Layer key={`CustomNode${index}`}> + <Rectangle x={x} y={y} width={width} height={height} fill="#5192ca" fillOpacity="1" /> + <text + textAnchor={isOut ? 'end' : 'start'} + x={isOut ? x - 6 : x + width + 6} + y={y + height / 2} + fontSize="8" + // stroke="#333" + > + {payload.name} + </text> + <text + textAnchor={isOut ? 'end' : 'start'} + x={isOut ? x - 6 : x + width + 6} + y={y + height / 2 + 13} + fontSize="12" + // stroke="#333" + // strokeOpacity="0.5" + > + {payload.value + 'k'} + </text> + </Layer> + ); +} + +const CustomLinkComponent = (props: any) => { + const [fill, setFill] = React.useState('url(#linkGradient)'); + const { sourceX, targetX, sourceY, targetY, sourceControlX, targetControlX, linkWidth, index } = + props; + return ( + <Layer key={`CustomLink${index}`}> + <path + d={` + M${sourceX},${sourceY + linkWidth / 2} + C${sourceControlX},${sourceY + linkWidth / 2} + ${targetControlX},${targetY + linkWidth / 2} + ${targetX},${targetY + linkWidth / 2} + L${targetX},${targetY - linkWidth / 2} + C${targetControlX},${targetY - linkWidth / 2} + ${sourceControlX},${sourceY - linkWidth / 2} + ${sourceX},${sourceY - linkWidth / 2} + Z + `} + fill={fill} + strokeWidth="0" + onMouseEnter={() => { + setFill('rgba(0, 136, 254, 0.5)'); + }} + onMouseLeave={() => { + setFill('url(#linkGradient)'); + }} + /> + </Layer> + ); +}; diff --git a/frontend/app/components/shared/Insights/SankeyChart/index.ts b/frontend/app/components/shared/Insights/SankeyChart/index.ts new file mode 100644 index 000000000..d651de3d0 --- /dev/null +++ b/frontend/app/components/shared/Insights/SankeyChart/index.ts @@ -0,0 +1 @@ +export { default } from './SankeyChart'; diff --git a/frontend/app/components/shared/Insights/ScatterChart/ScatterChart.stories.tsx b/frontend/app/components/shared/Insights/ScatterChart/ScatterChart.stories.tsx new file mode 100644 index 000000000..a56381cf7 --- /dev/null +++ b/frontend/app/components/shared/Insights/ScatterChart/ScatterChart.stories.tsx @@ -0,0 +1,23 @@ +import { storiesOf } from '@storybook/react'; +import ScatterChart from './ScatterChart'; + +const data01 = [ + { x: 100, y: 200, z: 200 }, + { x: 120, y: 100, z: 260 }, + { x: 170, y: 300, z: 400 }, + { x: 140, y: 250, z: 280 }, + { x: 150, y: 400, z: 500 }, + { x: 110, y: 280, z: 200 }, +]; +const data02 = [ + { x: 200, y: 260, z: 240 }, + { x: 240, y: 290, z: 220 }, + { x: 190, y: 290, z: 250 }, + { x: 198, y: 250, z: 210 }, + { x: 180, y: 280, z: 260 }, + { x: 210, y: 220, z: 230 }, +]; + +storiesOf('ScatterChart', module).add('Pure', () => ( + <ScatterChart dataFirst={data01} dataSecond={data02} /> +)); diff --git a/frontend/app/components/shared/Insights/ScatterChart/ScatterChart.tsx b/frontend/app/components/shared/Insights/ScatterChart/ScatterChart.tsx new file mode 100644 index 000000000..3d0566b4e --- /dev/null +++ b/frontend/app/components/shared/Insights/ScatterChart/ScatterChart.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { + ScatterChart, + Scatter, + Tooltip, + CartesianGrid, + XAxis, + YAxis, + ZAxis, + Legend, + ResponsiveContainer, +} from 'recharts'; + +interface Props { + dataFirst: any; + dataSecond: any; +} +function ScatterChartComponent(props: Props) { + const { dataFirst, dataSecond } = props; + return ( + <div className="rounded border shadow"> + <div className="text-lg p-3 border-b bg-gray-lightest">Scatter Chart</div> + <div className=""> + <ResponsiveContainer height={500} width="100%"> + <ScatterChart + width={730} + height={250} + margin={{ top: 20, right: 20, bottom: 10, left: 10 }} + > + <CartesianGrid strokeDasharray="3 3" /> + <XAxis dataKey="x" name="stature" unit="cm" /> + <YAxis dataKey="y" name="weight" unit="kg" /> + <ZAxis dataKey="z" range={[64, 144]} name="score" unit="km" /> + <Tooltip cursor={{ strokeDasharray: '3 3' }} /> + <Legend /> + <Scatter name="A school" data={dataFirst} fill="#8884d8" /> + <Scatter name="B school" data={dataSecond} fill="#82ca9d" /> + </ScatterChart> + </ResponsiveContainer> + </div> + </div> + ); +} + +export default ScatterChartComponent; diff --git a/frontend/app/components/shared/Insights/ScatterChart/index.ts b/frontend/app/components/shared/Insights/ScatterChart/index.ts new file mode 100644 index 000000000..26b894075 --- /dev/null +++ b/frontend/app/components/shared/Insights/ScatterChart/index.ts @@ -0,0 +1 @@ +export { default } from './ScatterChart'; diff --git a/frontend/app/components/shared/IntegrateSlackButton/IntegrateSlackButton.js b/frontend/app/components/shared/IntegrateSlackButton/IntegrateSlackButton.js index e75c4f7f5..7d1b0946e 100644 --- a/frontend/app/components/shared/IntegrateSlackButton/IntegrateSlackButton.js +++ b/frontend/app/components/shared/IntegrateSlackButton/IntegrateSlackButton.js @@ -1,26 +1,30 @@ import React from 'react' import { connect } from 'react-redux' -import { IconButton } from 'UI' +import { Button, Icon } from 'UI' import { CLIENT_TABS, client as clientRoute } from 'App/routes'; import { withRouter } from 'react-router-dom'; -function IntegrateSlackButton({ history, tenantId }) { +function IntegrateSlackTeamsButton({ history, tenantId }) { const gotoPreferencesIntegrations = () => { history.push(clientRoute(CLIENT_TABS.INTEGRATIONS)); } return ( <div> - <IconButton - className="my-auto mt-2 mb-2" - icon="integrations/slack" - label="Integrate Slack" + <Button + className="my-auto mt-2 mb-2 flex items-center gap-2" onClick={gotoPreferencesIntegrations} - /> + variant="text-primary" + > + <Icon name="integrations/slack" size={16} /> + <Icon name="integrations/teams" size={24} className="mr-2" /> + + <span>Integrate Slack or MS Teams</span> + </Button> </div> ) } export default withRouter(connect(state => ({ tenantId: state.getIn([ 'user', 'account', 'tenantId' ]), -}))(IntegrateSlackButton)) +}))(IntegrateSlackTeamsButton)) diff --git a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx index c4d0d7f78..11987d55b 100644 --- a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx +++ b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx @@ -1,4 +1,4 @@ -import React, { Fragment, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { connect } from 'react-redux'; import { NoContent, Loader, Pagination, Button } from 'UI'; import { List } from 'immutable'; @@ -118,7 +118,7 @@ function LiveSessionList(props: Props) { <NoContent title={ <div className="flex items-center justify-center flex-col"> - <AnimatedSVG name={ICONS.NO_LIVE_SESSIONS} size={170} /> + <AnimatedSVG name={ICONS.NO_LIVE_SESSIONS} size={180} /> <div className="mt-2" /> <div className="text-center text-gray-600">No live sessions found.</div> </div> @@ -126,8 +126,8 @@ function LiveSessionList(props: Props) { subtext={ <div className="text-center flex justify-center items-center flex-col"> <span> - Assist allows you to support your users through live screen viewing and - audio/video calls.{' '} + Assist is the best way to support you users while they're browsing your site, + {' '}through live replay, co-browsing and video conferencing capabilities. Learn More.{' '} <a target="_blank" className="link" diff --git a/frontend/app/components/shared/LiveSessionSearchField/LiveSessionSearchField.tsx b/frontend/app/components/shared/LiveSessionSearchField/LiveSessionSearchField.tsx index f9ea8d51c..08dd17866 100644 --- a/frontend/app/components/shared/LiveSessionSearchField/LiveSessionSearchField.tsx +++ b/frontend/app/components/shared/LiveSessionSearchField/LiveSessionSearchField.tsx @@ -35,7 +35,6 @@ function LiveSessionSearchField(props: Props) { onBlur={ () => setTimeout(setShowModal, 200, false) } onChange={ onSearchChange } icon="search" - iconPosition="left" placeholder={ 'Find live sessions by user or metadata.'} fluid id="search" @@ -56,4 +55,4 @@ function LiveSessionSearchField(props: Props) { ); } -export default connect(null, { fetchFilterSearch, editFilter, addFilterByKeyAndValue })(LiveSessionSearchField); \ No newline at end of file +export default connect(null, { fetchFilterSearch, editFilter, addFilterByKeyAndValue })(LiveSessionSearchField); diff --git a/frontend/app/components/shared/NoSessionsMessage/NoSessionsMessage.js b/frontend/app/components/shared/NoSessionsMessage/NoSessionsMessage.js index 79dc8e436..d8158f598 100644 --- a/frontend/app/components/shared/NoSessionsMessage/NoSessionsMessage.js +++ b/frontend/app/components/shared/NoSessionsMessage/NoSessionsMessage.js @@ -29,7 +29,7 @@ const NoSessionsMessage = (props) => { </div> <div className="ml-2 color-gray-darkest mr-auto text-base"> It might take a few minutes for first recording to appear. - <a href="https://docs.openreplay.com/troubleshooting" className="link ml-2"> + <a href="https://docs.openreplay.com/en/troubleshooting/session-recordings/" className="link ml-2"> Troubleshoot </a> . diff --git a/frontend/app/components/shared/SavedSearch/SavedSearch.tsx b/frontend/app/components/shared/SavedSearch/SavedSearch.tsx index 92b6585bc..e9d299292 100644 --- a/frontend/app/components/shared/SavedSearch/SavedSearch.tsx +++ b/frontend/app/components/shared/SavedSearch/SavedSearch.tsx @@ -25,7 +25,7 @@ function SavedSearch(props: Props) { <div className={cn("flex items-center", { [stl.disabled] : list.size === 0})}> <Button variant="outline" - onClick={() => showModal(<SavedSearchModal />, { right: true })} + onClick={() => showModal(<SavedSearchModal />, { right: true, width: 450 })} > <span className="mr-1">Saved Search</span> <span className="font-bold mr-2">{list.size}</span> diff --git a/frontend/app/components/shared/SavedSearch/components/SavedSearchModal/SavedSearchModal.tsx b/frontend/app/components/shared/SavedSearch/components/SavedSearchModal/SavedSearchModal.tsx index 02fdd236e..fe8ac1ade 100644 --- a/frontend/app/components/shared/SavedSearch/components/SavedSearchModal/SavedSearchModal.tsx +++ b/frontend/app/components/shared/SavedSearch/components/SavedSearchModal/SavedSearchModal.tsx @@ -59,10 +59,10 @@ function SavedSearchModal(props: Props) { setTimeout(() => setshowModal(true), 0); }; - const shownItems = props.list.filter((item) => item.name.includes(filterQuery)); + const shownItems = props.list.filter((item) => item.name.toLocaleLowerCase().includes(filterQuery.toLocaleLowerCase())); return ( - <div className="bg-white box-shadow h-screen" style={{ width: '450px' }}> + <div className="bg-white box-shadow h-screen"> <div className="p-6"> <h1 className="text-2xl"> Saved Search <span className="color-gray-medium">{props.list.size}</span> diff --git a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx index e7d8c8831..627956675 100644 --- a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx +++ b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { DATE_RANGE_OPTIONS, CUSTOM_RANGE } from 'App/dateRange'; import Select from 'Shared/Select'; -import Period, { LAST_7_DAYS } from 'Types/app/period'; +import Period from 'Types/app/period'; import { components } from 'react-select'; import DateRangePopup from 'Shared/DateRangeDropdown/DateRangePopup'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; diff --git a/frontend/app/components/shared/SessionItem/SessionItem.tsx b/frontend/app/components/shared/SessionItem/SessionItem.tsx index 0288461fa..1c9d756a3 100644 --- a/frontend/app/components/shared/SessionItem/SessionItem.tsx +++ b/frontend/app/components/shared/SessionItem/SessionItem.tsx @@ -12,200 +12,253 @@ import PlayLink from './PlayLink'; import ErrorBars from './ErrorBars'; import { assist as assistRoute, liveSession, sessions as sessionsRoute, isRoute } from 'App/routes'; import { capitalize } from 'App/utils'; +import { Duration } from 'luxon'; const ASSIST_ROUTE = assistRoute(); const ASSIST_LIVE_SESSION = liveSession(); const SESSIONS_ROUTE = sessionsRoute(); interface Props { - session: { - sessionId: string; - userBrowser: string; - userOs: string; - userId: string; - userAnonymousId: string; - userDisplayName: string; - userCountry: string; - startedAt: number; - duration: string; - eventsCount: number; - errorsCount: number; - pagesCount: number; - viewed: boolean; - favorite: boolean; - userDeviceType: string; - userUuid: string; - userNumericHash: number; - live: boolean; - metadata: Record<string, any>; - userSessionsCount: number; - issueTypes: []; - active: boolean; - isCallActive?: boolean; - agentIds?: string[]; - }; - onUserClick?: (userId: string, userAnonymousId: string) => void; - hasUserFilter?: boolean; - disableUser?: boolean; - metaList?: Array<any>; - // showActive?: boolean; - lastPlayedSessionId?: string; - live?: boolean; - onClick?: any; - compact?: boolean; + session: { + sessionId: string; + userBrowser: string; + userOs: string; + userId: string; + userAnonymousId: string; + userDisplayName: string; + userCountry: string; + startedAt: number; + duration: Duration; + eventsCount: number; + errorsCount: number; + pagesCount: number; + viewed: boolean; + favorite: boolean; + userDeviceType: string; + userUuid: string; + userNumericHash: number; + live: boolean; + metadata: Record<string, any>; + issueTypes: []; + active: boolean; + isCallActive?: boolean; + agentIds?: string[]; + }; + onUserClick?: (userId: string, userAnonymousId: string) => void; + hasUserFilter?: boolean; + disableUser?: boolean; + metaList?: Array<any>; + // showActive?: boolean; + lastPlayedSessionId?: string; + live?: boolean; + onClick?: any; + compact?: boolean; + isDisabled?: boolean; + isAdd?: boolean; } function SessionItem(props: RouteComponentProps & Props) { - const { settingsStore } = useStore(); - const { timezone } = settingsStore.sessionSettings; + const { settingsStore } = useStore(); + const { timezone } = settingsStore.sessionSettings; - const { - session, - onUserClick = () => null, - hasUserFilter = false, - disableUser = false, - metaList = [], - lastPlayedSessionId, - onClick = null, - compact = false, - } = props; + const { + session, + onUserClick = () => null, + hasUserFilter = false, + disableUser = false, + metaList = [], + lastPlayedSessionId, + onClick = null, + compact = false, + } = props; - const { - sessionId, - userBrowser, - userOs, - userId, - userAnonymousId, - userDisplayName, - userCountry, - startedAt, - duration, - eventsCount, - viewed, - userDeviceType, - userNumericHash, - live, - metadata, - issueTypes, - active, - } = session; + const { + sessionId, + userBrowser, + userOs, + userId, + userAnonymousId, + userDisplayName, + userCountry, + startedAt, + duration, + eventsCount, + viewed, + userDeviceType, + userNumericHash, + live, + metadata, + issueTypes, + active, + } = session; - const location = props.location; - const queryParams = Object.fromEntries(new URLSearchParams(location.search)); + const location = props.location; + const queryParams = Object.fromEntries(new URLSearchParams(location.search)); - const formattedDuration = durationFormatted(duration); - const hasUserId = userId || userAnonymousId; - const isSessions = isRoute(SESSIONS_ROUTE, location.pathname); - const isAssist = isRoute(ASSIST_ROUTE, location.pathname) || isRoute(ASSIST_LIVE_SESSION, location.pathname); - const isLastPlayed = lastPlayedSessionId === sessionId; + const formattedDuration = durationFormatted(duration); + const hasUserId = userId || userAnonymousId; + const isSessions = isRoute(SESSIONS_ROUTE, location.pathname); + const isAssist = + isRoute(ASSIST_ROUTE, location.pathname) || + isRoute(ASSIST_LIVE_SESSION, location.pathname) || + location.pathname.includes('multiview'); + const isLastPlayed = lastPlayedSessionId === sessionId; - const _metaList = Object.keys(metadata) - .filter((i) => metaList.includes(i)) - .map((key) => { - const value = metadata[key]; - return { label: key, value }; - }); + const _metaList = Object.keys(metadata) + .filter((i) => metaList.includes(i)) + .map((key) => { + const value = metadata[key]; + return { label: key, value }; + }); - return ( - <div className={cn(stl.sessionItem, 'flex flex-col py-2 px-4')} id="session-item" onClick={(e) => e.stopPropagation()}> - <div className="flex items-start"> - <div className={cn('flex items-center w-full')}> - {!compact && ( - <div className="flex items-center pr-2 shrink-0" style={{ width: '40%' }}> - <div> - <Avatar isActive={active} seed={userNumericHash} isAssist={isAssist} /> - </div> - <div className="flex flex-col overflow-hidden color-gray-medium ml-3 justify-between items-center shrink-0"> - <div - className={cn('text-lg', { - 'color-teal cursor-pointer': !disableUser && hasUserId, - [stl.userName]: !disableUser && hasUserId, - 'color-gray-medium': disableUser || !hasUserId, - })} - onClick={() => !disableUser && !hasUserFilter && hasUserId ? onUserClick(userId, userAnonymousId) : null} - > - <TextEllipsis text={userDisplayName} maxWidth="200px" popupProps={{ inverted: true, size: 'tiny' }} /> - </div> - </div> - </div> - )} - <div style={{ width: compact ? '40%' : '20%' }} className="px-2 flex flex-col justify-between"> - <div> - <Tooltip - delay={0} - title={`${formatTimeOrDate(startedAt, timezone, true)} ${timezone.label}`} - className="w-fit !block" - > - <TextEllipsis text={formatTimeOrDate(startedAt, timezone)} popupProps={{ inverted: true, size: 'tiny' }} /> - </Tooltip> - </div> - <div className="flex items-center color-gray-medium py-1"> - {!isAssist && ( - <> - <div className="color-gray-medium"> - <span className="mr-1">{eventsCount}</span> - <span>{eventsCount === 0 || eventsCount > 1 ? 'Events' : 'Event'}</span> - </div> - <Icon name="circle-fill" size={3} className="mx-4" /> - </> - )} - <div>{live ? <Counter startTime={startedAt} /> : formattedDuration}</div> - </div> - </div> - <div style={{ width: '30%' }} className="px-2 flex flex-col justify-between"> - <div style={{ height: '21px' }}> - <CountryFlag country={userCountry} style={{ paddingTop: '4px' }} label /> - </div> - <div className="color-gray-medium flex items-center py-1"> - <span className="capitalize" style={{ maxWidth: '70px' }}> - <TextEllipsis text={capitalize(userBrowser)} popupProps={{ inverted: true, size: 'tiny' }} /> - </span> - <Icon name="circle-fill" size={3} className="mx-4" /> - <span className="capitalize" style={{ maxWidth: '70px' }}> - <TextEllipsis text={capitalize(userOs)} popupProps={{ inverted: true, size: 'tiny' }} /> - </span> - <Icon name="circle-fill" size={3} className="mx-4" /> - <span className="capitalize" style={{ maxWidth: '70px' }}> - <TextEllipsis text={capitalize(userDeviceType)} popupProps={{ inverted: true, size: 'tiny' }} /> - </span> - </div> - </div> - {isSessions && ( - <div style={{ width: '10%' }} className="self-center px-2 flex items-center"> - <ErrorBars count={issueTypes.length} /> - </div> - )} - </div> - - <div className="flex items-center"> - <div className={stl.playLink} id="play-button" data-viewed={viewed}> - {live && session.isCallActive && session.agentIds.length > 0 ? ( - <div className="mr-4"> - <Label className="bg-gray-lightest p-1 px-2 rounded-lg"> - <span className="color-gray-medium text-xs" style={{ whiteSpace: 'nowrap' }}> - CALL IN PROGRESS - </span> - </Label> - </div> - ) : null} - {isSessions && ( - <div className="mr-4 flex-shrink-0 w-24"> - {isLastPlayed && ( - <Label className="bg-gray-lightest p-1 px-2 rounded-lg"> - <span className="color-gray-medium text-xs" style={{ whiteSpace: 'nowrap' }}> - LAST PLAYED - </span> - </Label> - )} - </div> - )} - <PlayLink isAssist={isAssist} sessionId={sessionId} viewed={viewed} onClick={onClick} queryParams={queryParams} /> - </div> + return ( + <div + className={cn(stl.sessionItem, 'flex flex-col py-2 px-4')} + id="session-item" + onClick={(e) => e.stopPropagation()} + > + <div className="flex items-start"> + <div className={cn('flex items-center w-full')}> + {!compact && ( + <div className="flex items-center pr-2 shrink-0" style={{ width: '40%' }}> + <div> + <Avatar isActive={active} seed={userNumericHash} isAssist={isAssist} /> + </div> + <div className="flex flex-col overflow-hidden color-gray-medium ml-3 justify-between items-center shrink-0"> + <div + className={cn('text-lg', { + 'color-teal cursor-pointer': !disableUser && hasUserId, + [stl.userName]: !disableUser && hasUserId, + 'color-gray-medium': disableUser || !hasUserId, + })} + onClick={() => + !disableUser && !hasUserFilter && hasUserId + ? onUserClick(userId, userAnonymousId) + : null + } + > + <TextEllipsis + text={userDisplayName} + maxWidth="200px" + popupProps={{ inverted: true, size: 'tiny' }} + /> </div> + </div> </div> - {_metaList.length > 0 && <SessionMetaList className="mt-4" metaList={_metaList} />} + )} + <div + style={{ width: compact ? '40%' : '20%' }} + className="px-2 flex flex-col justify-between" + > + <div> + <Tooltip + delay={0} + title={`${formatTimeOrDate(startedAt, timezone, true)} ${timezone.label}`} + className="w-fit !block" + > + <TextEllipsis + text={formatTimeOrDate(startedAt, timezone)} + popupProps={{ inverted: true, size: 'tiny' }} + /> + </Tooltip> + </div> + <div className="flex items-center color-gray-medium py-1"> + {!isAssist && ( + <> + <div className="color-gray-medium"> + <span className="mr-1">{eventsCount}</span> + <span>{eventsCount === 0 || eventsCount > 1 ? 'Events' : 'Event'}</span> + </div> + <Icon name="circle-fill" size={3} className="mx-4" /> + </> + )} + <div>{live ? <Counter startTime={startedAt} /> : formattedDuration}</div> + </div> + </div> + <div style={{ width: '30%' }} className="px-2 flex flex-col justify-between"> + <div style={{ height: '21px' }}> + <CountryFlag country={userCountry} style={{ paddingTop: '4px' }} label /> + </div> + <div className="color-gray-medium flex items-center py-1"> + <span className="capitalize" style={{ maxWidth: '70px' }}> + <TextEllipsis + text={capitalize(userBrowser)} + popupProps={{ inverted: true, size: 'tiny' }} + /> + </span> + <Icon name="circle-fill" size={3} className="mx-4" /> + <span className="capitalize" style={{ maxWidth: '70px' }}> + <TextEllipsis + text={capitalize(userOs)} + popupProps={{ inverted: true, size: 'tiny' }} + /> + </span> + <Icon name="circle-fill" size={3} className="mx-4" /> + <span className="capitalize" style={{ maxWidth: '70px' }}> + <TextEllipsis + text={capitalize(userDeviceType)} + popupProps={{ inverted: true, size: 'tiny' }} + /> + </span> + </div> + </div> + {isSessions && ( + <div style={{ width: '10%' }} className="self-center px-2 flex items-center"> + <ErrorBars count={issueTypes.length} /> + </div> + )} </div> - ); + + <div className="flex items-center"> + <div + className={cn(stl.playLink, props.isDisabled ? 'cursor-not-allowed' : 'cursor-pointer')} + id="play-button" + data-viewed={viewed} + > + {live && session.isCallActive && session.agentIds!.length > 0 ? ( + <div className="mr-4"> + <Label className="bg-gray-lightest p-1 px-2 rounded-lg"> + <span className="color-gray-medium text-xs" style={{ whiteSpace: 'nowrap' }}> + CALL IN PROGRESS + </span> + </Label> + </div> + ) : null} + {isSessions && ( + <div className="mr-4 flex-shrink-0 w-24"> + {isLastPlayed && ( + <Label className="bg-gray-lightest p-1 px-2 rounded-lg"> + <span className="color-gray-medium text-xs" style={{ whiteSpace: 'nowrap' }}> + LAST PLAYED + </span> + </Label> + )} + </div> + )} + {props.isAdd ? ( + <div + className="rounded-full border-tealx p-2 border" + onClick={() => (props.isDisabled ? null : props.onClick())} + > + <div className="bg-tealx rounded-full p-2"> + <Icon name="plus" size={16} color="white" /> + </div> + </div> + ) : ( + <PlayLink + isAssist={isAssist} + sessionId={sessionId} + viewed={viewed} + onClick={onClick} + queryParams={queryParams} + /> + )} + </div> + </div> + </div> + {_metaList.length > 0 && <SessionMetaList className="mt-4" metaList={_metaList} />} + </div> + ); } export default withRouter(observer(SessionItem)); diff --git a/frontend/app/components/shared/SessionItem/SessionMetaList/SessionMetaList.tsx b/frontend/app/components/shared/SessionItem/SessionMetaList/SessionMetaList.tsx index 236dde8ea..1ed439fef 100644 --- a/frontend/app/components/shared/SessionItem/SessionMetaList/SessionMetaList.tsx +++ b/frontend/app/components/shared/SessionItem/SessionMetaList/SessionMetaList.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { Popup } from 'UI'; import cn from 'classnames'; import MetaItem from '../MetaItem'; import MetaMoreButton from '../MetaMoreButton'; @@ -16,7 +15,9 @@ export default function SessionMetaList(props: Props) { return ( <div className={cn('text-sm flex items-center', className)}> {metaList.slice(0, maxLength).map(({ label, value }, index) => ( - <MetaItem key={index} label={label} value={'' + value} className="mr-3" /> + <React.Fragment key={index}> + <MetaItem label={label} value={'' + value} className="mr-3" /> + </React.Fragment> ))} {metaList.length > maxLength && <MetaMoreButton list={metaList} maxLength={maxLength} />} diff --git a/frontend/app/components/shared/SessionItem/sessionItem.module.css b/frontend/app/components/shared/SessionItem/sessionItem.module.css index 34ed53ad9..a7503984a 100644 --- a/frontend/app/components/shared/SessionItem/sessionItem.module.css +++ b/frontend/app/components/shared/SessionItem/sessionItem.module.css @@ -81,7 +81,6 @@ display: flex; align-items: center; transition: all 0.2s; - cursor: pointer; &[data-viewed=true] { opacity: 1; } diff --git a/frontend/app/components/shared/SessionListContainer/SessionListContainer.tsx b/frontend/app/components/shared/SessionListContainer/SessionListContainer.tsx index 6a863e69b..67b1e12e7 100644 --- a/frontend/app/components/shared/SessionListContainer/SessionListContainer.tsx +++ b/frontend/app/components/shared/SessionListContainer/SessionListContainer.tsx @@ -15,9 +15,6 @@ function SessionListContainer({ fetchMembers: () => void; members: object[]; }) { - React.useEffect(() => { - fetchMembers(); - }, []); return ( <div className="widget-wrapper"> <SessionHeader /> diff --git a/frontend/app/components/shared/SessionListContainer/components/Notes/NoteItem.tsx b/frontend/app/components/shared/SessionListContainer/components/Notes/NoteItem.tsx index d445e926c..05113f523 100644 --- a/frontend/app/components/shared/SessionListContainer/components/Notes/NoteItem.tsx +++ b/frontend/app/components/shared/SessionListContainer/components/Notes/NoteItem.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Icon, Link } from 'UI'; +import { Link } from 'UI'; import PlayLink from 'Shared/SessionItem/PlayLink'; import { tagProps, Note } from 'App/services/NotesService'; import { formatTimeOrDate } from 'App/date'; @@ -13,7 +13,6 @@ import TeamBadge from './TeamBadge'; interface Props { note: Note; - userEmail: string; } function NoteItem(props: Props) { @@ -69,7 +68,7 @@ function NoteItem(props: Props) { ) : null} <div className="text-disabled-text flex items-center text-sm"> <span className="color-gray-darkest mr-1">By </span> - {props.userEmail},{' '} + {props.note.userName},{' '} {formatTimeOrDate(props.note.createdAt as unknown as number, timezone)} <div className="mx-2" /> {!props.note.isPublic ? null : <TeamBadge />} diff --git a/frontend/app/components/shared/SessionListContainer/components/Notes/NoteList.tsx b/frontend/app/components/shared/SessionListContainer/components/Notes/NoteList.tsx index 8b9c056ed..3c48d2af0 100644 --- a/frontend/app/components/shared/SessionListContainer/components/Notes/NoteList.tsx +++ b/frontend/app/components/shared/SessionListContainer/components/Notes/NoteList.tsx @@ -1,9 +1,10 @@ import React from 'react'; -import { NoContent, Pagination, Loader, Icon } from 'UI'; +import { NoContent, Pagination, Loader } from 'UI'; import { sliceListPerPage } from 'App/utils'; import NoteItem from './NoteItem'; import { observer } from 'mobx-react-lite'; import { useStore } from 'App/mstore'; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; function NotesList({ members }: { members: Array<Record<string, any>> }) { const { notesStore } = useStore(); @@ -20,18 +21,21 @@ function NotesList({ members }: { members: Array<Record<string, any>> }) { show={list.length === 0} title={ <div className="flex flex-col items-center justify-center"> - <Icon name="no-dashboard" size={80} color="figmaColors-accent-secondary" /> - <div className="text-center text-gray-600 my-4">No notes yet</div> + {/* <Icon name="no-dashboard" size={80} color="figmaColors-accent-secondary" /> */} + <AnimatedSVG name={ICONS.NO_NOTES} size={180} /> + <div className="text-center text-gray-600 mt-4">No notes yet.</div> + </div> + } + subtext={ + <div className="text-center flex justify-center items-center flex-col"> + Create notes when replaying sessions and share your insights with the team. </div> } > <div className="border-b rounded bg-white"> {sliceListPerPage(list, notesStore.page - 1, notesStore.pageSize).map((note) => ( <React.Fragment key={note.noteId}> - <NoteItem - note={note} - userEmail={members.find((m) => m.id === note.userId)?.email || note.userId} - /> + <NoteItem note={note} /> </React.Fragment> ))} </div> diff --git a/frontend/app/components/shared/SessionListContainer/components/Notes/NoteTags.tsx b/frontend/app/components/shared/SessionListContainer/components/Notes/NoteTags.tsx index 26e57cc58..2831824b3 100644 --- a/frontend/app/components/shared/SessionListContainer/components/Notes/NoteTags.tsx +++ b/frontend/app/components/shared/SessionListContainer/components/Notes/NoteTags.tsx @@ -10,20 +10,22 @@ const sortOptionsMap = { 'createdAt-ASC': 'Oldest', }; const sortOptions = Object.entries(sortOptionsMap).map(([value, label]) => ({ value, label })); -const notesOwner = [{ value: '0', label: 'All Notes'},{ value: '1', label: 'My Notes'}] +const notesOwner = [ + { value: '0', label: 'All Notes' }, + { value: '1', label: 'My Notes' }, +]; function NoteTags() { - const { notesStore } = useStore() - + const { notesStore } = useStore(); return ( <div className="flex items-center"> - <div> - <TagItem - onClick={() => notesStore.toggleTag()} - label="ALL" - isActive={notesStore.activeTags.length === 0} - /> - </div> + <div> + <TagItem + onClick={() => notesStore.toggleTag()} + label="ALL" + isActive={notesStore.activeTags.length === 0} + /> + </div> {TAGS.map((tag: iTag) => ( <div key={tag}> <TagItem @@ -34,9 +36,23 @@ function NoteTags() { </div> ))} <div className="ml-2" /> - <Select name="notesOwner" plain right options={notesOwner} onChange={({ value }) => notesStore.toggleShared(value.value === '1')} defaultValue={notesOwner[0].value} /> + <Select + name="notesOwner" + plain + right + options={notesOwner} + onChange={({ value }) => notesStore.toggleShared(value.value === '1')} + defaultValue={notesOwner[0].value} + /> <div className="ml-2" /> - <Select name="sortNotes" plain right options={sortOptions} onChange={({ value }) => notesStore.toggleSort(value.value)} defaultValue={sortOptions[0].value} /> + <Select + name="sortNotes" + plain + right + options={sortOptions} + onChange={({ value }) => notesStore.toggleSort(value.value)} + defaultValue={sortOptions[0].value} + /> </div> ); } diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx b/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx index 5f279c394..eb46db987 100644 --- a/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx +++ b/frontend/app/components/shared/SessionListContainer/components/SessionList/SessionList.tsx @@ -1,8 +1,9 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { connect } from 'react-redux'; import { FilterKey } from 'Types/filter/filterType'; import SessionItem from 'Shared/SessionItem'; import { NoContent, Loader, Pagination, Button, Icon } from 'UI'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import { fetchSessions, @@ -22,7 +23,7 @@ enum NoContentType { const AUTOREFRESH_INTERVAL = 5 * 60 * 1000; let sessionTimeOut: any = null; -interface Props { +interface Props extends RouteComponentProps { loading: boolean; list: any; currentPage: number; @@ -94,7 +95,7 @@ function SessionList(props: Props) { // handle scroll position const { scrollY } = props; window.scrollTo(0, scrollY); - if (total === 0) { + if (total === 0 && props.history.location.search === "") { props.fetchSessions(null, true); } props.fetchMetadata(); @@ -137,13 +138,13 @@ function SessionList(props: Props) { <NoContent title={ <div className="flex items-center justify-center flex-col"> - <AnimatedSVG name={NO_CONTENT.icon} size={170} /> + <AnimatedSVG name={NO_CONTENT.icon} size={180} /> <div className="mt-2" /> <div className="text-center text-gray-600 relative"> {NO_CONTENT.message} {noContentType === NoContentType.ToDate ? ( - <div style={{ position: 'absolute', right: -170, top: -110 }}> - <Icon name="list-arrow" size={130} width={150} /> + <div style={{ position: 'absolute', right: -200, top: -170 }}> + <Icon name="pointer-sessions-search" size={250} width={240} /> </div> ) : null} </div> @@ -154,7 +155,7 @@ function SessionList(props: Props) { {(isVault || isBookmark) && ( <div> {isVault - ? 'Add a session to your vault from player screen to retain it for ever.' + ? 'Add any session to your vault from the replay page and retain it longer.' : 'Bookmark important sessions in player screen and quickly find them here.'} </div> )} @@ -169,7 +170,7 @@ function SessionList(props: Props) { </Button> </div> } - show={!loading && list.size === 0} + show={!loading && list.length === 0} > {list.map((session: any) => ( <div key={session.sessionId} className="border-b"> @@ -188,7 +189,7 @@ function SessionList(props: Props) { <div className="flex items-center justify-between p-5"> <div> Showing <span className="font-medium">{(currentPage - 1) * pageSize + 1}</span> to{' '} - <span className="font-medium">{(currentPage - 1) * pageSize + list.size}</span> of{' '} + <span className="font-medium">{(currentPage - 1) * pageSize + list.length}</span> of{' '} <span className="font-medium">{numberWithCommas(total)}</span> sessions. </div> <Pagination @@ -226,4 +227,4 @@ export default connect( fetchMetadata, checkForLatestSessions, } -)(SessionList); +)(withRouter(SessionList)); diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionSettingButton/SessionSettingButton.tsx b/frontend/app/components/shared/SessionListContainer/components/SessionSettingButton/SessionSettingButton.tsx index 428b8f153..7b9726000 100644 --- a/frontend/app/components/shared/SessionListContainer/components/SessionSettingButton/SessionSettingButton.tsx +++ b/frontend/app/components/shared/SessionListContainer/components/SessionSettingButton/SessionSettingButton.tsx @@ -7,7 +7,7 @@ function SessionSettingButton(props: any) { const { showModal } = useModal(); const handleClick = () => { - showModal(<SessionSettings />, { right: true }); + showModal(<SessionSettings />, { right: true, width: 450 }); }; return ( diff --git a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx index 48856d929..84fb770a8 100644 --- a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx +++ b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx @@ -1,24 +1,32 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import FilterList from 'Shared/Filters/FilterList'; import FilterSelection from 'Shared/Filters/FilterSelection'; import SaveFilterButton from 'Shared/SaveFilterButton'; import { connect } from 'react-redux'; import { Button } from 'UI'; -import { edit, addFilter } from 'Duck/search'; +import { edit, addFilter, fetchSessions, updateFilter } from 'Duck/search'; import SessionSearchQueryParamHandler from 'Shared/SessionSearchQueryParamHandler'; +import { debounce } from 'App/utils'; + +let debounceFetch: any = () => {} + interface Props { appliedFilter: any; edit: typeof edit; addFilter: typeof addFilter; saveRequestPayloads: boolean; metaLoading?: boolean + fetchSessions: typeof fetchSessions; + updateFilter: typeof updateFilter; } function SessionSearch(props: Props) { const { appliedFilter, saveRequestPayloads = false, metaLoading } = props; const hasEvents = appliedFilter.filters.filter((i: any) => i.isEvent).size > 0; const hasFilters = appliedFilter.filters.filter((i: any) => !i.isEvent).size > 0; - + useEffect(() => { + debounceFetch = debounce(() => props.fetchSessions(), 500); + }, []) const onAddFilter = (filter: any) => { props.addFilter(filter); @@ -33,10 +41,12 @@ function SessionSearch(props: Props) { } }); - props.edit({ + props.updateFilter({ ...appliedFilter, filters: newFilters, }); + + debounceFetch() }; const onRemoveFilter = (filterIndex: any) => { @@ -44,15 +54,19 @@ function SessionSearch(props: Props) { return i !== filterIndex; }); - props.edit({ + props.updateFilter({ filters: newFilters, }); + + debounceFetch() }; const onChangeEventsOrder = (e: any, { value }: any) => { - props.edit({ + props.updateFilter({ eventsOrder: value, }); + + debounceFetch() }; return !metaLoading && ( @@ -102,5 +116,5 @@ export default connect( appliedFilter: state.getIn(['search', 'instance']), metaLoading: state.getIn(['customFields', 'fetchRequestActive', 'loading']) }), - { edit, addFilter } + { edit, addFilter, fetchSessions, updateFilter } )(SessionSearch); diff --git a/frontend/app/components/shared/SessionSearchQueryParamHandler/SessionSearchQueryParamHandler.tsx b/frontend/app/components/shared/SessionSearchQueryParamHandler/SessionSearchQueryParamHandler.tsx index f2624b460..5bd5d739d 100644 --- a/frontend/app/components/shared/SessionSearchQueryParamHandler/SessionSearchQueryParamHandler.tsx +++ b/frontend/app/components/shared/SessionSearchQueryParamHandler/SessionSearchQueryParamHandler.tsx @@ -2,95 +2,38 @@ import React, { useEffect } from 'react'; import { useHistory } from 'react-router'; import { connect } from 'react-redux'; import { addFilterByKeyAndValue, addFilter } from 'Duck/search'; -import { getFilterKeyTypeByKey, setQueryParamKeyFromFilterkey } from 'Types/filter/filterType'; -import { filtersMap } from 'App/types/filter/newFilter'; +import { updateFilter } from 'Duck/search'; +import { createUrlQuery, getFiltersFromQuery } from 'App/utils/search'; interface Props { appliedFilter: any; + updateFilter: any; addFilterByKeyAndValue: typeof addFilterByKeyAndValue; addFilter: typeof addFilter; } -const SessionSearchQueryParamHandler = React.memo((props: Props) => { +const SessionSearchQueryParamHandler = (props: Props) => { const { appliedFilter } = props; const history = useHistory(); - const createUrlQuery = (filters: any) => { - const query: any = {}; - filters.forEach((filter: any) => { - if (filter.value.length > 0) { - const _key = setQueryParamKeyFromFilterkey(filter.key); - if (_key) { - let str = `${filter.operator}|${filter.value.join('|')}`; - if (filter.hasSource) { - str = `${str}^${filter.sourceOperator}|${filter.source.join('|')}`; - } - query[_key] = str; - } else { - let str = `${filter.operator}|${filter.value.join('|')}`; - query[filter.key] = str; - } - } - }); - return query; - }; - - const addFilter = ([key, value]: [any, any]): void => { - if (value !== '') { - const filterKey = getFilterKeyTypeByKey(key); - const tmp = value.split('^'); - const valueArr = tmp[0].split('|'); - const operator = valueArr.shift(); - - const sourceArr = tmp[1] ? tmp[1].split('|') : []; - const sourceOperator = sourceArr.shift(); - // TODO validate operator - if (filterKey) { - props.addFilterByKeyAndValue(filterKey, valueArr, operator, sourceOperator, sourceArr); - } else { - const _filters: any = { ...filtersMap }; - const _filter = _filters[key]; - if (!!_filter) { - _filter.value = valueArr; - _filter.operator = operator; - _filter.source = sourceArr; - props.addFilter(_filter); - } - } - } - }; - const applyFilterFromQuery = () => { - if (appliedFilter.filters.size > 0) { - return; - } - const entires = getQueryObject(history.location.search); - if (entires.length > 0) { - entires.forEach(addFilter); - } + const filter = getFiltersFromQuery(history.location.search, appliedFilter); + props.updateFilter(filter, true); }; const generateUrlQuery = () => { - const query: any = createUrlQuery(appliedFilter.filters); - // const queryString = Object.entries(query).map(([key, value]) => `${key}=${value}`).join('&'); - const queryString = new URLSearchParams(query).toString(); - history.replace({ search: queryString }); + const search: any = createUrlQuery(appliedFilter); + history.replace({ search }); }; useEffect(applyFilterFromQuery, []); useEffect(generateUrlQuery, [appliedFilter]); + return <></>; -}); +}; export default connect( (state: any) => ({ appliedFilter: state.getIn(['search', 'instance']), }), - { addFilterByKeyAndValue, addFilter } + { addFilterByKeyAndValue, addFilter, updateFilter } )(SessionSearchQueryParamHandler); - -function getQueryObject(search: any) { - const queryParams = Object.fromEntries( - Object.entries(Object.fromEntries(new URLSearchParams(search))) - ); - return Object.entries(queryParams); -} diff --git a/frontend/app/components/shared/SessionSettings/SessionSettings.tsx b/frontend/app/components/shared/SessionSettings/SessionSettings.tsx index 9a097a47a..ff98fb53c 100644 --- a/frontend/app/components/shared/SessionSettings/SessionSettings.tsx +++ b/frontend/app/components/shared/SessionSettings/SessionSettings.tsx @@ -6,7 +6,7 @@ import CaptureRate from './components/CaptureRate'; function SessionSettings() { return ( - <div className="bg-white box-shadow h-screen overflow-y-auto" style={{ width: '450px'}}> + <div className="bg-white box-shadow h-screen overflow-y-auto"> <div className="px-6 pt-6"> <h1 className="text-2xl">Sessions Settings</h1> </div> diff --git a/frontend/app/components/shared/SessionSettings/components/ListingVisibility.tsx b/frontend/app/components/shared/SessionSettings/components/ListingVisibility.tsx index c18629490..8bea8b004 100644 --- a/frontend/app/components/shared/SessionSettings/components/ListingVisibility.tsx +++ b/frontend/app/components/shared/SessionSettings/components/ListingVisibility.tsx @@ -49,9 +49,11 @@ function ListingVisibility() { value={durationSettings.count} type="number" name="count" + min={0} placeholder="E.g 10" onChange={({ target: { value } }: any) => { - changeSettings({ count: value }) + console.log('value', value) + changeSettings({ count: value > 0 ? value : '' }) }} /> </div> diff --git a/frontend/app/components/shared/SessionStack/SessionStack.js b/frontend/app/components/shared/SessionStack/SessionStack.js deleted file mode 100644 index 394e36ab7..000000000 --- a/frontend/app/components/shared/SessionStack/SessionStack.js +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react' -import stl from './sessionStack.module.css' -import cn from 'classnames'; -import { Icon } from 'UI' -import { names } from 'Types/watchdog' -import { applySavedFilter, setActiveFlow } from 'Duck/filters'; -import { connect } from 'react-redux'; -import { setActiveTab } from 'Duck/sessions'; - -const IconLabel = ({ icon, label}) => ( - <div className="w-9/12 flex items-center justify-end"> - <Icon name={icon} size="20" color={label > 0 ? 'gray' : 'gray-medium'} /> - <div className={cn('ml-2 text-xl', label > 0 ? 'color-gray' : 'color-gray-medium')}>{label}</div> - </div> -) - -function SessionStack({ flow = {}, applySavedFilter, setActiveTab, setActiveFlow }) { - const onAllClick = (flow) => { - setActiveFlow(flow) - applySavedFilter(flow.filter) - setActiveTab({ type: 'all', name: 'All'}) - } - return ( - <div className={stl.wrapper}> - <div - className="text-xl mb-6 capitalize color-teal cursor-pointer" - onClick={() => onAllClick(flow)}> - {flow.name} - </div> - <div className="flex items-center"> - <div className="w-2/12 text-xl"><span className="text-3xl">{flow.count}</span> Sessions</div> - <div className="w-6/12 flex items-center ml-auto"> - {flow.watchdogs.map(({type, count}) => ( - <IconLabel key={type} icon={names[type].icon} label={count} /> - ))} - </div> - </div> - </div> - ) -} - -export default connect(null, { applySavedFilter, setActiveTab, setActiveFlow })(SessionStack) diff --git a/frontend/app/components/shared/SessionStack/index.js b/frontend/app/components/shared/SessionStack/index.js deleted file mode 100644 index db3464728..000000000 --- a/frontend/app/components/shared/SessionStack/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SessionStack'; \ No newline at end of file diff --git a/frontend/app/components/shared/SessionStack/sessionStack.module.css b/frontend/app/components/shared/SessionStack/sessionStack.module.css deleted file mode 100644 index 30b2a6eef..000000000 --- a/frontend/app/components/shared/SessionStack/sessionStack.module.css +++ /dev/null @@ -1,18 +0,0 @@ - -@import 'mixins.css'; - -.wrapper { - background: #fff; - border: solid thin $gray-light; - border-radius: 3px; - @mixin defaultHover; - box-shadow: - /* The top layer shadow */ - /* 0 1px 1px rgba(0,0,0,0.15), */ - /* The second layer */ - 4px 4px 1px 1px white, - /* The second layer shadow */ - 4px 4px 0px 1px rgba(0,0,0,0.4); - /* Padding for demo purposes */ - padding: 16px; -} \ No newline at end of file diff --git a/frontend/app/components/shared/SharePopup/SessionCopyLink/SessionCopyLink.tsx b/frontend/app/components/shared/SharePopup/SessionCopyLink/SessionCopyLink.tsx index fccbb6b81..6131e9c8f 100644 --- a/frontend/app/components/shared/SharePopup/SessionCopyLink/SessionCopyLink.tsx +++ b/frontend/app/components/shared/SharePopup/SessionCopyLink/SessionCopyLink.tsx @@ -1,15 +1,14 @@ import React from 'react'; import { Button, Icon } from 'UI'; import copy from 'copy-to-clipboard'; -import { connectPlayer } from 'Player'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; -interface Props { - content: string; - time: any; -} - -function SessionCopyLink({ content = '', time }: Props) { +function SessionCopyLink() { const [copied, setCopied] = React.useState(false); + const { store } = React.useContext(PlayerContext) + + const time = store.get().time const copyHandler = () => { setCopied(true); @@ -21,12 +20,6 @@ function SessionCopyLink({ content = '', time }: Props) { return ( <div className="flex justify-between items-center w-full mt-2"> - {/* <IconButton - label="Copy URL at current time" - primaryText - icon="link-45deg" - onClick={copyHandler} - /> */} <Button variant="text-primary" onClick={copyHandler}> <> <Icon name="link-45deg" className="mr-2" color="teal" size="18" /> @@ -38,8 +31,4 @@ function SessionCopyLink({ content = '', time }: Props) { ); } -const SessionCopyLinkCompo = connectPlayer((state: any) => ({ - time: state.time, -}))(SessionCopyLink); - -export default React.memo(SessionCopyLinkCompo); +export default observer(SessionCopyLink); diff --git a/frontend/app/components/shared/SharePopup/SharePopup.js b/frontend/app/components/shared/SharePopup/SharePopup.js index 4c75df64b..07726d14a 100644 --- a/frontend/app/components/shared/SharePopup/SharePopup.js +++ b/frontend/app/components/shared/SharePopup/SharePopup.js @@ -1,45 +1,71 @@ import React from 'react'; import { connect } from 'react-redux'; import { toast } from 'react-toastify'; -import withRequest from 'HOCs/withRequest'; -import { Icon, Button, Popover } from 'UI'; +import { Icon, Button, Popover, Loader } from 'UI'; import styles from './sharePopup.module.css'; import IntegrateSlackButton from '../IntegrateSlackButton/IntegrateSlackButton'; import SessionCopyLink from './SessionCopyLink'; import Select from 'Shared/Select'; import cn from 'classnames'; -import { fetchList } from 'Duck/integrations/slack'; +import { fetchList as fetchSlack, sendSlackMsg } from 'Duck/integrations/slack'; +import { fetchList as fetchTeams, sendMsTeamsMsg } from 'Duck/integrations/teams'; @connect( (state) => ({ + sessionId: state.getIn(['sessions', 'current']).sessionId, channels: state.getIn(['slack', 'list']), + msTeamsChannels: state.getIn(['teams', 'list']), tenantId: state.getIn(['user', 'account', 'tenantId']), }), - { fetchList } + { fetchSlack, fetchTeams, sendSlackMsg, sendMsTeamsMsg } ) -@withRequest({ - endpoint: ({ id, entity }, integrationId) => - `/integrations/slack/notify/${integrationId}/${entity}/${id}`, - method: 'POST', -}) export default class SharePopup extends React.PureComponent { state = { comment: '', isOpen: false, channelId: this.props.channels.getIn([0, 'webhookId']), + teamsChannel: this.props.msTeamsChannels.getIn([0, 'webhookId']), + loadingSlack: false, + loadingTeams: false, }; - componentDidMount() { - if (this.props.channels.size === 0) { - this.props.fetchList(); + componentDidUpdate() { + if (this.state.isOpen) { + if (this.props.channels.size === 0) { + this.props.fetchSlack(); + } + if (this.props.msTeamsChannels.size === 0) { + this.props.fetchTeams(); + } } } editMessage = (e) => this.setState({ comment: e.target.value }); - share = () => - this.props - .request({ comment: this.state.comment }, this.state.channelId) - .then(this.handleSuccess); + shareToSlack = () => { + this.setState({ loadingSlack: true }, () => { + this.props + .sendSlackMsg({ + integrationId: this.state.channelId, + entity: 'sessions', + entityId: this.props.sessionId, + data: { comment: this.state.comment }, + }) + .then(() => this.handleSuccess('Slack')); + }); + }; + + shareToMSTeams = () => { + this.setState({ loadingTeams: true }, () => { + this.props + .sendMsTeamsMsg({ + integrationId: this.state.teamsChannel, + entity: 'sessions', + entityId: this.props.sessionId, + data: { comment: this.state.comment }, + }) + .then(() => this.handleSuccess('MS Teams')); + }); + }; handleOpen = () => { setTimeout(function () { @@ -51,79 +77,138 @@ export default class SharePopup extends React.PureComponent { this.setState({ comment: '' }); }; - handleSuccess = () => { - this.setState({ isOpen: false, comment: '' }); - toast.success('Sent to Slack.'); + handleSuccess = (endpoint) => { + const obj = + endpoint === 'Slack' + ? { loadingSlack: false } + : { loadingTeams: false }; + this.setState(obj); + toast.success(`Sent to ${endpoint}.`); }; - changeChannel = ({ value }) => this.setState({ channelId: value.value }); + changeSlackChannel = ({ value }) => this.setState({ channelId: value.value }); + + changeTeamsChannel = ({ value }) => this.setState({ teamsChannel: value.value }); onClickHandler = () => { this.setState({ isOpen: true }); }; render() { - const { trigger, loading, channels, showCopyLink = false } = this.props; - const { comment, channelId, isOpen } = this.state; + const { trigger, channels, msTeamsChannels, showCopyLink = false } = this.props; + const { comment, channelId, teamsChannel, loadingSlack, loadingTeams } = this.state; - const options = channels + const slackOptions = channels + .map(({ webhookId, name }) => ({ value: webhookId, label: name })) + .toJS(); + + const msTeamsOptions = msTeamsChannels .map(({ webhookId, name }) => ({ value: webhookId, label: name })) .toJS(); return ( <Popover + onOpen={() => this.setState({ isOpen: true })} + onClose={() => this.setState({ isOpen: false, comment: '' })} render={() => ( <div className={styles.wrapper}> - <div className={styles.header}> - <div className={cn(styles.title, 'text-lg')}>Share this session link to Slack</div> - </div> - {options.length === 0 ? ( + {this.state.loadingTeams || this.state.loadingSlack ? ( + <Loader loading /> + ) :( <> - <div className={styles.body}> - <IntegrateSlackButton /> - </div> - {showCopyLink && ( - <div className={styles.footer}> - <SessionCopyLink /> + <div className={styles.header}> + <div className={cn(styles.title, 'text-lg')}> + Share this session link to Slack/MS Teams </div> - )} - </> - ) : ( - <div> - <div className={styles.body}> - <textarea - name="message" - id="message" - cols="30" - rows="4" - resize="none" - onChange={this.editMessage} - value={comment} - placeholder="Add Message (Optional)" - className="p-4" - /> + </div> + {slackOptions.length > 0 || msTeamsOptions.length > 0 ? ( + <div> + <div className={styles.body}> + <textarea + name="message" + id="message" + cols="30" + rows="4" + resize="none" + onChange={this.editMessage} + value={comment} + placeholder="Add Message (Optional)" + className="p-4 text-figmaColors-text-primary text-base" + /> - <div className="flex items-center justify-between"> - <Select - options={options} - defaultValue={channelId} - onChange={this.changeChannel} - className="mr-4" - /> - <div> - <Button onClick={this.share} variant="primary"> - <div className="flex items-center"> - <Icon name="integrations/slack-bw" size="18" marginRight="10" /> - {loading ? 'Sending...' : 'Send'} - </div> - </Button> + {slackOptions.length > 0 && ( + <> + <span>Share to slack</span> + <div className="flex items-center justify-between mb-2"> + <Select + options={slackOptions} + defaultValue={channelId} + onChange={this.changeSlackChannel} + className="mr-4" + /> + {this.state.channelId && ( + <Button onClick={this.shareToSlack} variant="primary"> + <div className="flex items-center"> + <Icon + name="integrations/slack-bw" + color="white" + size="18" + marginRight="10" + /> + {loadingSlack ? 'Sending...' : 'Send'} + </div> + </Button> + )} + </div> + </> + )} + {msTeamsOptions.length > 0 && ( + <> + <span>Share to MS Teams</span> + <div className="flex items-center justify-between"> + <Select + options={msTeamsOptions} + defaultValue={teamsChannel} + onChange={this.changeTeamsChannel} + className="mr-4" + /> + {this.state.teamsChannel && ( + <Button onClick={this.shareToMSTeams} variant="primary"> + <div className="flex items-center"> + <Icon + name="integrations/teams-white" + color="white" + size="18" + marginRight="10" + /> + {loadingTeams ? 'Sending...' : 'Send'} + </div> + </Button> + )} + </div> + </> + )} + </div> + <div className={styles.footer}> + <SessionCopyLink /> </div> </div> - </div> - <div className={styles.footer}> - <SessionCopyLink /> - </div> - </div> + ) : ( + <> + <div className={styles.body}> + <IntegrateSlackButton /> + </div> + {showCopyLink && ( + <> + <div className="border-t -mx-2" /> + <div> + <SessionCopyLink /> + </div> + </> + )} + </> + )} + </> )} </div> )} diff --git a/frontend/app/components/shared/SharePopup/sharePopup.module.css b/frontend/app/components/shared/SharePopup/sharePopup.module.css index b1d3a580c..bd7ba37eb 100644 --- a/frontend/app/components/shared/SharePopup/sharePopup.module.css +++ b/frontend/app/components/shared/SharePopup/sharePopup.module.css @@ -38,17 +38,6 @@ margin-bottom: 14px; } -.footer { - /* display: flex; */ - /* align-items: center; */ - /* justify-content: space-between; */ - /* padding: 10px 0; */ - border-top: solid thin $gray-light; - margin: 0 -14px; - padding: 0 14px; - /* border-bottom: solid thin $gray-light; */ -} - textarea { width: 100%; background-color: $gray-lightest; diff --git a/frontend/app/components/shared/TrackingCodeModal/ProjectCodeSnippet/ProjectCodeSnippet.js b/frontend/app/components/shared/TrackingCodeModal/ProjectCodeSnippet/ProjectCodeSnippet.js index 496d7aec8..b8bd87b6a 100644 --- a/frontend/app/components/shared/TrackingCodeModal/ProjectCodeSnippet/ProjectCodeSnippet.js +++ b/frontend/app/components/shared/TrackingCodeModal/ProjectCodeSnippet/ProjectCodeSnippet.js @@ -1,12 +1,9 @@ import React, { useState } from 'react' import { connect } from 'react-redux'; import { editGDPR, saveGDPR } from 'Duck/site'; -import copy from 'copy-to-clipboard'; import { Checkbox } from 'UI'; -import GDPR from 'Types/site/gdpr'; import cn from 'classnames' import styles from './projectCodeSnippet.module.css' -import Highlight from 'react-highlight' import Select from 'Shared/Select' import CodeSnippet from '../../CodeSnippet'; @@ -23,78 +20,21 @@ inputModeOptions.forEach((o, i) => inputModeOptionsMap[o.value] = i) const ProjectCodeSnippet = props => { const { gdpr, site } = props; const [changed, setChanged] = useState(false) - const [copied, setCopied] = useState(false) - const codeSnippet = `<!-- OpenReplay Tracking Code for HOST --> -<script> - var initOpts = { - projectKey: "PROJECT_KEY", - ingestPoint: "https://${window.location.hostname}/ingest", - defaultInputMode: ${inputModeOptionsMap[gdpr.defaultInputMode]}, - obscureTextNumbers: ${gdpr.maskNumbers}, - obscureTextEmails: ${gdpr.maskEmails}, - }; - var startOpts = { userID: "" }; - (function(A,s,a,y,e,r){ - r=window.OpenReplay=[e,r,y,[s-1, e]]; - s=document.createElement('script');s.src=A;s.async=!a; - document.getElementsByTagName('head')[0].appendChild(s); - r.start=function(v){r.push([0])}; - r.stop=function(v){r.push([1])}; - r.setUserID=function(id){r.push([2,id])}; - r.setUserAnonymousID=function(id){r.push([3,id])}; - r.setMetadata=function(k,v){r.push([4,k,v])}; - r.event=function(k,p,i){r.push([5,k,p,i])}; - r.issue=function(k,p){r.push([6,k,p])}; - r.isActive=function(){return false}; - r.getSessionToken=function(){}; - })("//static.openreplay.com/${window.env.TRACKER_VERSION}/openreplay.js",1,0,initOpts,startOpts); -</script>`; - - const saveGDPR = (value) => { + const saveGDPR = () => { setChanged(true) - props.saveGDPR(site.id, GDPR({...value})); + props.saveGDPR(site.id); } const onChangeSelect = ({ name, value }) => { - const { gdpr } = site; props.editGDPR({ [ name ]: value }); - saveGDPR({ ...gdpr, [ name ]: value }); + saveGDPR(); }; const onChangeOption = ({ target: { name, checked }}) => { - const { gdpr } = props.site; - const _gdpr = { ...gdpr.toData() }; - _gdpr[name] = checked; props.editGDPR({ [ name ]: checked }); - saveGDPR(_gdpr) + saveGDPR() } - - const getOptionValues = () => { - const { gdpr } = props.site; - return (!!gdpr.maskEmails)|(!!gdpr.maskNumbers << 1)|(['plain' , 'obscured', 'hidden'].indexOf(gdpr.defaultInputMode) << 5)|28 - } - - - const getCodeSnippet = site => { - let snippet = codeSnippet; - if (site && site.id) { - snippet = snippet.replace('PROJECT_KEY', site.projectKey); - } - return snippet - //.replace('XXX', getOptionValues()) - //.replace('HOST', site && site.host); - } - - const copyHandler = (code) => { - setCopied(true); - copy(code); - setTimeout(() => { - setCopied(false); - }, 1000); - }; - - const _snippet = getCodeSnippet(site); return ( <div> @@ -155,8 +95,6 @@ const ProjectCodeSnippet = props => { } export default connect(state => ({ - // siteId: state.getIn([ 'site', 'siteId' ]), - // site: state.getIn([ 'site', 'instance' ]), gdpr: state.getIn([ 'site', 'instance', 'gdpr' ]), saving: state.getIn([ 'site', 'saveGDPR', 'loading' ]) }), { editGDPR, saveGDPR })(ProjectCodeSnippet) diff --git a/frontend/app/components/shared/TrackingCodeModal/TrackingCodeModal.js b/frontend/app/components/shared/TrackingCodeModal/TrackingCodeModal.js index 9534d875f..96061521e 100644 --- a/frontend/app/components/shared/TrackingCodeModal/TrackingCodeModal.js +++ b/frontend/app/components/shared/TrackingCodeModal/TrackingCodeModal.js @@ -1,11 +1,7 @@ import React from 'react'; -import { Modal, Icon, Tabs } from 'UI'; -import styles from './trackingCodeModal.module.css'; -import { editGDPR, saveGDPR } from 'Duck/site'; -import { connect } from 'react-redux'; +import { Tabs } from 'UI'; import ProjectCodeSnippet from './ProjectCodeSnippet'; import InstallDocs from './InstallDocs'; -import cn from 'classnames'; const PROJECT = 'Using Script'; const DOCUMENTATION = 'Using NPM'; @@ -33,7 +29,7 @@ class TrackingCodeModal extends React.PureComponent { }; render() { - const { site, displayed, onClose, title = '', subTitle } = this.props; + const { title = '', subTitle } = this.props; const { activeTab } = this.state; return ( <div className="bg-white h-screen overflow-y-auto" style={{ width: '700px' }}> @@ -50,14 +46,4 @@ class TrackingCodeModal extends React.PureComponent { } } -export default connect( - (state) => ({ - // site: state.getIn(['site', 'instance']), - // gdpr: state.getIn(['site', 'instance', 'gdpr']), - saving: state.getIn(['site', 'saveGDPR', 'loading']), - }), - { - editGDPR, - saveGDPR, - } -)(TrackingCodeModal); +export default TrackingCodeModal; diff --git a/frontend/app/components/shared/UpdateFunnelButton/UpdateFunnelButton.tsx b/frontend/app/components/shared/UpdateFunnelButton/UpdateFunnelButton.tsx index 50c1215ed..9f7a2eb06 100644 --- a/frontend/app/components/shared/UpdateFunnelButton/UpdateFunnelButton.tsx +++ b/frontend/app/components/shared/UpdateFunnelButton/UpdateFunnelButton.tsx @@ -1,6 +1,5 @@ -import React, { useState } from 'react'; +import React from 'react'; import { IconButton } from 'UI'; -import FunnelSaveModal from 'App/components/Funnels/FunnelSaveModal'; import { connect } from 'react-redux'; import { save } from 'Duck/funnels'; diff --git a/frontend/app/components/shared/UserSessionsModal/UserSessionsModal.tsx b/frontend/app/components/shared/UserSessionsModal/UserSessionsModal.tsx index cb5baf47f..0c8b1b480 100644 --- a/frontend/app/components/shared/UserSessionsModal/UserSessionsModal.tsx +++ b/frontend/app/components/shared/UserSessionsModal/UserSessionsModal.tsx @@ -1,12 +1,9 @@ import React, { useEffect } from 'react'; import { useStore } from 'App/mstore'; -import Filter from 'Types/filter'; -import { filtersMap } from 'Types/filter/newFilter'; import { FilterKey } from 'App/types/filter/filterType'; import { NoContent, Pagination, Loader, Avatar } from 'UI'; import SessionItem from 'Shared/SessionItem'; import SelectDateRange from 'Shared/SelectDateRange'; -import Period from 'Types/app/period'; import { useObserver, observer } from 'mobx-react-lite'; import { useModal } from 'App/components/Modal'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; @@ -49,7 +46,7 @@ function UserSessionsModal(props: Props) { useEffect(fetchData, [filter.page, filter.startDate, filter.endDate]); return ( - <div className="h-screen overflow-y-auto bg-white" style={{ width: '700px' }}> + <div className="h-screen overflow-y-auto bg-white"> <div className="flex items-center justify-between w-full px-5 py-3"> <div className="text-lg flex items-center"> <Avatar isActive={false} seed={hash} isAssist={false} className={''} /> diff --git a/frontend/app/components/shared/WidgetAutoComplete/WidgetAutoComplete.js b/frontend/app/components/shared/WidgetAutoComplete/WidgetAutoComplete.js index 97fbc9b90..f7de177e6 100644 --- a/frontend/app/components/shared/WidgetAutoComplete/WidgetAutoComplete.js +++ b/frontend/app/components/shared/WidgetAutoComplete/WidgetAutoComplete.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Icon, CircularLoader, Button, TextEllipsis } from 'UI'; +import { Icon, CircularLoader, Button } from 'UI'; import cn from 'classnames'; import stl from './widgetAutoComplete.module.css'; import { debounce } from 'App/utils'; diff --git a/frontend/app/components/shared/XRayButton/XRayButton.tsx b/frontend/app/components/shared/XRayButton/XRayButton.tsx index 58daaa070..ec7842991 100644 --- a/frontend/app/components/shared/XRayButton/XRayButton.tsx +++ b/frontend/app/components/shared/XRayButton/XRayButton.tsx @@ -1,16 +1,16 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import stl from './xrayButton.module.css'; import cn from 'classnames'; import { Tooltip } from 'UI'; -import GuidePopup, { FEATURE_KEYS } from 'Shared/GuidePopup'; -import { Controls as Player } from 'Player'; -import { INDEXES } from 'App/constants/zindex'; +import { PlayerContext } from 'App/components/Session/playerContext'; interface Props { onClick?: () => void; isActive?: boolean; } function XRayButton(props: Props) { + const { player: Player } = React.useContext(PlayerContext); + const { isActive } = props; // const [showGuide, setShowGuide] = useState(!localStorage.getItem(FEATURE_KEYS.XRAY)); const showGuide = false; @@ -41,40 +41,14 @@ function XRayButton(props: Props) { ></div> )} <div className="relative"> - {showGuide ? ( - // <GuidePopup - // title={<div className="color-gray-dark">Introducing <span className={stl.text}>X-Ray</span></div>} - // description={"Get a quick overview on the issues in this session."} - // > - <button - className={cn(stl.wrapper, { [stl.default]: !isActive, [stl.active]: isActive })} - onClick={onClick} - style={{ zIndex: INDEXES.POPUP_GUIDE_BTN, position: 'relative' }} - > - <span className="z-1">X-RAY</span> - </button> - - // <div - // className="absolute bg-white top-0 left-0 z-0" - // style={{ - // zIndex: INDEXES.POPUP_GUIDE_BG, - // width: '100px', - // height: '50px', - // borderRadius: '30px', - // margin: '-10px -16px', - // }} - // ></div> - // </GuidePopup> - ) : ( - <Tooltip title="Get a quick overview on the issues in this session." disabled={isActive}> - <button - className={cn(stl.wrapper, { [stl.default]: !isActive, [stl.active]: isActive })} - onClick={onClick} - > - <span className="z-1">X-RAY</span> - </button> - </Tooltip> - )} + <Tooltip title="Get a quick overview on the issues in this session." disabled={isActive}> + <button + className={cn(stl.wrapper, { [stl.default]: !isActive, [stl.active]: isActive })} + onClick={onClick} + > + <span className="z-1">X-RAY</span> + </button> + </Tooltip> </div> </> ); diff --git a/frontend/app/components/ui/BackLink/BackLink.js b/frontend/app/components/ui/BackLink/BackLink.js index a150865bb..93445cce6 100644 --- a/frontend/app/components/ui/BackLink/BackLink.js +++ b/frontend/app/components/ui/BackLink/BackLink.js @@ -4,7 +4,7 @@ import cls from './backLink.module.css'; import cn from 'classnames'; export default function BackLink ({ - className, to, onClick, label, vertical = false, style + className, to, onClick, label = '', vertical = false, style }) { const children = ( <div className={ cn('flex items-center', {'border w-10 h-10 rounded-full bg-white p-3 items-center justify-center hover:bg-active-blue' : !label })}> @@ -18,7 +18,7 @@ export default function BackLink ({ className={ verticalClassName } to={ to } > - { children } + { children } </Link> : <button @@ -29,4 +29,3 @@ export default function BackLink ({ { children } </button> } - diff --git a/frontend/app/components/ui/Button/Button.tsx b/frontend/app/components/ui/Button/Button.tsx index dc73f9e60..3b08f55f7 100644 --- a/frontend/app/components/ui/Button/Button.tsx +++ b/frontend/app/components/ui/Button/Button.tsx @@ -5,7 +5,7 @@ import { CircularLoader, Icon, Tooltip } from 'UI'; interface Props { className?: string; children?: React.ReactNode; - onClick?: () => void; + onClick?: (e: React.MouseEvent<HTMLElement>) => void; disabled?: boolean; type?: 'button' | 'submit' | 'reset'; variant?: 'default' | 'primary' | 'text' | 'text-primary' | 'text-red' | 'outline' | 'green'; @@ -32,41 +32,25 @@ export default (props: Props) => { ...rest } = props; - let classes = ['relative flex items-center h-10 px-3 rounded tracking-wide whitespace-nowrap']; let iconColor = variant === 'text' || variant === 'default' ? 'gray-dark' : 'teal'; - if (variant === 'default') { - classes.push('bg-white hover:bg-gray-light border border-gray-light'); - } + const variantClasses = { + default: 'bg-white hover:bg-gray-light border border-gray-light', + primary: 'bg-teal color-white hover:bg-teal-dark', + green: 'bg-green color-white hover:bg-green-dark', + text: 'bg-transparent color-gray-dark hover:bg-gray-light-shade hover:!text-teal hover-fill-teal', + 'text-primary': 'bg-transparent color-teal hover:bg-teal-light hover:color-teal-dark', + 'text-red': 'bg-transparent color-red hover:bg-teal-light', + outline: 'bg-white color-teal border border-teal hover:bg-teal-light', + }; - if (variant === 'primary') { - classes.push('bg-teal color-white hover:bg-teal-dark'); - } - - if (variant === 'green') { - classes.push('bg-green color-white hover:bg-green-dark'); - iconColor = 'white'; - } - - if (variant === 'text') { - classes.push('bg-transparent color-gray-dark hover:bg-gray-light-shade'); - } - - if (variant === 'text-primary') { - classes.push('bg-transparent color-teal hover:bg-teal-light hover:color-teal-dark'); - } - - if (variant === 'text-red') { - classes.push('bg-transparent color-red hover:bg-teal-light'); - } - - if (variant === 'outline') { - classes.push('bg-white color-teal border border-teal hover:bg-teal-light'); - } - - if (disabled) { - classes.push('opacity-40 pointer-events-none'); - } + let classes = cn( + 'relative flex items-center h-10 px-3 rounded tracking-wide whitespace-nowrap', + variantClasses[variant], + { 'opacity-40 pointer-events-none': disabled }, + { '!rounded-full h-10 w-10 justify-center': rounded }, + className + ); if (variant === 'primary') { iconColor = 'white'; @@ -75,10 +59,6 @@ export default (props: Props) => { iconColor = 'red'; } - if (rounded) { - classes = classes.map((c) => c.replace('rounded', 'rounded-full h-10 w-10 justify-center')); - } - const render = () => ( <button {...rest} type={type} className={cn(classes, className)}> {icon && ( diff --git a/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx b/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx index 4acd355c2..2aca274ea 100644 --- a/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx +++ b/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx @@ -80,7 +80,7 @@ export default connect( (state: any) => ({ // errorStack: state.getIn(['sessions', 'errorStack']), errorStack: state.getIn(['errors', 'instanceTrace']), - sessionId: state.getIn(['sessions', 'current', 'sessionId']), + sessionId: state.getIn(['sessions', 'current']).sessionId, }), { fetchErrorStackList } )(ErrorDetails); diff --git a/frontend/app/components/ui/ErrorItem/ErrorItem.tsx b/frontend/app/components/ui/ErrorItem/ErrorItem.tsx index 74ad634d9..19908cbb1 100644 --- a/frontend/app/components/ui/ErrorItem/ErrorItem.tsx +++ b/frontend/app/components/ui/ErrorItem/ErrorItem.tsx @@ -1,8 +1,6 @@ import React from 'react'; import cn from 'classnames'; -import { IconButton } from 'UI'; import stl from './errorItem.module.css'; -import { Duration } from 'luxon'; import { useModal } from 'App/components/Modal'; import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorDetailsModal'; import JumpButton from 'Shared/DevTools/JumpButton'; @@ -17,7 +15,7 @@ function ErrorItem({ error = {}, onJump, inactive, selected }: Props) { const { showModal } = useModal(); const onErrorClick = () => { - showModal(<ErrorDetailsModal errorId={error.errorId} />, { right: true }); + showModal(<ErrorDetailsModal errorId={error.errorId} />, { right: true, width: 1200 }); }; return ( <div diff --git a/frontend/app/components/ui/EscapeButton/EscapeButton.js b/frontend/app/components/ui/EscapeButton/EscapeButton.js index 1f9b372a0..b913c9c55 100644 --- a/frontend/app/components/ui/EscapeButton/EscapeButton.js +++ b/frontend/app/components/ui/EscapeButton/EscapeButton.js @@ -2,7 +2,7 @@ import React from 'react' import { Icon } from 'UI'; import stl from './escapeButton.module.css' -function EscapeButton({ onClose = null}) { +function EscapeButton({ onClose = () => null }) { return ( <div className={ stl.closeWrapper } onClick={ onClose }> <Icon name="close" size="16" /> diff --git a/frontend/app/components/ui/ItemMenu/ItemMenu.tsx b/frontend/app/components/ui/ItemMenu/ItemMenu.tsx index e2d85a568..bd8ecee28 100644 --- a/frontend/app/components/ui/ItemMenu/ItemMenu.tsx +++ b/frontend/app/components/ui/ItemMenu/ItemMenu.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Icon, Popover } from 'UI'; +import { Icon, Popover, Tooltip } from 'UI'; import styles from './itemMenu.module.css'; import cn from 'classnames'; @@ -9,6 +9,7 @@ interface Item { onClick: (args: any) => void; hidden?: boolean; disabled?: boolean; + tooltipTitle?: string; } interface Props { @@ -16,6 +17,7 @@ interface Props { flat?: boolean; items: Item[]; label?: React.ReactNode; + sm?: boolean; onToggle?: (args: any) => void; } @@ -52,7 +54,7 @@ export default class ItemMenu extends React.PureComponent<Props> { }; render() { - const { items, label = '', bold } = this.props; + const { items, label = '', bold, sm } = this.props; const { displayed } = this.state; const parentStyles = label ? 'rounded px-2 py-2 hover:bg-gray-light' : ''; @@ -61,30 +63,29 @@ export default class ItemMenu extends React.PureComponent<Props> { render={() => ( <div className={cn(styles.menu, { [styles.menuDim]: !bold })} - // style={{ - // top: this.props.flat ? 24 : undefined, - // }} // data-displayed={displayed} > {items .filter(({ hidden }) => !hidden) - .map(({ onClick, text, icon, disabled = false }) => ( - <div - key={text} - onClick={!disabled ? this.onClick(onClick) : () => {}} - className={disabled ? 'cursor-not-allowed' : ''} - role="menuitem" - > - <div className={cn(styles.menuItem, 'text-neutral-700', { disabled: disabled })}> - {icon && ( - <div className={styles.iconWrapper}> - {/* @ts-ignore */} - <Icon name={icon} size="13" color="gray-dark" /> - </div> - )} - <div>{text}</div> + .map(({ onClick, text, icon, disabled = false, tooltipTitle = '' }) => ( + <Tooltip disabled={!disabled} title={tooltipTitle} delay={0}> + <div + key={text} + onClick={!disabled ? this.onClick(onClick) : () => {}} + className={disabled ? 'cursor-not-allowed' : ''} + role="menuitem" + > + <div className={cn(styles.menuItem, 'text-neutral-700', { disabled: disabled })}> + {icon && ( + <div className={styles.iconWrapper}> + {/* @ts-ignore */} + <Icon name={icon} size="13" color="gray-dark" /> + </div> + )} + <div>{text}</div> + </div> </div> - </div> + </Tooltip> ))} </div> )} @@ -92,7 +93,7 @@ export default class ItemMenu extends React.PureComponent<Props> { <div // onClick={this.toggleMenu} className={cn( - 'flex items-center cursor-pointer select-none hover rounded-full', + 'flex items-center cursor-pointer select-none', !this.props.flat ? parentStyles : '', { 'bg-gray-light': !this.props.flat && displayed && label } )} @@ -111,7 +112,8 @@ export default class ItemMenu extends React.PureComponent<Props> { }} className={cn('rounded-full flex items-center justify-center', { 'bg-gray-light': displayed, - 'w-10 h-10': !label, + 'w-10 h-10': !label && !sm, + 'w-8 h-8': sm, })} role="button" > diff --git a/frontend/app/components/ui/ItemMenu/itemMenu.module.css b/frontend/app/components/ui/ItemMenu/itemMenu.module.css index 7583e9975..4257fcf64 100644 --- a/frontend/app/components/ui/ItemMenu/itemMenu.module.css +++ b/frontend/app/components/ui/ItemMenu/itemMenu.module.css @@ -11,7 +11,7 @@ } width: 36px; height: 36px; - border-radius: 18px; + /* border-radius: 18px; */ border: 1px solid transparent; transition: all 0.2s; margin: 0 auto; diff --git a/frontend/app/components/ui/Label/Label.js b/frontend/app/components/ui/Label/Label.js index 371b80c9b..74ebb0f7d 100644 --- a/frontend/app/components/ui/Label/Label.js +++ b/frontend/app/components/ui/Label/Label.js @@ -1,5 +1,4 @@ import React from 'react'; -import styles from './label.module.css'; import cn from 'classnames'; export default ({ diff --git a/frontend/app/components/ui/Message/Message.js b/frontend/app/components/ui/Message/Message.js index f8417c25f..6819e2ea6 100644 --- a/frontend/app/components/ui/Message/Message.js +++ b/frontend/app/components/ui/Message/Message.js @@ -12,7 +12,7 @@ const Message = ({ inline = false, success = false, info = true, - text, + text = undefined, }) => visible || !hidden ? ( <div className={cn(styles.message, 'flex items-center')} data-inline={inline}> diff --git a/frontend/app/components/ui/Modal/Modal.tsx b/frontend/app/components/ui/Modal/Modal.tsx index 89ba9f5d9..c489aa216 100644 --- a/frontend/app/components/ui/Modal/Modal.tsx +++ b/frontend/app/components/ui/Modal/Modal.tsx @@ -13,7 +13,8 @@ function Modal(props: Props) { useEffect(() => { if (open) { document.body.style.overflow = 'hidden'; - } else { + } + return () => { document.body.style.overflow = 'auto'; } }, [open]); diff --git a/frontend/app/components/ui/NoSessionPermission/NoSessionPermission.tsx b/frontend/app/components/ui/NoSessionPermission/NoSessionPermission.tsx index 4d66e3f2e..ca62e8fb6 100644 --- a/frontend/app/components/ui/NoSessionPermission/NoSessionPermission.tsx +++ b/frontend/app/components/ui/NoSessionPermission/NoSessionPermission.tsx @@ -1,6 +1,6 @@ import React from "react"; import stl from "./NoSessionPermission.module.css"; -import { Icon, Button, Link } from "UI"; +import { Icon, Button } from "UI"; import { connect } from "react-redux"; import { sessions as sessionsRoute, diff --git a/frontend/app/components/ui/Popover/Popover.tsx b/frontend/app/components/ui/Popover/Popover.tsx index d8a0e260f..8170df7fd 100644 --- a/frontend/app/components/ui/Popover/Popover.tsx +++ b/frontend/app/components/ui/Popover/Popover.tsx @@ -21,14 +21,17 @@ interface Props { placement?: Placement; children: JSX.Element; onOpen?: () => void; + onClose?: () => void; } -const Popover = ({ children, render, placement, onOpen = () => {} }: Props) => { +const Popover = ({ children, render, placement, onOpen, onClose }: Props) => { const [open, setOpen] = useState(false); useEffect(() => { if (open) { - onOpen(); + onOpen?.(); + } else { + onClose?.(); } }, [open]); diff --git a/frontend/app/components/ui/SVG.tsx b/frontend/app/components/ui/SVG.tsx index 50a451917..66af7be76 100644 --- a/frontend/app/components/ui/SVG.tsx +++ b/frontend/app/components/ui/SVG.tsx @@ -1,7 +1,7 @@ import React from 'react'; -export type IconNames = 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-bar-left' | 'arrow-clockwise' | 'arrow-down-short' | 'arrow-down' | 'arrow-repeat' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | 'arrow-up-short' | 'arrow-up' | 'arrows-angle-extend' | 'avatar/icn_bear' | 'avatar/icn_beaver' | 'avatar/icn_bird' | 'avatar/icn_bison' | 'avatar/icn_camel' | 'avatar/icn_chameleon' | 'avatar/icn_deer' | 'avatar/icn_dog' | 'avatar/icn_dolphin' | 'avatar/icn_elephant' | 'avatar/icn_fish' | 'avatar/icn_fox' | 'avatar/icn_gorilla' | 'avatar/icn_hippo' | 'avatar/icn_horse' | 'avatar/icn_hyena' | 'avatar/icn_kangaroo' | 'avatar/icn_lemur' | 'avatar/icn_mammel' | 'avatar/icn_monkey' | 'avatar/icn_moose' | 'avatar/icn_panda' | 'avatar/icn_penguin' | 'avatar/icn_porcupine' | 'avatar/icn_quail' | 'avatar/icn_rabbit' | 'avatar/icn_rhino' | 'avatar/icn_sea_horse' | 'avatar/icn_sheep' | 'avatar/icn_snake' | 'avatar/icn_squirrel' | 'avatar/icn_tapir' | 'avatar/icn_turtle' | 'avatar/icn_vulture' | 'avatar/icn_wild1' | 'avatar/icn_wild_bore' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'bell-fill' | 'bell-plus' | 'bell-slash' | 'bell' | 'binoculars' | 'book' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'bullhorn' | 'business-time' | 'calendar-alt' | 'calendar-check' | 'calendar-day' | 'calendar' | 'call' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-right-text' | 'chat-square-quote' | 'check-circle-fill' | 'check-circle' | 'check' | 'chevron-double-left' | 'chevron-double-right' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'clipboard-list-check' | 'clock' | 'close' | 'cloud-fog2-fill' | 'code' | 'cog' | 'cogs' | 'collection' | 'columns-gap-filled' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-front' | 'cubes' | 'dash' | 'dashboard-icn' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'door-closed' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'enter' | 'envelope' | 'errors-icon' | 'event/click' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/link' | 'event/location' | 'event/resize' | 'event/view' | 'exclamation-circle' | 'expand-wide' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch' | 'file-code' | 'file-medical-alt' | 'file-pdf' | 'file' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/state-action' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'folder-plus' | 'folder2' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel-new' | 'funnel' | 'gear-fill' | 'gear' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-3x3' | 'grid-check' | 'grid-horizontal' | 'grip-horizontal' | 'hash' | 'hdd-stack' | 'headset' | 'heart-rate' | 'high-engagement' | 'history' | 'hourglass-start' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/vuejs' | 'journal-code' | 'layer-group' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-arrow' | 'list-ul' | 'list' | 'lock-alt' | 'magic' | 'map-marker-alt' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'network' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'percent' | 'performance-icon' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plus-circle' | 'plus' | 'prev1' | 'puzzle-piece' | 'puzzle' | 'question-circle' | 'question-lg' | 'quote-left' | 'quote-right' | 'quotes' | 'redo-back' | 'redo' | 'remote-control' | 'replay-10' | 'resources-icon' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slack' | 'slash-circle' | 'sliders' | 'social/slack' | 'social/trello' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | 'stopwatch' | 'store' | 'sync-alt' | 'table-new' | 'table' | 'tablet-android' | 'tachometer-slow' | 'tachometer-slowest' | 'tags' | 'team-funnel' | 'telephone-fill' | 'telephone' | 'text-paragraph' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web-vitals' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; +export type IconNames = 'activity' | 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-bar-left' | 'arrow-clockwise' | 'arrow-counterclockwise' | 'arrow-down-short' | 'arrow-down' | 'arrow-repeat' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | 'arrow-up-short' | 'arrow-up' | 'arrows-angle-extend' | 'avatar/icn_bear' | 'avatar/icn_beaver' | 'avatar/icn_bird' | 'avatar/icn_bison' | 'avatar/icn_camel' | 'avatar/icn_chameleon' | 'avatar/icn_deer' | 'avatar/icn_dog' | 'avatar/icn_dolphin' | 'avatar/icn_elephant' | 'avatar/icn_fish' | 'avatar/icn_fox' | 'avatar/icn_gorilla' | 'avatar/icn_hippo' | 'avatar/icn_horse' | 'avatar/icn_hyena' | 'avatar/icn_kangaroo' | 'avatar/icn_lemur' | 'avatar/icn_mammel' | 'avatar/icn_monkey' | 'avatar/icn_moose' | 'avatar/icn_panda' | 'avatar/icn_penguin' | 'avatar/icn_porcupine' | 'avatar/icn_quail' | 'avatar/icn_rabbit' | 'avatar/icn_rhino' | 'avatar/icn_sea_horse' | 'avatar/icn_sheep' | 'avatar/icn_snake' | 'avatar/icn_squirrel' | 'avatar/icn_tapir' | 'avatar/icn_turtle' | 'avatar/icn_vulture' | 'avatar/icn_wild1' | 'avatar/icn_wild_bore' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'bell-fill' | 'bell-plus' | 'bell-slash' | 'bell' | 'binoculars' | 'book' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'bullhorn' | 'business-time' | 'calendar-alt' | 'calendar-check' | 'calendar-day' | 'calendar' | 'call' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'card-checklist' | 'card-text' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-right-text' | 'chat-square-quote' | 'check-circle-fill' | 'check-circle' | 'check' | 'chevron-double-left' | 'chevron-double-right' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'clipboard-list-check' | 'clock' | 'close' | 'cloud-fog2-fill' | 'code' | 'cog' | 'cogs' | 'collection' | 'columns-gap-filled' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-front' | 'cross' | 'cubes' | 'dash' | 'dashboard-icn' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'door-closed' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'enter' | 'envelope' | 'errors-icon' | 'event/click' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/link' | 'event/location' | 'event/resize' | 'event/view' | 'exclamation-circle' | 'expand-wide' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch' | 'file-code' | 'file-medical-alt' | 'file-pdf' | 'file' | 'files' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/state-action' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'folder-plus' | 'folder2' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel-new' | 'funnel' | 'gear-fill' | 'gear' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-1x2' | 'grid-3x3' | 'grid-check' | 'grid-horizontal' | 'grid' | 'grip-horizontal' | 'hash' | 'hdd-stack' | 'headset' | 'heart-rate' | 'high-engagement' | 'history' | 'hourglass-start' | 'ic-errors' | 'ic-network' | 'ic-rage' | 'ic-resources' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/teams-white' | 'integrations/teams' | 'integrations/vuejs' | 'journal-code' | 'layer-group' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-arrow' | 'list-ul' | 'list' | 'lock-alt' | 'magic' | 'map-marker-alt' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'network' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'no-recordings' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'percent' | 'performance-icon' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-bold' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plus-circle' | 'plus-lg' | 'plus' | 'pointer-sessions-search' | 'prev1' | 'puzzle-piece' | 'puzzle' | 'question-circle' | 'question-lg' | 'quote-left' | 'quote-right' | 'quotes' | 'record-circle' | 'redo-back' | 'redo' | 'remote-control' | 'replay-10' | 'resources-icon' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'signpost-split' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slack' | 'slash-circle' | 'sliders' | 'social/slack' | 'social/trello' | 'speedometer2' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | 'stop-record-circle' | 'stopwatch' | 'store' | 'sync-alt' | 'table-new' | 'table' | 'tablet-android' | 'tachometer-slow' | 'tachometer-slowest' | 'tags' | 'team-funnel' | 'telephone-fill' | 'telephone' | 'text-paragraph' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web-vitals' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; interface Props { name: IconNames; @@ -15,6 +15,7 @@ interface Props { const SVG = (props: Props) => { const { name, size = 14, width = size, height = size, fill = '' } = props; switch (name) { + case 'activity': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M6 2a.5.5 0 0 1 .47.33L10 12.036l1.53-4.208A.5.5 0 0 1 12 7.5h3.5a.5.5 0 0 1 0 1h-3.15l-1.88 5.17a.5.5 0 0 1-.94 0L6 3.964 4.47 8.171A.5.5 0 0 1 4 8.5H.5a.5.5 0 0 1 0-1h3.15l1.88-5.17A.5.5 0 0 1 6 2Z"/></svg>; case 'alarm-clock': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8.5 5.5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5z"/><path d="M6.5 0a.5.5 0 0 0 0 1H7v1.07a7.001 7.001 0 0 0-3.273 12.474l-.602.602a.5.5 0 0 0 .707.708l.746-.746A6.97 6.97 0 0 0 8 16a6.97 6.97 0 0 0 3.422-.892l.746.746a.5.5 0 0 0 .707-.708l-.601-.602A7.001 7.001 0 0 0 9 2.07V1h.5a.5.5 0 0 0 0-1h-3zm1.038 3.018a6.093 6.093 0 0 1 .924 0 6 6 0 1 1-.924 0zM0 3.5c0 .753.333 1.429.86 1.887A8.035 8.035 0 0 1 4.387 1.86 2.5 2.5 0 0 0 0 3.5zM13.5 1c-.753 0-1.429.333-1.887.86a8.035 8.035 0 0 1 3.527 3.527A2.5 2.5 0 0 0 13.5 1z"/></svg>; case 'alarm-plus': return <svg viewBox="0 0 512 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M344 272h-72v-72a8 8 0 0 0-8-8h-16a8 8 0 0 0-8 8v72h-72a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h72v72a8 8 0 0 0 8 8h16a8 8 0 0 0 8-8v-72h72a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8zM32 112a80.09 80.09 0 0 1 80-80 79.23 79.23 0 0 1 50 18 253.22 253.22 0 0 1 34.44-10.8C175.89 15.42 145.86 0 112 0A112.14 112.14 0 0 0 0 112c0 25.86 9.17 49.41 24 68.39a255.93 255.93 0 0 1 17.4-31.64A78.94 78.94 0 0 1 32 112zM400 0c-33.86 0-63.89 15.42-84.44 39.25A253.22 253.22 0 0 1 350 50.05a79.23 79.23 0 0 1 50-18 80.09 80.09 0 0 1 80 80 78.94 78.94 0 0 1-9.36 36.75A255.93 255.93 0 0 1 488 180.39c14.79-19 24-42.53 24-68.39A112.14 112.14 0 0 0 400 0zM256 64C132.29 64 32 164.29 32 288a222.89 222.89 0 0 0 54.84 146.54L34.34 487a8 8 0 0 0 0 11.32l11.31 11.31a8 8 0 0 0 11.32 0l52.49-52.5a223.21 223.21 0 0 0 293.08 0L455 509.66a8 8 0 0 0 11.32 0l11.31-11.31a8 8 0 0 0 0-11.32l-52.5-52.49A222.89 222.89 0 0 0 480 288c0-123.71-100.29-224-224-224zm0 416c-105.87 0-192-86.13-192-192S150.13 96 256 96s192 86.13 192 192-86.13 192-192 192z"/></svg>; case 'all-sessions': return <svg viewBox="0 0 27.3 21.7" width={ `${ width }px` } height={ `${ height }px` } ><g><path d="M22.86 20.34a.34.34 0 0 1 .34.34v.68a.34.34 0 0 1-.34.34H4.44a.35.35 0 0 1-.35-.34v-.68a.35.35 0 0 1 .35-.34h18.42ZM25.93 0a1.36 1.36 0 0 1 1.37 1.36v14.91a1.36 1.36 0 0 1-1.37 1.36H1.37A1.36 1.36 0 0 1 0 16.27V1.36A1.36 1.36 0 0 1 1.37 0h24.56Zm0 1.36H1.37v14.91h24.56ZM12.5 6.24a1 1 0 0 1 .5.14l3 1.76a1 1 0 0 1 .36 1.36 1 1 0 0 1-.36.36l-3 1.76a1 1 0 0 1-1.36-.36 1 1 0 0 1-.14-.5V7.24a1 1 0 0 1 1-1Z"/></g></svg>; @@ -23,6 +24,7 @@ const SVG = (props: Props) => { case 'arrow-alt-square-right': return <svg viewBox="0 0 448 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48H48C21.5 32 0 53.5 0 80zm400-16c8.8 0 16 7.2 16 16v352c0 8.8-7.2 16-16 16H48c-8.8 0-16-7.2-16-16V80c0-8.8 7.2-16 16-16h352zm-208 64v64H88c-13.2 0-24 10.8-24 24v80c0 13.2 10.8 24 24 24h104v64c0 28.4 34.5 42.8 54.6 22.6l128-128c12.5-12.5 12.5-32.8 0-45.3l-128-128c-20.1-20-54.6-5.8-54.6 22.7zm160 128L224 384v-96H96v-64h128v-96l128 128z"/></svg>; case 'arrow-bar-left': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M12.5 15a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5zM10 8a.5.5 0 0 1-.5.5H3.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L3.707 7.5H9.5a.5.5 0 0 1 .5.5z"/></svg>; case 'arrow-clockwise': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/><path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/></svg>; + case 'arrow-counterclockwise': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z"/><path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z"/></svg>; case 'arrow-down-short': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 4a.5.5 0 0 1 .5.5v5.793l2.146-2.147a.5.5 0 0 1 .708.708l-3 3a.5.5 0 0 1-.708 0l-3-3a.5.5 0 1 1 .708-.708L7.5 10.293V4.5A.5.5 0 0 1 8 4z"/></svg>; case 'arrow-down': return <svg viewBox="0 0 448 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="m443.5 248.5-7.1-7.1c-4.7-4.7-12.3-4.7-17 0L241 419.9V44c0-6.6-5.4-12-12-12h-10c-6.6 0-12 5.4-12 12v375.9L28.5 241.4c-4.7-4.7-12.3-4.7-17 0l-7.1 7.1c-4.7 4.7-4.7 12.3 0 17l211 211.1c4.7 4.7 12.3 4.7 17 0l211-211.1c4.8-4.8 4.8-12.3.1-17z"/></svg>; case 'arrow-repeat': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41zm-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9z"/><path d="M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z"/></svg>; @@ -97,6 +99,8 @@ const SVG = (props: Props) => { case 'camera-video-off': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M10.961 12.365a1.99 1.99 0 0 0 .522-1.103l3.11 1.382A1 1 0 0 0 16 11.731V4.269a1 1 0 0 0-1.406-.913l-3.111 1.382A2 2 0 0 0 9.5 3H4.272l.714 1H9.5a1 1 0 0 1 1 1v6a1 1 0 0 1-.144.518l.605.847zM1.428 4.18A.999.999 0 0 0 1 5v6a1 1 0 0 0 1 1h5.014l.714 1H2a2 2 0 0 1-2-2V5c0-.675.334-1.272.847-1.634l.58.814zM15 11.73l-3.5-1.555v-4.35L15 4.269v7.462zm-4.407 3.56-10-14 .814-.58 10 14-.814.58z"/></svg>; case 'camera-video': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M0 5a2 2 0 0 1 2-2h7.5a2 2 0 0 1 1.983 1.738l3.11-1.382A1 1 0 0 1 16 4.269v7.462a1 1 0 0 1-1.406.913l-3.111-1.382A2 2 0 0 1 9.5 13H2a2 2 0 0 1-2-2V5zm11.5 5.175 3.5 1.556V4.269l-3.5 1.556v4.35zM2 4a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h7.5a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H2z"/></svg>; case 'camera': return <svg viewBox="0 0 512 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M324.3 64c3.3 0 6.3 2.1 7.5 5.2l22.1 58.8H464c8.8 0 16 7.2 16 16v288c0 8.8-7.2 16-16 16H48c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16h110.2l20.1-53.6c2.3-6.2 8.3-10.4 15-10.4h131m0-32h-131c-20 0-37.9 12.4-44.9 31.1L136 96H48c-26.5 0-48 21.5-48 48v288c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48V144c0-26.5-21.5-48-48-48h-88l-14.3-38c-5.8-15.7-20.7-26-37.4-26zM256 408c-66.2 0-120-53.8-120-120s53.8-120 120-120 120 53.8 120 120-53.8 120-120 120zm0-208c-48.5 0-88 39.5-88 88s39.5 88 88 88 88-39.5 88-88-39.5-88-88-88z"/></svg>; + case 'card-checklist': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h13zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/><path d="M7 5.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm-1.496-.854a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0zM7 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm-1.496-.854a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 0 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0z"/></svg>; + case 'card-text': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h13zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/><path d="M3 5.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zM3 8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9A.5.5 0 0 1 3 8zm0 2.5a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5z"/></svg>; case 'caret-down-fill': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z"/></svg>; case 'caret-left-fill': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="m3.86 8.753 5.482 4.796c.646.566 1.658.106 1.658-.753V3.204a1 1 0 0 0-1.659-.753l-5.48 4.796a1 1 0 0 0 0 1.506z"/></svg>; case 'caret-right-fill': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/></svg>; @@ -134,6 +138,7 @@ const SVG = (props: Props) => { case 'cookies': return <svg viewBox="0 0 512 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M507.44 208.64a15.982 15.982 0 0 0-13.824-12.928 15.962 15.962 0 0 0-16.48 9.312c-5.072 11.2-16.208 18.992-29.12 18.976-14.32.032-26.416-9.632-30.448-22.896-2.432-8.096-10.752-12.896-18.976-10.976-5.056 1.184-9.84 1.872-14.592 1.872-35.248-.064-63.936-28.752-64-64 0-4.752.688-9.536 1.872-14.576 1.936-8.224-2.88-16.56-10.976-18.992C297.632 90.416 287.968 78.32 288 64c-.016-12.928 7.776-24.048 18.976-29.12a15.974 15.974 0 0 0 9.312-16.48A15.983 15.983 0 0 0 303.36 4.56C288.096 1.696 272.288 0 256 0 114.784.032.032 114.784 0 256c.032 141.216 114.784 255.968 256 256 141.216-.032 255.968-114.784 256-256 0-16.288-1.696-32.096-4.56-47.36zm-93.12 205.68C373.696 454.912 317.792 480 256 480s-117.696-25.088-158.32-65.68C57.088 373.696 32 317.792 32 256S57.088 138.304 97.68 97.68C138.304 57.088 194.208 32 256 32c2.88 0 5.696.304 8.56.432-5.344 9.312-8.544 20.032-8.56 31.568.032 23.888 13.28 44.368 32.592 55.296-.304 2.848-.592 5.696-.592 8.704.032 52.976 43.024 95.968 96 96 3.008 0 5.856-.288 8.704-.592C403.632 242.704 424.096 255.968 448 256c11.536-.016 22.256-3.216 31.568-8.56.128 2.848.432 5.68.432 8.56 0 61.792-25.088 117.696-65.68 158.32z"/><circle cx="192" cy="128" r="32"/><circle cx="128" cy="256" r="32"/><circle cx="288" cy="384" r="32"/><circle cx="272" cy="272" r="16"/><circle cx="400" cy="336" r="16"/><circle cx="176" cy="368" r="16"/></svg>; case 'copy': return <svg viewBox="0 0 448 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="m433.941 65.941-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM352 32.491a15.88 15.88 0 0 1 7.431 4.195l51.882 51.883A15.885 15.885 0 0 1 415.508 96H352V32.491zM288 464c0 8.822-7.178 16-16 16H48c-8.822 0-16-7.178-16-16V144c0-8.822 7.178-16 16-16h80v240c0 26.51 21.49 48 48 48h112v48zm128-96c0 8.822-7.178 16-16 16H176c-8.822 0-16-7.178-16-16V48c0-8.822 7.178-16 16-16h144v72c0 13.2 10.8 24 24 24h72v240z"/></svg>; case 'credit-card-front': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4zm2-1a1 1 0 0 0-1 1v1h14V4a1 1 0 0 0-1-1H2zm13 4H1v5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V7z"/><path d="M2 10a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-1z"/></svg>; + case 'cross': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/></svg>; case 'cubes': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M13 1a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h10zM3 0a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V3a3 3 0 0 0-3-3H3z"/><path d="M5.5 4a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm8 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0 8a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm-8 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/></svg>; case 'dash': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8z"/></svg>; case 'dashboard-icn': return <svg viewBox="0 0 276 241" width={ `${ width }px` } height={ `${ height }px` } ><g filter="url(#a)"><rect x="6" y="4" width="264" height="229" rx="6" fill="#fff"/></g><g opacity=".7"><rect x="141" y="14" width="119" height="101" rx="4.244" fill="#000" fillOpacity=".08"/><rect x="141.5" y="14.5" width="118" height="100" rx="3.744" stroke="#000" stroke-opacity=".12"/></g><g opacity=".3"><rect x="16" y="14" width="119" height="101" rx="4.244" fill="#000" fillOpacity=".08"/><rect x="16.5" y="14.5" width="118" height="100" rx="3.744" stroke="#000" stroke-opacity=".12"/></g><g opacity=".46"><rect x="16" y="122" width="244" height="99" rx="4.244" fill="#000" fillOpacity=".08"/><rect x="16.5" y="122.5" width="243" height="98" rx="3.744" stroke="#000" stroke-opacity=".12"/></g><rect opacity=".2" x="149" y="85" width="15" height="20" rx="2" fill="#3EAAAF"/><rect opacity=".4" x="178" y="54" width="15" height="51" rx="2" fill="#3EAAAF"/><rect opacity=".6" x="207" y="62" width="15" height="43" rx="2" fill="#3EAAAF"/><rect opacity=".4" x="236" y="45" width="15" height="60" rx="2" fill="#3EAAAF"/><path opacity=".6" d="M109 62a32.997 32.997 0 0 1-56.334 23.335l9.16-9.162A20.044 20.044 0 0 0 96.045 62H109Z" fill="#3EAAAF"/><path opacity=".2" d="M51.09 83.645a33.002 33.002 0 0 1-6.892-30.457l12.582 3.486a19.945 19.945 0 0 0 4.165 18.408l-9.855 8.563Z" fill="#3EAAAF"/><path opacity=".4" d="M44.652 51.688a33 33 0 0 1 64.32 8.95l-12.948.535a20.04 20.04 0 0 0-39.061-5.435l-12.31-4.05Z" fill="#3EAAAF"/><path d="M150.176 79.433a1 1 0 0 0 1.648 1.134l-1.648-1.134ZM247 29l-11.457 1.437 6.972 9.204L247 29Zm-35.954 23.046-.552.834.552-.834Zm5.777-.185-.604-.797.604.797Zm-36.606-14.317-.823-.567.823.567Zm6.877-1.336-.551.834.551-.834Zm-35.27 44.359 29.217-42.457-1.647-1.133-29.218 42.456 1.648 1.134Zm34.719-43.525 23.951 15.838 1.103-1.668-23.951-15.839-1.103 1.669Zm30.884 15.616 23.003-17.426-1.208-1.594-23.003 17.426 1.208 1.594Zm-6.933.222a6 6 0 0 0 6.933-.222l-1.208-1.594a4 4 0 0 1-4.622.148l-1.103 1.668Zm-29.453-14.77a4 4 0 0 1 5.502-1.068l1.103-1.669a6 6 0 0 0-8.252 1.604l1.647 1.133Z" fill="#3EAAAF"/><rect opacity=".2" x="60" y="102" width="7" height="3" rx="1.5" fill="#3EAAAF"/><rect opacity=".3" x="72" y="102" width="7" height="3" rx="1.5" fill="#3EAAAF"/><rect opacity=".6" x="84" y="102" width="7" height="3" rx="1.5" fill="#3EAAAF"/><path clipRule="evenodd" d="M17 187.928c0-1.01 1.326-1.383 1.853-.522l1.777 2.906c3.63 5.938 11.092 17.813 18.352 19 7.26 1.188 14.721-8.312 21.981-20.187C68.425 177.25 75.685 163 82.945 163c7.462 0 14.722 14.25 21.982 21.375 7.461 7.125 14.721 7.125 21.981 2.375 7.462-4.75 14.722-14.25 22.184-14.25 7.26 0 14.52 9.5 21.981 16.625 7.26 7.125 14.52 11.875 21.982 8.313 7.26-3.563 14.52-15.438 21.982-14.25 7.26 1.187 14.721 15.437 21.981 16.624 7.26 1.188 14.722-10.687 18.352-16.624l1.777-2.907c.527-.861 1.853-.488 1.853.522V217a3 3 0 0 1-3 3H20a3 3 0 0 1-3-3v-29.072Z" fill="url(#b)"/><path d="M258 180.5c-7 11-17.882 29.121-29 14-12.5-17-21.333-11.5-24.5-8-16.5 21.5-30.5 10.5-43-6-10-13.2-19.833-7.833-23.5-3.5-22.5 28-35.5 3.5-48.5-11.5C79 154 67 179 59 194c-8 13.5-21 29.5-41-6.5" stroke="#3EAAAF" stroke-width="2" stroke-linecap="round"/><circle cx="42" cy="206" r="7" fill="#fff" stroke="#3EAAAF" stroke-width="2"/><circle cx="117" cy="187" r="7" fill="#fff" stroke="#3EAAAF" stroke-width="2"/><circle cx="212" cy="183" r="7" fill="#fff" stroke="#3EAAAF" stroke-width="2"/><defs><linearGradient id="b" x1="138" y1="163" x2="138.49" y2="224" gradientUnits="userSpaceOnUse"><stop stopColor="#86C6C9"/><stop offset="1" stopColor="#F6F6F6"/></linearGradient><filter id="a" x="0" y="0" width="276" height="241" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/><feOffset dy="2"/><feGaussianBlur stdDeviation="3"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_59_3139"/><feBlend in="SourceGraphic" in2="effect1_dropShadow_59_3139" result="shape"/></filter></defs></svg>; @@ -171,6 +176,7 @@ const SVG = (props: Props) => { case 'file-medical-alt': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/><path d="m10.273 2.513-.921-.944.715-.698.622.637.89-.011a2.89 2.89 0 0 1 2.924 2.924l-.01.89.636.622a2.89 2.89 0 0 1 0 4.134l-.637.622.011.89a2.89 2.89 0 0 1-2.924 2.924l-.89-.01-.622.636a2.89 2.89 0 0 1-4.134 0l-.622-.637-.89.011a2.89 2.89 0 0 1-2.924-2.924l.01-.89-.636-.622a2.89 2.89 0 0 1 0-4.134l.637-.622-.011-.89a2.89 2.89 0 0 1 2.924-2.924l.89.01.622-.636a2.89 2.89 0 0 1 4.134 0l-.715.698a1.89 1.89 0 0 0-2.704 0l-.92.944-1.32-.016a1.89 1.89 0 0 0-1.911 1.912l.016 1.318-.944.921a1.89 1.89 0 0 0 0 2.704l.944.92-.016 1.32a1.89 1.89 0 0 0 1.912 1.911l1.318-.016.921.944a1.89 1.89 0 0 0 2.704 0l.92-.944 1.32.016a1.89 1.89 0 0 0 1.911-1.912l-.016-1.318.944-.921a1.89 1.89 0 0 0 0-2.704l-.944-.92.016-1.32a1.89 1.89 0 0 0-1.912-1.911l-1.318.016z"/></svg>; case 'file-pdf': return <svg viewBox="0 0 13 14" width={ `${ width }px` } height={ `${ height }px` } ><g clipPath="url(#a)"><path d="M3.493.662A1.584 1.584 0 0 0 1.91 2.247v9.506a1.584 1.584 0 0 0 1.584 1.585h6.338a1.584 1.584 0 0 0 1.585-1.585V2.247A1.585 1.585 0 0 0 9.83.662H3.493Zm0 .792h6.338a.792.792 0 0 1 .792.793v9.506a.792.792 0 0 1-.792.793H3.493a.792.792 0 0 1-.792-.793V2.247a.792.792 0 0 1 .792-.793Z" fillOpacity=".6"/><path d="M3.971 10.238a.641.641 0 0 1-.347-.333c-.154-.307-.103-.615.063-.873.157-.243.417-.45.711-.623a6.084 6.084 0 0 1 1.174-.511c.317-.57.598-1.16.841-1.765a5.759 5.759 0 0 1-.34-1.026c-.068-.317-.095-.63-.037-.9.06-.28.217-.532.515-.652.152-.06.317-.095.477-.06a.555.555 0 0 1 .378.289c.07.13.095.282.1.426a2.54 2.54 0 0 1-.037.486c-.066.404-.213.899-.411 1.421.218.468.478.915.776 1.336a4.558 4.558 0 0 1 1.057.04c.288.051.581.154.76.368a.67.67 0 0 1 .159.41.922.922 0 0 1-.11.447.824.824 0 0 1-.28.33.678.678 0 0 1-.404.108c-.262-.01-.518-.155-.74-.33a4.528 4.528 0 0 1-.721-.753 9.223 9.223 0 0 0-1.582.322 8.96 8.96 0 0 1-.809 1.196c-.23.278-.482.52-.734.624a.628.628 0 0 1-.459.023Zm1.092-1.506c-.131.06-.253.123-.363.188-.26.154-.429.304-.513.434-.074.115-.076.198-.031.286a.18.18 0 0 0 .02.035.213.213 0 0 0 .028-.01c.108-.044.281-.186.503-.453a6.48 6.48 0 0 0 .356-.48Zm1.3-1.054c.264-.062.531-.113.8-.153a9.243 9.243 0 0 1-.404-.68c-.124.281-.256.559-.396.832v.001Zm1.938.357c.118.128.234.237.344.324.19.151.323.2.395.203a.085.085 0 0 0 .055-.012.243.243 0 0 0 .075-.099.345.345 0 0 0 .046-.158.075.075 0 0 0-.02-.05c-.041-.05-.159-.12-.41-.166a3.075 3.075 0 0 0-.485-.042ZM6.724 5.257c.067-.215.12-.434.158-.656.025-.149.034-.272.03-.368a.486.486 0 0 0-.025-.157.41.41 0 0 0-.115.032c-.069.027-.125.084-.155.224-.032.152-.024.371.036.651.02.088.043.18.072.274h-.001Z"/></g><defs><clipPath id="a"><path fill="#fff" transform="translate(.324 .662)" d="M0 0h12.676v12.676H0z"/></clipPath></defs></svg>; case 'file': return <svg viewBox="0 0 384 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M369.9 97.9 286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zm-22.6 22.7c2.1 2.1 3.5 4.6 4.2 7.4H256V32.5c2.8.7 5.3 2.1 7.4 4.2l83.9 83.9zM336 480H48c-8.8 0-16-7.2-16-16V48c0-8.8 7.2-16 16-16h176v104c0 13.3 10.7 24 24 24h104v304c0 8.8-7.2 16-16 16z"/></svg>; + case 'files': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M13 0H6a2 2 0 0 0-2 2 2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2 2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm0 13V4a2 2 0 0 0-2-2H5a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zM3 4a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4z"/></svg>; case 'filter': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2h-11z"/></svg>; case 'filters/arrow-return-right': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M1.5 1.5A.5.5 0 0 0 1 2v4.8a2.5 2.5 0 0 0 2.5 2.5h9.793l-3.347 3.346a.5.5 0 0 0 .708.708l4.2-4.2a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 8.3H3.5A1.5 1.5 0 0 1 2 6.8V2a.5.5 0 0 0-.5-.5z"/></svg>; case 'filters/browser': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855A7.97 7.97 0 0 0 5.145 4H7.5V1.077zM4.09 4a9.267 9.267 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.025 7.025 0 0 0 2.255 4H4.09zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5h2.49zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5H4.847zM8.5 5v2.5h2.99a12.495 12.495 0 0 0-.337-2.5H8.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5H4.51zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5H8.5zM5.145 12c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12H5.145zm.182 2.472a6.696 6.696 0 0 1-.597-.933A9.268 9.268 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472zM3.82 11a13.652 13.652 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5H3.82zm6.853 3.472A7.024 7.024 0 0 0 13.745 12H11.91a9.27 9.27 0 0 1-.64 1.539 6.688 6.688 0 0 1-.597.933zM8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855.173-.324.33-.682.468-1.068H8.5zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm2.802-3.5a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5h2.49zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7.024 7.024 0 0 0-3.072-2.472c.218.284.418.598.597.933zM10.855 4a7.966 7.966 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4h2.355z"/></svg>; @@ -242,9 +248,11 @@ const SVG = (props: Props) => { case 'github': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>; case 'graph-up-arrow': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M0 0h1v15h15v1H0V0Zm10 3.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V4.9l-3.613 4.417a.5.5 0 0 1-.74.037L7.06 6.767l-3.656 5.027a.5.5 0 0 1-.808-.588l4-5.5a.5.5 0 0 1 .758-.06l2.609 2.61L13.445 4H10.5a.5.5 0 0 1-.5-.5Z"/></svg>; case 'graph-up': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M0 0h1v15h15v1H0V0Zm14.817 3.113a.5.5 0 0 1 .07.704l-4.5 5.5a.5.5 0 0 1-.74.037L7.06 6.767l-3.656 5.027a.5.5 0 0 1-.808-.588l4-5.5a.5.5 0 0 1 .758-.06l2.609 2.61 4.15-5.073a.5.5 0 0 1 .704-.07Z"/></svg>; + case 'grid-1x2': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M6 1H1v14h5V1zm9 0h-5v5h5V1zm0 9v5h-5v-5h5zM0 1a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V1zm9 0a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1V1zm1 8a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1h-5z"/></svg>; case 'grid-3x3': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M0 1.5A1.5 1.5 0 0 1 1.5 0h13A1.5 1.5 0 0 1 16 1.5v13a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 0 14.5v-13zM1.5 1a.5.5 0 0 0-.5.5V5h4V1H1.5zM5 6H1v4h4V6zm1 4h4V6H6v4zm-1 1H1v3.5a.5.5 0 0 0 .5.5H5v-4zm1 0v4h4v-4H6zm5 0v4h3.5a.5.5 0 0 0 .5-.5V11h-4zm0-1h4V6h-4v4zm0-5h4V1.5a.5.5 0 0 0-.5-.5H11v4zm-1 0V1H6v4h4z"/></svg>; case 'grid-check': return <svg viewBox="0 0 52 52" width={ `${ width }px` } height={ `${ height }px` } ><path d="M6.5 32.5h9.75a3.25 3.25 0 0 1 3.25 3.25v9.75a3.25 3.25 0 0 1-3.25 3.25H6.5a3.25 3.25 0 0 1-3.25-3.25v-9.75A3.25 3.25 0 0 1 6.5 32.5ZM35.75 3.25h9.75a3.25 3.25 0 0 1 3.25 3.25v9.75a3.25 3.25 0 0 1-3.25 3.25h-9.75a3.25 3.25 0 0 1-3.25-3.25V6.5a3.25 3.25 0 0 1 3.25-3.25Zm0 29.25a3.25 3.25 0 0 0-3.25 3.25v9.75a3.25 3.25 0 0 0 3.25 3.25h9.75a3.25 3.25 0 0 0 3.25-3.25v-9.75a3.25 3.25 0 0 0-3.25-3.25h-9.75Zm0-32.5a6.5 6.5 0 0 0-6.5 6.5v9.75a6.5 6.5 0 0 0 6.5 6.5h9.75a6.5 6.5 0 0 0 6.5-6.5V6.5A6.5 6.5 0 0 0 45.5 0h-9.75ZM6.5 29.25a6.5 6.5 0 0 0-6.5 6.5v9.75A6.5 6.5 0 0 0 6.5 52h9.75a6.5 6.5 0 0 0 6.5-6.5v-9.75a6.5 6.5 0 0 0-6.5-6.5H6.5Zm22.75 6.5a6.5 6.5 0 0 1 6.5-6.5h9.75a6.5 6.5 0 0 1 6.5 6.5v9.75a6.5 6.5 0 0 1-6.5 6.5h-9.75a6.5 6.5 0 0 1-6.5-6.5v-9.75ZM0 6.5A6.5 6.5 0 0 1 6.5 0h9.75a6.5 6.5 0 0 1 6.5 6.5v9.75a6.5 6.5 0 0 1-6.5 6.5H6.5a6.5 6.5 0 0 1-6.5-6.5V6.5Zm17.4 2.775a1.627 1.627 0 0 0-2.3-2.3l-5.35 5.352-2.1-2.102a1.627 1.627 0 1 0-2.3 2.3l3.25 3.25a1.625 1.625 0 0 0 2.3 0l6.5-6.5Z"/></svg>; case 'grid-horizontal': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M2 8a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm0-3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm3 3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm0-3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm3 3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm0-3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm3 3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm0-3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm3 3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm0-3a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg>; + case 'grid': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5v-3zM2.5 2a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zM1 10.5A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z"/></svg>; case 'grip-horizontal': return <svg viewBox="0 0 448 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M424 96h-80c-13.22 0-24 10.77-24 24v80c0 13.23 10.78 24 24 24h80c13.22 0 24-10.77 24-24v-80c0-13.23-10.78-24-24-24zm-8 96h-64v-64h64v64zM264 96h-80c-13.22 0-24 10.77-24 24v80c0 13.23 10.78 24 24 24h80c13.22 0 24-10.77 24-24v-80c0-13.23-10.78-24-24-24zm-8 96h-64v-64h64v64zM104 96H24c-13.22 0-24 10.77-24 24v80c0 13.23 10.78 24 24 24h80c13.22 0 24-10.77 24-24v-80c0-13.23-10.78-24-24-24zm-8 96H32v-64h64v64zm328 96h-80c-13.22 0-24 10.77-24 24v80c0 13.23 10.78 24 24 24h80c13.22 0 24-10.77 24-24v-80c0-13.23-10.78-24-24-24zm-8 96h-64v-64h64v64zm-152-96h-80c-13.22 0-24 10.77-24 24v80c0 13.23 10.78 24 24 24h80c13.22 0 24-10.77 24-24v-80c0-13.23-10.78-24-24-24zm-8 96h-64v-64h64v64zm-152-96H24c-13.22 0-24 10.77-24 24v80c0 13.23 10.78 24 24 24h80c13.22 0 24-10.77 24-24v-80c0-13.23-10.78-24-24-24zm-8 96H32v-64h64v64z"/></svg>; case 'hash': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8.39 12.648a1.32 1.32 0 0 0-.015.18c0 .305.21.508.5.508.266 0 .492-.172.555-.477l.554-2.703h1.204c.421 0 .617-.234.617-.547 0-.312-.188-.53-.617-.53h-.985l.516-2.524h1.265c.43 0 .618-.227.618-.547 0-.313-.188-.524-.618-.524h-1.046l.476-2.304a1.06 1.06 0 0 0 .016-.164.51.51 0 0 0-.516-.516.54.54 0 0 0-.539.43l-.523 2.554H7.617l.477-2.304c.008-.04.015-.118.015-.164a.512.512 0 0 0-.523-.516.539.539 0 0 0-.531.43L6.53 5.484H5.414c-.43 0-.617.22-.617.532 0 .312.187.539.617.539h.906l-.515 2.523H4.609c-.421 0-.609.219-.609.531 0 .313.188.547.61.547h.976l-.516 2.492c-.008.04-.015.125-.015.18 0 .305.21.508.5.508.265 0 .492-.172.554-.477l.555-2.703h2.242l-.515 2.492zm-1-6.109h2.266l-.515 2.563H6.859l.532-2.563z"/></svg>; case 'hdd-stack': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M14 10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-1a1 1 0 0 1 1-1h12zM2 9a2 2 0 0 0-2 2v1a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-1a2 2 0 0 0-2-2H2z"/><path d="M5 11.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0zm-2 0a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0zM14 3a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h12zM2 2a2 2 0 0 0-2 2v1a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H2z"/><path d="M5 4.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0zm-2 0a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z"/></svg>; @@ -253,6 +261,10 @@ const SVG = (props: Props) => { case 'high-engagement': return <svg viewBox="0 0 640 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="m638.59 368.22-33.37-211.59c-8.86-50.26-48.4-90.77-100.66-103.13h-.07a803.14 803.14 0 0 0-369 0C83.17 65.86 43.64 106.36 34.78 156.63L1.41 368.22C-8.9 426.73 38.8 480 101.51 480c49.67 0 93.77-30.07 109.48-74.64l7.52-21.36h203l7.49 21.36C444.72 449.93 488.82 480 538.49 480c62.71 0 110.41-53.27 100.1-111.78zm-45.11 54.88c-13.28 15.82-33.33 24.9-55 24.9-36.2 0-68.07-21.41-79.29-53.27l-7.53-21.36-7.52-21.37H195.86l-7.53 21.37-7.53 21.36C169.58 426.59 137.71 448 101.51 448c-21.66 0-41.71-9.08-55-24.9A59.93 59.93 0 0 1 33 373.2l33.28-211c6.66-37.7 36.72-68.14 76.53-77.57a771.07 771.07 0 0 1 354.38 0c39.84 9.42 69.87 39.86 76.42 77l33.47 212.15c3.11 17.64-1.72 35.16-13.6 49.32zm-339.3-218.74h-42.54v-42.54a9.86 9.86 0 0 0-9.82-9.82h-19.64a9.86 9.86 0 0 0-9.82 9.82v42.54h-42.54a9.86 9.86 0 0 0-9.82 9.82v19.64a9.86 9.86 0 0 0 9.82 9.82h42.54v42.54a9.86 9.86 0 0 0 9.82 9.82h19.64a9.86 9.86 0 0 0 9.82-9.82v-42.54h42.54a9.86 9.86 0 0 0 9.82-9.82v-19.64a9.86 9.86 0 0 0-9.82-9.82zM416 224a32 32 0 1 0 32 32 32 32 0 0 0-32-32zm64-64a32 32 0 1 0 32 32 32 32 0 0 0-32-32z"/></svg>; case 'history': return <svg viewBox="0 0 512 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M20 24h10c6.627 0 12 5.373 12 12v94.625C85.196 57.047 165.239 7.715 256.793 8.001 393.18 8.428 504.213 120.009 504 256.396 503.786 393.181 392.834 504 256 504c-63.926 0-122.202-24.187-166.178-63.908-5.113-4.618-5.354-12.561-.482-17.433l7.069-7.069c4.503-4.503 11.749-4.714 16.482-.454C150.782 449.238 200.935 470 256 470c117.744 0 214-95.331 214-214 0-117.744-95.331-214-214-214-82.862 0-154.737 47.077-190.289 116H164c6.627 0 12 5.373 12 12v10c0 6.627-5.373 12-12 12H20c-6.627 0-12-5.373-12-12V36c0-6.627 5.373-12 12-12zm321.647 315.235 4.706-6.47c3.898-5.36 2.713-12.865-2.647-16.763L272 263.853V116c0-6.627-5.373-12-12-12h-8c-6.627 0-12 5.373-12 12v164.147l84.884 61.734c5.36 3.899 12.865 2.714 16.763-2.646z"/></svg>; case 'hourglass-start': return <svg viewBox="0 0 384 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M368 32h4c6.627 0 12-5.373 12-12v-8c0-6.627-5.373-12-12-12H12C5.373 0 0 5.373 0 12v8c0 6.627 5.373 12 12 12h4c0 91.821 44.108 193.657 129.646 224C59.832 286.441 16 388.477 16 480h-4c-6.627 0-12 5.373-12 12v8c0 6.627 5.373 12 12 12h360c6.627 0 12-5.373 12-12v-8c0-6.627-5.373-12-12-12h-4c0-91.821-44.108-193.657-129.646-224C324.168 225.559 368 123.523 368 32zM48 32h288c0 110.457-64.471 200-144 200S48 142.457 48 32zm288 448H48c0-110.457 64.471-200 144-200s144 89.543 144 200zM285.621 96H98.379a12.01 12.01 0 0 1-11.602-8.903 199.464 199.464 0 0 1-2.059-8.43C83.054 71.145 88.718 64 96.422 64h191.157c7.704 0 13.368 7.145 11.704 14.667a199.464 199.464 0 0 1-2.059 8.43A12.013 12.013 0 0 1 285.621 96zm-15.961 50.912a141.625 141.625 0 0 1-6.774 8.739c-2.301 2.738-5.671 4.348-9.248 4.348H130.362c-3.576 0-6.947-1.61-9.248-4.348a142.319 142.319 0 0 1-6.774-8.739c-5.657-7.91.088-18.912 9.813-18.912h135.694c9.725 0 15.469 11.003 9.813 18.912z"/></svg>; + case 'ic-errors': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/><path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/></svg>; + case 'ic-network': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M7.646 10.854a.5.5 0 0 0 .708 0l2-2a.5.5 0 0 0-.708-.708L8.5 9.293V5.5a.5.5 0 0 0-1 0v3.793L6.354 8.146a.5.5 0 1 0-.708.708l2 2z"/><path d="M4.406 3.342A5.53 5.53 0 0 1 8 2c2.69 0 4.923 2 5.166 4.579C14.758 6.804 16 8.137 16 9.773 16 11.569 14.502 13 12.687 13H3.781C1.708 13 0 11.366 0 9.318c0-1.763 1.266-3.223 2.942-3.593.143-.863.698-1.723 1.464-2.383zm.653.757c-.757.653-1.153 1.44-1.153 2.056v.448l-.445.049C2.064 6.805 1 7.952 1 9.318 1 10.785 2.23 12 3.781 12h8.906C13.98 12 15 10.988 15 9.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 4.825 10.328 3 8 3a4.53 4.53 0 0 0-2.941 1.1z"/></svg>; + case 'ic-rage': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/><path d="M4.285 12.433a.5.5 0 0 0 .683-.183A3.498 3.498 0 0 1 8 10.5c1.295 0 2.426.703 3.032 1.75a.5.5 0 0 0 .866-.5A4.498 4.498 0 0 0 8 9.5a4.5 4.5 0 0 0-3.898 2.25.5.5 0 0 0 .183.683zm6.991-8.38a.5.5 0 1 1 .448.894l-1.009.504c.176.27.285.64.285 1.049 0 .828-.448 1.5-1 1.5s-1-.672-1-1.5c0-.247.04-.48.11-.686a.502.502 0 0 1 .166-.761l2-1zm-6.552 0a.5.5 0 0 0-.448.894l1.009.504A1.94 1.94 0 0 0 5 6.5C5 7.328 5.448 8 6 8s1-.672 1-1.5c0-.247-.04-.48-.11-.686a.502.502 0 0 0-.166-.761l-2-1z"/></svg>; + case 'ic-resources': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z"/><path d="M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5L9.5 0zm0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/></svg>; case 'id-card': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h13zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/><path d="M3 8.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm0-5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5v-1z"/></svg>; case 'image': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M4.502 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/><path d="M14.002 13a2 2 0 0 1-2 2h-10a2 2 0 0 1-2-2V5A2 2 0 0 1 2 3a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v8a2 2 0 0 1-1.998 2zM14 2H4a1 1 0 0 0-1 1h9.002a2 2 0 0 1 2 2v7A1 1 0 0 0 15 11V3a1 1 0 0 0-1-1zM2.002 4a1 1 0 0 0-1 1v8l2.646-2.354a.5.5 0 0 1 .63-.062l2.66 1.773 3.71-3.71a.5.5 0 0 1 .577-.094l1.777 1.947V5a1 1 0 0 0-1-1h-10z"/></svg>; case 'info-circle-fill': return <svg viewBox="0 0 36 36" width={ `${ width }px` } height={ `${ height }px` } ><path d="M17.75 35.5a17.75 17.75 0 1 0 0-35.5 17.75 17.75 0 0 0 0 35.5Zm2.064-20.883-2.22 10.44c-.155.754.065 1.182.675 1.182.43 0 1.08-.155 1.522-.546l-.195.923c-.637.768-2.041 1.327-3.25 1.327-1.56 0-2.224-.937-1.793-2.927l1.637-7.694c.142-.65.014-.886-.637-1.043l-1-.18.182-.845 5.08-.637h-.002Zm-2.064-2.414a2.219 2.219 0 1 1 0-4.437 2.219 2.219 0 0 1 0 4.437Z"/></svg>; @@ -289,6 +301,8 @@ const SVG = (props: Props) => { case 'integrations/stackdriver': return <svg viewBox="0 0 65 57" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" font-family="Roboto" font-size="14" text-anchor="middle" width={ `${ width }px` } height={ `${ height }px` } fill={ `${ fill }` }><use xlinkHref="#a" x=".5" y=".5"/><symbol id="a" overflow="visible"><g stroke="none"><path d="m47.83 55.172-16-29.834-15.984 29.84z" fill="#009245"/><path d="m31.83 27.617-16 27.565L0 27.617z" fill="#006837"/><path d="M0 27.627 15.97.01h31.942L31.905 27.627z" fill="#39b54a"/><path d="M47.83 55.172 31.904 27.618 47.91 0 64 27.585z" fill="#8cc63f"/></g></symbol></svg>; case 'integrations/sumologic-text': return <svg viewBox="0 0 577 107.3" width={ `${ width }px` } height={ `${ height }px` } fill={ `${ fill }` }><path d="M118.3 23.8v60.4h-14.1v-6.4C101 83 95.1 86 86.5 86c-14.3 0-21.4-7.4-21.4-19.2v-43h15.4v38.9c0 6.3 3.4 10 10 10 7.7 0 12.4-4.4 12.4-12.5V23.8h15.4zm100 18.2v42.2h-15.4V46.6c0-7.2-2.9-11.6-9.6-11.6s-10.6 5-10.6 12v37.2h-15.4V46.6c0-7.7-3.2-11.6-9.6-11.6-6.8 0-10.6 5-10.6 12v37.2h-15.4V23.8H146v6.7c3.5-5.8 9.3-8.7 17.2-8.7 7.7 0 13.5 3.2 16.8 8.9 4.1-5.9 10.2-8.9 18.1-8.9 12.9 0 20.2 7.7 20.2 20.2zm123.6 42.2h-15.4V.2h15.4v84zm167 0h-15.4V23.8h15.4v60.4zm-15.7-69.5h15.9V0h-15.9v14.7zm73.4 48.9c-6.3 6.7-10.6 9.3-16.4 9.3-6.9 0-11.8-4-14-10.2V45.1c2.3-6.3 7.2-10.3 14.2-10.3 5.1 0 9.2 1.8 15.7 7.9l9.6-9.5c-7.7-8.4-15.2-11.5-25.3-11.5-14.1 0-25 7.4-29.3 19.8v24.8C525.3 78.7 536 86 550 86c10.9 0 18.4-3.6 26.8-14l-10.2-8.4zM31.7 47c-4.2-.9-7.2-1.7-8.9-2.1-2.1-.6-3.7-1.4-4.5-2.5v-4.6c1.4-2.2 4.8-3.6 9.5-3.6 6.5 0 11 1.5 18.1 7.1l8.3-10c-8.4-7-15.6-9.5-26-9.5-12 0-20.3 4.9-23.6 11.9v15.2c2.6 5.1 8.6 8 20.6 10.6 4.3 1 7.2 1.7 8.9 2 2.2.7 4.2 1.6 5.3 3.2V70c-1.5 2.4-5.1 3.7-9.8 3.7-3.4.1-6.7-.6-9.8-1.9-3-1.2-6.5-3.5-10.9-7.1L0 74.6C9.6 83.1 17.7 86 29.2 86c12.3 0 21-4.7 24.3-12.2V58.5C50.7 52.8 44 49.8 31.7 47zm227-25.3c-13.9 0-24.8 7.5-29.3 19.5v25.4c4.4 12 15.4 19.5 29.3 19.5s24.7-7.5 29.1-19.4V41.1c-4.5-11.9-15.4-19.4-29.1-19.4zm14.2 41.1c-2.4 6.4-7.5 10.2-14.3 10.2-6.7 0-12-3.8-14.4-10.2V45c2.4-6.4 7.5-10.3 14.4-10.3 6.8 0 11.8 3.9 14.2 10.3l.1 17.8zm110.2-41.1c-13.9 0-24.8 7.5-29.3 19.5v25.4c4.5 12 15.4 19.5 29.3 19.5s24.7-7.5 29.1-19.4V41.1c-4.4-11.9-15.3-19.4-29.1-19.4zm14.2 41.1c-2.4 6.4-7.5 10.2-14.2 10.2s-12-3.8-14.4-10.2V45c2.4-6.4 7.5-10.3 14.4-10.3s11.8 3.9 14.2 10.3v17.8zm68.5-39v6.7c-3.9-5.8-10-8.7-18.4-8.7-11.4 0-20.6 7-24.6 18.1v24.9c3.9 11.1 13.1 18 24.6 18 7.7 0 13.5-2.6 17.6-7.7v4.8c0 10-5.8 14.7-14.8 14.7-6.7 0-12.2-2-21-7.2l-7.5 11.1c8.5 5.7 18.5 8.8 28.7 8.9 17 0 29.5-9.1 29.5-26.1V23.8h-14.1zm-1.9 36.7c-2.3 5.7-7.1 9.3-13.1 9.3s-10.8-3.5-13.1-9.3V44c2.3-5.7 7-9.2 13.1-9.2s10.8 3.5 13.1 9.2v16.5z"/></svg>; case 'integrations/sumologic': return <svg viewBox="0 0 1024 1024" width={ `${ width }px` } height={ `${ height }px` } fill={ `${ fill }` }><circle cx="512" cy="512" r="512"/><path d="M352.9 361.4c-12.7-2.7-21.8-5.1-26.9-6.3-6.3-1.8-11.2-4.2-13.6-7.6v-13.9c4.2-6.6 14.5-10.9 28.7-10.9 19.6 0 33.2 4.5 54.7 21.5l25.1-30.2c-25.4-21.2-47.1-28.7-78.6-28.7-36.3 0-61.3 14.8-71.3 36v45.9c7.9 15.4 26 24.2 62.2 32 13 3 21.8 5.1 26.9 6 6.6 2.1 12.7 4.8 16 9.7v16c-4.5 7.3-15.4 11.2-29.6 11.2-10.3.3-20.2-1.8-29.6-5.7-9.1-3.6-19.6-10.6-32.9-21.5l-26.9 29.9c29 25.7 53.5 34.4 88.2 34.4 37.2 0 63.5-14.2 73.4-36.9v-46.2c-8.4-17.2-28.7-26.2-65.8-34.7zm401.5-70.1v182.5h-42.6v-19.3c-9.7 15.7-27.5 24.8-53.5 24.8-43.2 0-64.7-22.4-64.7-58v-130h46.5v117.5c0 19 10.3 30.2 30.2 30.2 23.3 0 37.5-13.3 37.5-37.8v-110h46.6zM531.8 606.9v127.5h-46.5V620.8c0-21.8-8.8-35.1-29-35.1s-32 15.1-32 36.3v112.4h-46.5V620.8c0-23.3-9.7-35.1-29-35.1-20.5 0-32 15.1-32 36.3v112.4h-46.5V551.9h43.2v20.2c10.6-17.5 28.1-26.3 52-26.3 23.3 0 40.8 9.7 50.8 26.9 12.4-17.8 30.8-26.9 54.7-26.9 38.8.1 60.8 23.4 60.8 61.1zm139.8-62.8c-42 0-74.9 22.7-88.5 58.9v76.8c13.3 36.3 46.5 58.9 88.5 58.9s74.6-22.7 87.9-58.6v-77.4c-13.5-35.9-46.5-58.6-87.9-58.6zm43 124.2c-7.3 19.3-22.7 30.8-43.2 30.8-20.2 0-36.3-11.5-43.5-30.8v-53.8c7.3-19.3 22.7-31.1 43.5-31.1 20.5 0 35.7 11.8 42.9 31.1l.3 53.8z"/></svg>; + case 'integrations/teams-white': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } fill={ `${ fill }` }><path d="M9.186 4.797a2.42 2.42 0 1 0-2.86-2.448h1.178c.929 0 1.682.753 1.682 1.682v.766Zm-4.295 7.738h2.613c.929 0 1.682-.753 1.682-1.682V5.58h2.783a.7.7 0 0 1 .682.716v4.294a4.197 4.197 0 0 1-4.093 4.293c-1.618-.04-3-.99-3.667-2.35Zm10.737-9.372a1.674 1.674 0 1 1-3.349 0 1.674 1.674 0 0 1 3.349 0Zm-2.238 9.488c-.04 0-.08 0-.12-.002a5.19 5.19 0 0 0 .381-2.07V6.306a1.692 1.692 0 0 0-.15-.725h1.792c.39 0 .707.317.707.707v3.765a2.598 2.598 0 0 1-2.598 2.598h-.013Z"/><path d="M.682 3.349h6.822c.377 0 .682.305.682.682v6.822a.682.682 0 0 1-.682.682H.682A.682.682 0 0 1 0 10.853V4.03c0-.377.305-.682.682-.682Zm5.206 2.596v-.72h-3.59v.72h1.357V9.66h.87V5.945h1.363Z"/></svg>; + case 'integrations/teams': return <svg viewBox="-334.325 -518.333 2897.483 3110.001" width={ `${ width }px` } height={ `${ height }px` } fill={ `${ fill }` }><path d="M1554.637 777.5h575.713c54.391 0 98.483 44.092 98.483 98.483v524.398c0 199.901-162.051 361.952-361.952 361.952h-1.711c-199.901.028-361.975-162-362.004-361.901V828.971c.001-28.427 23.045-51.471 51.471-51.471z" fill="#5059C9"/><circle r="233.25" cy="440.583" cx="1943.75" fill="#5059C9"/><circle r="336.917" cy="336.917" cx="1218.083" fill="#7B83EB"/><path d="M1667.323 777.5H717.01c-53.743 1.33-96.257 45.931-95.01 99.676v598.105c-7.505 322.519 247.657 590.16 570.167 598.053 322.51-7.893 577.671-275.534 570.167-598.053V877.176c1.245-53.745-41.268-98.346-95.011-99.676z" fill="#7B83EB"/><path d="M1244 777.5v838.145c-.258 38.435-23.549 72.964-59.09 87.598a91.856 91.856 0 0 1-35.765 7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833a631.287 631.287 0 0 1-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52z" opacity=".1"/><path d="M1192.167 777.5v889.978a91.802 91.802 0 0 1-7.257 35.765c-14.634 35.541-49.163 58.833-87.598 59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833-7.257-17.623-12.958-34.21-18.142-51.833a631.282 631.282 0 0 1-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52z" opacity=".2"/><path d="M1192.167 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855h-447.84A631.282 631.282 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52z" opacity=".2"/><path d="M1140.333 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855H649.472A631.282 631.282 0 0 1 622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52z" opacity=".2"/><path d="M1244 509.522v163.275c-8.812.518-17.105 1.037-25.917 1.037-8.812 0-17.105-.518-25.917-1.037a284.472 284.472 0 0 1-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003a288.02 288.02 0 0 1-16.587-51.833h258.648c52.305.198 94.657 42.549 94.856 94.854z" opacity=".1"/><path d="M1192.167 561.355v111.442a284.472 284.472 0 0 1-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z" opacity=".2"/><path d="M1192.167 561.355v111.442a284.472 284.472 0 0 1-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z" opacity=".2"/><path d="M1140.333 561.355v103.148c-104.963-24.857-191.679-98.469-233.25-198.003h138.395c52.305.199 94.656 42.551 94.855 94.855z" opacity=".2"/><linearGradient gradientTransform="matrix(1 0 0 -1 0 2075.333)" y2="394.261" x2="942.234" y1="1683.073" x1="198.099" gradientUnits="userSpaceOnUse" id="a"><stop offset="0" stopColor="#5a62c3"/><stop offset=".5" stopColor="#4d55bd"/><stop offset="1" stopColor="#3940ab"/></linearGradient><path d="M95.01 466.5h950.312c52.473 0 95.01 42.538 95.01 95.01v950.312c0 52.473-42.538 95.01-95.01 95.01H95.01c-52.473 0-95.01-42.538-95.01-95.01V561.51c0-52.472 42.538-95.01 95.01-95.01z" fill="url(#a)"/><path d="M820.211 828.193h-189.97v517.297h-121.03V828.193H320.123V727.844h500.088z" fill="#FFF"/></svg>; case 'integrations/vuejs': return <svg viewBox="0 0 120 120" width={ `${ width }px` } height={ `${ height }px` } fill={ `${ fill }` }><rect fill="#fff" height="120" rx="6.01" width="120"/><path d="M87.83 20h18.55L60 100 13.62 20H49.1L60 38.55 70.67 20z" fill="#41b883"/><path d="M13.62 20 60 100l46.38-80H87.83L60 68 31.94 20z" fill="#41b883"/><path d="M31.94 20 60 68.23 87.83 20H70.67L60 38.55 49.1 20z" fill="#35495e"/></svg>; case 'journal-code': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8.646 5.646a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708-.708L10.293 8 8.646 6.354a.5.5 0 0 1 0-.708zm-1.292 0a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0 0 .708l2 2a.5.5 0 0 0 .708-.708L5.707 8l1.647-1.646a.5.5 0 0 0 0-.708z"/><path d="M3 0h10a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-1h1v1a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v1H1V2a2 2 0 0 1 2-2z"/><path d="M1 5v-.5a.5.5 0 0 1 1 0V5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0V8h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0v.5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1z"/></svg>; case 'layer-group': return <svg viewBox="0 0 512 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M512 256.01c0-9.98-5.81-18.94-14.77-22.81l-99.74-43.27 99.7-43.26c9-3.89 14.81-12.84 14.81-22.81s-5.81-18.92-14.77-22.79L271.94 3.33c-10.1-4.44-21.71-4.45-31.87-.02L14.81 101.06C5.81 104.95 0 113.9 0 123.87s5.81 18.92 14.77 22.79l99.73 43.28-99.7 43.26C5.81 237.08 0 246.03 0 256.01c0 9.97 5.81 18.92 14.77 22.79l99.72 43.26-99.69 43.25C5.81 369.21 0 378.16 0 388.14c0 9.97 5.81 18.92 14.77 22.79l225.32 97.76a40.066 40.066 0 0 0 15.9 3.31c5.42 0 10.84-1.1 15.9-3.31l225.29-97.74c9-3.89 14.81-12.84 14.81-22.81 0-9.98-5.81-18.94-14.77-22.81l-99.72-43.26 99.69-43.25c9-3.89 14.81-12.84 14.81-22.81zM45.23 123.87l208.03-90.26.03-.02c1.74-.71 3.65-.76 5.45.02l208.03 90.26-208.03 90.27c-1.81.77-3.74.77-5.48 0L45.23 123.87zm421.54 264.27L258.74 478.4c-1.81.77-3.74.77-5.48 0L45.23 388.13l110.76-48.06 84.11 36.49a40.066 40.066 0 0 0 15.9 3.31c5.42 0 10.84-1.1 15.9-3.31l84.11-36.49 110.76 48.07zm-208.03-41.87c-1.81.77-3.74.77-5.48 0L45.23 256 156 207.94l84.1 36.5a40.066 40.066 0 0 0 15.9 3.31c5.42 0 10.84-1.1 15.9-3.31l84.1-36.49 110.77 48.07-208.03 90.25z"/></svg>; @@ -313,6 +327,7 @@ const SVG = (props: Props) => { case 'no-dashboard': return <svg viewBox="0 0 100 100" width={ `${ width }px` } height={ `${ height }px` } ><rect width="100" height="100" rx="13.158" fillOpacity=".08"/><g clipPath="url(#a)" fillOpacity=".5"><path d="M27.417 33.333a2.083 2.083 0 1 0 0-4.166 2.083 2.083 0 0 0 0 4.166Zm8.333-2.083a2.083 2.083 0 1 1-4.167 0 2.083 2.083 0 0 1 4.167 0Zm4.167 2.083a2.083 2.083 0 1 0 0-4.166 2.083 2.083 0 0 0 0 4.166Z"/><path d="M25.333 20.833A8.333 8.333 0 0 0 17 29.167v41.666a8.334 8.334 0 0 0 8.333 8.334h50a8.333 8.333 0 0 0 8.334-8.334V29.167a8.333 8.333 0 0 0-8.334-8.334h-50ZM79.5 29.167V37.5H21.167v-8.333A4.167 4.167 0 0 1 25.333 25h50a4.167 4.167 0 0 1 4.167 4.167ZM25.333 75a4.167 4.167 0 0 1-4.166-4.167V41.667H79.5v29.166A4.167 4.167 0 0 1 75.333 75h-50Z"/></g><defs><clipPath id="a"><path fill="#fff" transform="translate(17 20)" d="M0 0h66.667v60H0z"/></clipPath></defs></svg>; case 'no-metrics-chart': return <svg viewBox="0 0 250 78" width={ `${ width }px` } height={ `${ height }px` } ><path clipRule="evenodd" d="m239.854 9.853-58.513 58.8L93.005 29.55 9.44 68.51l-.422-.906L92.995 28.45l88.122 39.01 58.029-58.314.708.706Z" fill="#C2C2C2"/><path d="M9.66 77.694c5.334 0 9.659-4.325 9.659-9.66 0-5.334-4.325-9.66-9.66-9.66C4.325 58.375 0 62.7 0 68.035c0 5.335 4.325 9.66 9.66 9.66Z" fill="#C7CCF9"/><path d="M92.985 38.907c5.334 0 9.659-4.325 9.659-9.66 0-5.334-4.325-9.659-9.66-9.659a9.66 9.66 0 0 0-9.659 9.66 9.66 9.66 0 0 0 9.66 9.66Z" fill="#B4E4E7"/><path d="M180.31 77.694c5.335 0 9.659-4.325 9.659-9.66 0-5.334-4.324-9.66-9.659-9.66-5.335 0-9.66 4.326-9.66 9.66 0 5.335 4.325 9.66 9.66 9.66Z" fill="#C7CCF9"/><path d="M239.659 19.319c5.335 0 9.66-4.325 9.66-9.66 0-5.334-4.325-9.659-9.66-9.659C234.325 0 230 4.325 230 9.66c0 5.334 4.325 9.659 9.659 9.659Z" fill="#B4E4E7"/></svg>; case 'no-metrics': return <svg viewBox="0 0 100 100" width={ `${ width }px` } height={ `${ height }px` } ><rect width="100" height="100" rx="13.158" fillOpacity=".08"/><g clipPath="url(#a)" fillOpacity=".5"><path d="M36.875 65A1.875 1.875 0 0 1 35 63.125v-7.5a1.875 1.875 0 0 1 1.875-1.875h3.75a1.875 1.875 0 0 1 1.875 1.875v7.5A1.875 1.875 0 0 1 40.625 65h-3.75Zm11.25 0a1.875 1.875 0 0 1-1.875-1.875v-15a1.875 1.875 0 0 1 1.875-1.875h3.75a1.875 1.875 0 0 1 1.875 1.875v15A1.875 1.875 0 0 1 51.875 65h-3.75Zm11.25 0a1.875 1.875 0 0 1-1.875-1.875v-22.5a1.875 1.875 0 0 1 1.875-1.875h3.75A1.875 1.875 0 0 1 65 40.625v22.5A1.875 1.875 0 0 1 63.125 65h-3.75Z"/><path d="M35 20a7.5 7.5 0 0 0-7.5 7.5v45A7.5 7.5 0 0 0 35 80h30a7.5 7.5 0 0 0 7.5-7.5v-45A7.5 7.5 0 0 0 65 20H35Zm0 3.75h30a3.75 3.75 0 0 1 3.75 3.75v45A3.75 3.75 0 0 1 65 76.25H35a3.75 3.75 0 0 1-3.75-3.75v-45A3.75 3.75 0 0 1 35 23.75Z"/></g><defs><clipPath id="a"><path fill="#fff" transform="translate(20 20)" d="M0 0h60v60H0z"/></clipPath></defs></svg>; + case 'no-recordings': return <svg viewBox="0 0 250 100" width={ `${ width }px` } height={ `${ height }px` } ><rect width="250" height="100" rx="13.158" fill="#3EAAAF" fillOpacity=".08"/><rect opacity=".6" x="86.842" y="28.579" width="138.158" height="14.474" rx="6.579" fillOpacity=".5"/><rect opacity=".3" x="86.842" y="55.579" width="38.158" height="14.474" rx="6.579" fillOpacity=".5"/><rect opacity=".3" x="129.842" y="55.579" width="18.158" height="14.474" rx="6.579" fillOpacity=".5"/><g clipPath="url(#a)"><path d="M46 73.625a23.625 23.625 0 1 1 0-47.25 23.625 23.625 0 0 1 0 47.25ZM46 77a27 27 0 1 0 0-54 27 27 0 0 0 0 54Z" fillOpacity=".5"/><g clipPath="url(#b)"><path d="M56.125 50a10.125 10.125 0 1 1-20.25 0 10.125 10.125 0 0 1 20.25 0Zm-13.474-4.245a.634.634 0 0 0-.896.896L45.105 50l-3.35 3.349a.636.636 0 0 0 0 .896.634.634 0 0 0 .896 0L46 50.895l3.349 3.35a.636.636 0 0 0 .896 0 .635.635 0 0 0 0-.896L46.895 50l3.35-3.349a.636.636 0 0 0 0-.896.635.635 0 0 0-.896 0L46 49.105l-3.349-3.35Z" fillOpacity=".5"/></g></g><defs><clipPath id="a"><path fill="#fff" transform="translate(19 23)" d="M0 0h54v54H0z"/></clipPath><clipPath id="b"><path fill="#fff" transform="translate(35.875 39.875)" d="M0 0h20.25v20.25H0z"/></clipPath></defs></svg>; case 'os/android': return <svg viewBox="0 0 419 519" width={ `${ width }px` } height={ `${ height }px` } ><path d="M271.926 51.66C315.852 74.373 345.991 120.142 346 172.9c0 5.033-4.077 9.1-9.1 9.1H82.1a9.097 9.097 0 0 1-9.1-9.1c0-52.767 30.148-98.537 74.074-121.24L124.351 13.79c-2.584-4.313-1.192-9.9 3.122-12.484 4.313-2.585 9.9-1.202 12.485 3.12l23.978 39.965c14.278-5.077 29.566-7.99 45.564-7.99 15.998 0 31.286 2.913 45.564 7.99l23.978-39.964a9.08 9.08 0 0 1 12.485-3.121c4.314 2.584 5.706 8.17 3.122 12.484L271.926 51.66ZM91.546 163.801h235.908C322.795 102.808 271.671 54.61 209.5 54.61c-62.171 0-113.295 48.2-117.954 109.192ZM273.993 104a6.006 6.006 0 0 1 6.007 6v12c0 3.314-2.685 6-5.998 6h-12.004a5.998 5.998 0 0 1-5.998-6v-12c0-3.314 2.685-6 5.998-6h11.995Zm-116.99 0a5.998 5.998 0 0 1 5.997 6v12c0 3.314-2.685 6-5.998 6h-12.004a5.998 5.998 0 0 1-5.998-6v-12c0-3.314 2.685-6 5.998-6h12.004ZM336.9 191c5.032 0 9.1 4.073 9.1 9.111v183.78c0 24.263-19.729 43.998-43.99 43.998H291.4v54.721c0 20.063-16.325 36.39-36.4 36.39s-36.4-16.327-36.4-36.39V427.89h-18.2v54.721c0 20.063-16.325 36.39-36.4 36.39s-36.4-16.327-36.4-36.39V427.89h-10.61c-24.252 0-43.99-19.735-43.99-43.998v-183.78c0-5.038 4.077-9.111 9.1-9.111h254.8Zm-9.1 192.891V209.222H91.2v174.67c0 14.204 11.575 25.775 25.799 25.775H136.7c5.023 0 9.1 4.072 9.1 9.11v63.833c0 10.013 8.163 18.168 18.2 18.168 10.037 0 18.2-8.146 18.2-18.168v-63.832c0-5.039 4.077-9.111 9.1-9.111h36.4c5.023 0 9.1 4.072 9.1 9.11v63.833c0 10.013 8.163 18.168 18.2 18.168 10.037 0 18.2-8.146 18.2-18.168v-63.832c0-5.039 4.077-9.111 9.1-9.111h19.71c14.224 0 25.79-11.562 25.79-25.776ZM387.5 191c17.37 0 31.5 14.298 31.5 31.87v127.26c0 17.572-14.13 31.87-31.5 31.87-17.37 0-31.5-14.298-31.5-31.87V222.87c0-17.572 14.13-31.87 31.5-31.87ZM401 350.13V222.87c0-7.54-6.057-13.68-13.5-13.68s-13.5 6.14-13.5 13.68v127.26c0 7.54 6.057 13.68 13.5 13.68s13.5-6.14 13.5-13.68ZM31.5 191c17.37 0 31.5 14.298 31.5 31.87v127.26C63 367.702 48.87 382 31.5 382 14.13 382 0 367.702 0 350.13V222.87C0 205.298 14.13 191 31.5 191ZM45 350.13V222.87c0-7.54-6.057-13.68-13.5-13.68S18 215.33 18 222.87v127.26c0 7.54 6.057 13.68 13.5 13.68S45 357.67 45 350.13Z"/></svg>; case 'os/chrome_os': return <svg viewBox="0 0 517 517" width={ `${ width }px` } height={ `${ height }px` } ><path d="M148.978 223c8.725-25.136 24.616-44.93 47.674-59.383 23.058-14.453 48.297-21.05 75.718-19.794L464 154.191c-20.565-41.474-50.79-73.835-90.674-97.086C337.181 36.368 298.232 26 256.478 26c-34.275 0-67.304 7.54-99.087 22.622C125.61 63.703 98.811 85.068 77 112.718L148.978 223Zm61.483-37.353c-18.07 11.327-30.15 26.373-36.92 45.879l-17.375 50.057-111.15-170.3 11.571-14.668C77 67 110.845 41.93 146.245 25.132 181.488 8.41 218.321 0 256.478 0c46.292 0 89.702 11.556 129.787 34.553l.155.09c44.282 25.814 78.05 61.971 100.874 107.998l19.794 39.92-236.01-12.77c-22.108-.994-42.08 4.237-60.617 15.856ZM179 258.5c0 21.806 7.583 40.34 22.75 55.604C216.917 329.368 235.333 337 257 337s40.083-7.632 55.25-22.896C327.417 298.84 335 280.306 335 258.5c0-21.806-7.583-40.34-22.75-55.604C297.083 187.632 278.667 180 257 180s-40.083 7.632-55.25 22.896C186.583 218.16 179 236.694 179 258.5Zm-26 0c0-28.647 10.282-53.777 30.307-73.93C203.363 164.384 228.422 154 257 154c28.578 0 53.637 10.384 73.693 30.57C350.718 204.723 361 229.853 361 258.5c0 28.647-10.282 53.777-30.307 73.93C310.637 352.616 285.578 363 257 363c-28.578 0-53.637-10.384-73.693-30.57C163.282 312.277 153 287.147 153 258.5ZM474.853 175l-129.839 6.535c17.437 20.54 26.466 44.348 27.089 71.424.623 27.075-6.539 52.128-21.484 75.157L246 489.636c46.082 2.49 89.05-7.78 128.905-30.81 33.005-19.296 59.47-44.504 79.398-75.625 19.928-31.121 31.76-65.043 35.496-101.766 3.736-36.724-1.245-72.201-14.946-106.435Zm24.14-11.66c15.26 38.132 20.834 79.828 16.672 120.726-4.149 40.78-17.341 78.602-39.466 113.155-22.164 34.616-51.648 62.698-88.172 84.05l-.113.067c-44.226 25.555-92.201 37.021-143.317 34.26-10.485-.58-18.184-1.113-23.097-1.598-5.048-.498-12.29-1.44-21.725-2.824l24.403-35.675 104.63-161.538c12.111-18.661 17.804-38.576 17.302-60.406-.486-21.128-7.342-39.204-20.917-55.195l-34.08-40.146 200.98-10.117 6.9 15.24ZM151.164 304.41 63.363 132C38.454 170.73 26 213.207 26 259.433c0 38.105 8.562 73.555 25.687 106.35 17.124 32.796 40.632 60.125 70.522 81.989 29.89 21.863 63.205 35.606 99.945 41.228L281 371.874c-26.154 4.997-51.218 1.093-75.192-11.713-23.975-12.805-42.189-31.39-54.643-55.752Zm66.892 32.818c18.76 10.02 37.782 12.983 58.063 9.108l51.778-9.894L239.5 516.5l-21.279-1.8c-40.865-6.253-78.097-21.61-111.362-45.943-33.108-24.217-59.249-54.608-78.22-90.94C9.56 341.28 0 301.698 0 259.434c0-51.208 13.886-98.568 41.495-141.497L70.5 79.5l103.824 213.092c10.042 19.637 24.454 34.338 43.733 44.636Z"/></svg>; case 'os/fedora': return <svg viewBox="0 0 204.7 200.9" width={ `${ width }px` } height={ `${ height }px` } ><path d="M102.7 1.987c-55.41 0-100.3 44.21-100.4 98.79h-.018v76.47H2.3c.027 12.38 10.22 22.4 22.8 22.4h77.58c55.42-.035 100.3-44.24 100.3-98.79 0-54.58-44.91-98.79-100.4-98.79zm20.39 40.68c16.85 0 32.76 12.7 32.76 30.23 0 1.625.01 3.252-.26 5.095-.467 4.662-4.794 8.012-9.505 7.355-4.711-.665-7.909-5.07-7.037-9.679.08-.526.108-1.352.108-2.772 0-9.938-8.257-13.77-16.06-13.77-7.805 0-14.84 6.462-14.85 13.77.135 8.455 0 16.84 0 25.29l14.49-.107c11.31-.23 11.44 16.54.13 16.46l-14.61.107c-.035 6.801.054 5.571.019 8.996 0 0 .122 8.318-.13 14.62-1.749 18.52-17.76 33.32-37 33.32-20.4 0-37.2-16.41-37.2-36.54.612-20.7 17.38-36.99 38.5-36.8l11.78-.087v16.43l-11.78.106h-.062c-11.6.338-21.55 8.1-21.74 20.34 0 11.15 9.148 20.08 20.5 20.08 11.34 0 20.42-8.124 20.42-20.06l-.018-62.23c.006-1.155.044-2.073.173-3.347 1.914-15.22 15.74-26.82 31.39-26.82z"/></svg>; @@ -334,6 +349,7 @@ const SVG = (props: Props) => { case 'person': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0zm4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4zm-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664h10z"/></svg>; case 'pie-chart-fill': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M15.985 8.5H8.207l-5.5 5.5a8 8 0 0 0 13.277-5.5zM2 13.292A8 8 0 0 1 7.5.015v7.778l-5.5 5.5zM8.5.015V7.5h7.485A8.001 8.001 0 0 0 8.5.015z"/></svg>; case 'pin-fill': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M4.146.146A.5.5 0 0 1 4.5 0h7a.5.5 0 0 1 .5.5c0 .68-.342 1.174-.646 1.479-.126.125-.25.224-.354.298v4.431l.078.048c.203.127.476.314.751.555C12.36 7.775 13 8.527 13 9.5a.5.5 0 0 1-.5.5h-4v4.5c0 .276-.224 1.5-.5 1.5s-.5-1.224-.5-1.5V10h-4a.5.5 0 0 1-.5-.5c0-.973.64-1.725 1.17-2.189A5.921 5.921 0 0 1 5 6.708V2.277a2.77 2.77 0 0 1-.354-.298C4.342 1.674 4 1.179 4 .5a.5.5 0 0 1 .146-.354z"/></svg>; + case 'play-circle-bold': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/><path d="M6.271 5.055a.5.5 0 0 1 .52.038l3.5 2.5a.5.5 0 0 1 0 .814l-3.5 2.5A.5.5 0 0 1 6 10.5v-5a.5.5 0 0 1 .271-.445z"/></svg>; case 'play-circle-light': return <svg viewBox="0 0 52 52" width={ `${ width }px` } height={ `${ height }px` } ><path d="M26 0A26 26 0 1 1 0 26 26 26 0 0 1 26 0Zm0 1.58A24.42 24.42 0 1 0 50.42 26 24.44 24.44 0 0 0 26 1.58Zm10.3 22c2.27 1.36 2.27 3.6 0 5l-13.16 7.79c-2.28 1.36-4.14.44-4.14-2v-16.7c0-2.49 1.86-3.4 4.13-2ZM35.37 27a1 1 0 0 0 0-1.85l-12.86-7.91c-.83-.51-1.51-.17-1.51.75V34c0 .93.68 1.27 1.52.76Z"/></svg>; case 'play-circle': return <svg viewBox="0 0 512 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M256 504c137 0 248-111 248-248S393 8 256 8 8 119 8 256s111 248 248 248zM40 256c0-118.7 96.1-216 216-216 118.7 0 216 96.1 216 216 0 118.7-96.1 216-216 216-118.7 0-216-96.1-216-216zm331.7-18-176-107c-15.8-8.8-35.7 2.5-35.7 21v208c0 18.4 19.8 29.8 35.7 21l176-101c16.4-9.1 16.4-32.8 0-42zM192 335.8V176.9c0-4.7 5.1-7.6 9.1-5.1l134.5 81.7c3.9 2.4 3.8 8.1-.1 10.3L201 341c-4 2.3-9-.6-9-5.2z"/></svg>; case 'play-fill-new': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="m11.596 8.697-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393z"/></svg>; @@ -341,7 +357,9 @@ const SVG = (props: Props) => { case 'play-hover': return <svg viewBox="0 0 42 42" width={ `${ width }px` } height={ `${ height }px` } ><g clipPath="url(#a)"><path d="M21 0a21 21 0 1 1 0 42 21 21 0 0 1 0-42Zm0 1.276a19.724 19.724 0 1 0 0 39.448 19.724 19.724 0 0 0 0-39.448Z"/><circle cx="21" cy="21" r="20"/><path d="M29.32 23.084c1.833-1.13 1.833-2.94 0-4.039l-10.638-6.389c-1.833-1.13-3.336-.395-3.336 1.616V27.76c0 1.971 1.503 2.714 3.344 1.616l10.63-6.292Z" fill="#fff"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h42v42H0z"/></clipPath></defs></svg>; case 'play': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M10.804 8 5 4.633v6.734L10.804 8zm.792-.696a.802.802 0 0 1 0 1.392l-6.363 3.692C4.713 12.69 4 12.345 4 11.692V4.308c0-.653.713-.998 1.233-.696l6.363 3.692z"/></svg>; case 'plus-circle': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/><path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/></svg>; - case 'plus': return <svg viewBox="0 0 8 8" width={ `${ width }px` } height={ `${ height }px` } ><path d="M4 0a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 4 0Z"/></svg>; + case 'plus-lg': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 2a.5.5 0 0 1 .5.5v5h5a.5.5 0 0 1 0 1h-5v5a.5.5 0 0 1-1 0v-5h-5a.5.5 0 0 1 0-1h5v-5A.5.5 0 0 1 8 2Z"/></svg>; + case 'plus': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/></svg>; + case 'pointer-sessions-search': return <svg viewBox="0 0 157 200" width={ `${ width }px` } height={ `${ height }px` } ><path d="M156.877 1.647a1 1 0 0 0-1.415-.96l-3.852 1.76a1 1 0 0 0-.072 1.782l3.662 2.047a1 1 0 0 0 1.487-.823l.19-3.806ZM.372 100.542c7.18-.574 15.4-.41 23.76-.369 8.333.041 16.793-.039 24.361-1.12 7.564-1.08 14.325-3.171 19.195-7.208 4.894-4.057 7.792-10.005 7.792-18.627h-1.395c0 8.364-2.8 13.96-7.354 17.737-4.579 3.795-11.02 5.827-18.462 6.89-7.439 1.062-15.791 1.146-24.13 1.105-8.314-.042-16.5 1.011-23.767 1.592Zm67.68-14.233c2.937 2.554 6.59 3.334 10.667 2.6 4.036-.726 8.484-2.932 13.149-6.323 9.338-6.788 19.76-18.48 29.779-33.325 0 0-.039-.38-.508-.624-9.983 14.792-21.001 26.348-30.16 33.006-4.585 3.332-8.825 6.174-12.54 6.843-3.675.661-6.838-.04-9.406-2.271l-.982.094Zm53.595-37.048c25.462-37.726 24.623-33.749 32.521-44.276l-.472-.673c-7.82 10.423-7.082 6.58-32.557 44.325.469.244.508.624.508.624ZM75.48 73.218c0-.428-.047-.805-.156-1.121-.107-.314-.291-.612-.601-.816-.675-.443-1.469-.18-2.005.116-1.135.63-2.463 2.104-3.575 3.823-1.127 1.742-2.103 3.845-2.466 5.824-.358 1.955-.142 3.947 1.374 5.265l.982-.094c-1.034-.899-1.312-3.16-.979-4.977.33-1.795.539-3.76 1.61-5.414 1.086-1.68 2.966-2.93 3.799-3.392.447-.248.5-.121.425-.17-.01-.007.045.025.1.188.056.161.097.409.097.768h1.395Z" fill="#000"/></svg>; case 'prev1': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"/></svg>; case 'puzzle-piece': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M6.75 1a.75.75 0 0 1 .75.75V8a.5.5 0 0 0 1 0V5.467l.086-.004c.317-.012.637-.008.816.027.134.027.294.096.448.182.077.042.15.147.15.314V8a.5.5 0 1 0 1 0V6.435a4.9 4.9 0 0 1 .106-.01c.316-.024.584-.01.708.04.118.046.3.207.486.43.081.096.15.19.2.259V8.5a.5.5 0 0 0 1 0v-1h.342a1 1 0 0 1 .995 1.1l-.271 2.715a2.5 2.5 0 0 1-.317.991l-1.395 2.442a.5.5 0 0 1-.434.252H6.035a.5.5 0 0 1-.416-.223l-1.433-2.15a1.5 1.5 0 0 1-.243-.666l-.345-3.105a.5.5 0 0 1 .399-.546L5 8.11V9a.5.5 0 0 0 1 0V1.75A.75.75 0 0 1 6.75 1zM8.5 4.466V1.75a1.75 1.75 0 1 0-3.5 0v5.34l-1.2.24a1.5 1.5 0 0 0-1.196 1.636l.345 3.106a2.5 2.5 0 0 0 .405 1.11l1.433 2.15A1.5 1.5 0 0 0 6.035 16h6.385a1.5 1.5 0 0 0 1.302-.756l1.395-2.441a3.5 3.5 0 0 0 .444-1.389l.271-2.715a2 2 0 0 0-1.99-2.199h-.581a5.114 5.114 0 0 0-.195-.248c-.191-.229-.51-.568-.88-.716-.364-.146-.846-.132-1.158-.108l-.132.012a1.26 1.26 0 0 0-.56-.642 2.632 2.632 0 0 0-.738-.288c-.31-.062-.739-.058-1.05-.046l-.048.002zm2.094 2.025z"/></svg>; case 'puzzle': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M3.112 3.645A1.5 1.5 0 0 1 4.605 2H7a.5.5 0 0 1 .5.5v.382c0 .696-.497 1.182-.872 1.469a.459.459 0 0 0-.115.118.113.113 0 0 0-.012.025L6.5 4.5v.003l.003.01c.004.01.014.028.036.053a.86.86 0 0 0 .27.194C7.09 4.9 7.51 5 8 5c.492 0 .912-.1 1.19-.24a.86.86 0 0 0 .271-.194.213.213 0 0 0 .039-.063v-.009a.112.112 0 0 0-.012-.025.459.459 0 0 0-.115-.118c-.375-.287-.872-.773-.872-1.469V2.5A.5.5 0 0 1 9 2h2.395a1.5 1.5 0 0 1 1.493 1.645L12.645 6.5h.237c.195 0 .42-.147.675-.48.21-.274.528-.52.943-.52.568 0 .947.447 1.154.862C15.877 6.807 16 7.387 16 8s-.123 1.193-.346 1.638c-.207.415-.586.862-1.154.862-.415 0-.733-.246-.943-.52-.255-.333-.48-.48-.675-.48h-.237l.243 2.855A1.5 1.5 0 0 1 11.395 14H9a.5.5 0 0 1-.5-.5v-.382c0-.696.497-1.182.872-1.469a.459.459 0 0 0 .115-.118.113.113 0 0 0 .012-.025L9.5 11.5v-.003a.214.214 0 0 0-.039-.064.859.859 0 0 0-.27-.193C8.91 11.1 8.49 11 8 11c-.491 0-.912.1-1.19.24a.859.859 0 0 0-.271.194.214.214 0 0 0-.039.063v.003l.001.006a.113.113 0 0 0 .012.025c.016.027.05.068.115.118.375.287.872.773.872 1.469v.382a.5.5 0 0 1-.5.5H4.605a1.5 1.5 0 0 1-1.493-1.645L3.356 9.5h-.238c-.195 0-.42.147-.675.48-.21.274-.528.52-.943.52-.568 0-.947-.447-1.154-.862C.123 9.193 0 8.613 0 8s.123-1.193.346-1.638C.553 5.947.932 5.5 1.5 5.5c.415 0 .733.246.943.52.255.333.48.48.675.48h.238l-.244-2.855zM4.605 3a.5.5 0 0 0-.498.55l.001.007.29 3.4A.5.5 0 0 1 3.9 7.5h-.782c-.696 0-1.182-.497-1.469-.872a.459.459 0 0 0-.118-.115.112.112 0 0 0-.025-.012L1.5 6.5h-.003a.213.213 0 0 0-.064.039.86.86 0 0 0-.193.27C1.1 7.09 1 7.51 1 8c0 .491.1.912.24 1.19.07.14.14.225.194.271a.213.213 0 0 0 .063.039H1.5l.006-.001a.112.112 0 0 0 .025-.012.459.459 0 0 0 .118-.115c.287-.375.773-.872 1.469-.872H3.9a.5.5 0 0 1 .498.542l-.29 3.408a.5.5 0 0 0 .497.55h1.878c-.048-.166-.195-.352-.463-.557-.274-.21-.52-.528-.52-.943 0-.568.447-.947.862-1.154C6.807 10.123 7.387 10 8 10s1.193.123 1.638.346c.415.207.862.586.862 1.154 0 .415-.246.733-.52.943-.268.205-.415.39-.463.557h1.878a.5.5 0 0 0 .498-.55l-.001-.007-.29-3.4A.5.5 0 0 1 12.1 8.5h.782c.696 0 1.182.497 1.469.872.05.065.091.099.118.115.013.008.021.01.025.012a.02.02 0 0 0 .006.001h.003a.214.214 0 0 0 .064-.039.86.86 0 0 0 .193-.27c.14-.28.24-.7.24-1.191 0-.492-.1-.912-.24-1.19a.86.86 0 0 0-.194-.271.215.215 0 0 0-.063-.039H14.5l-.006.001a.113.113 0 0 0-.025.012.459.459 0 0 0-.118.115c-.287.375-.773.872-1.469.872H12.1a.5.5 0 0 1-.498-.543l.29-3.407a.5.5 0 0 0-.497-.55H9.517c.048.166.195.352.463.557.274.21.52.528.52.943 0 .568-.447.947-.862 1.154C9.193 5.877 8.613 6 8 6s-1.193-.123-1.638-.346C5.947 5.447 5.5 5.068 5.5 4.5c0-.415.246-.733.52-.943.268-.205.415-.39.463-.557H4.605z"/></svg>; @@ -350,6 +368,7 @@ const SVG = (props: Props) => { case 'quote-left': return <svg viewBox="0 0 512 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M464 256h-80v-64c0-35.3 28.7-64 64-64h8c13.3 0 24-10.7 24-24V56c0-13.3-10.7-24-24-24h-8c-88.4 0-160 71.6-160 160v240c0 26.5 21.5 48 48 48h128c26.5 0 48-21.5 48-48V304c0-26.5-21.5-48-48-48zm-288 0H96v-64c0-35.3 28.7-64 64-64h8c13.3 0 24-10.7 24-24V56c0-13.3-10.7-24-24-24h-8C71.6 32 0 103.6 0 192v240c0 26.5 21.5 48 48 48h128c26.5 0 48-21.5 48-48V304c0-26.5-21.5-48-48-48z"/></svg>; case 'quote-right': return <svg viewBox="0 0 512 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M464 32H336c-26.5 0-48 21.5-48 48v128c0 26.5 21.5 48 48 48h80v64c0 35.3-28.7 64-64 64h-8c-13.3 0-24 10.7-24 24v48c0 13.3 10.7 24 24 24h8c88.4 0 160-71.6 160-160V80c0-26.5-21.5-48-48-48zm-288 0H48C21.5 32 0 53.5 0 80v128c0 26.5 21.5 48 48 48h80v64c0 35.3-28.7 64-64 64h-8c-13.3 0-24 10.7-24 24v48c0 13.3 10.7 24 24 24h8c88.4 0 160-71.6 160-160V80c0-26.5-21.5-48-48-48z"/></svg>; case 'quotes': return <svg viewBox="0 0 29 19" width={ `${ width }px` } height={ `${ height }px` } ><path opacity=".7" d="M11.636 2.905A6.318 6.318 0 0 0 0 6.35a6.326 6.326 0 0 0 9.719 5.336c-.497 1.476-1.423 3.051-2.949 4.63a1.583 1.583 0 1 0 2.277 2.201c5.64-5.844 4.907-12.197 2.588-15.605v-.007Zm15.18 0A6.318 6.318 0 0 0 15.18 6.35a6.326 6.326 0 0 0 9.719 5.336c-.497 1.476-1.423 3.051-2.949 4.63a1.583 1.583 0 1 0 2.277 2.201c5.64-5.844 4.907-12.197 2.588-15.605v-.007Z"/></svg>; + case 'record-circle': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/><path d="M11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/></svg>; case 'redo-back': return <svg viewBox="0 0 496 496" width={ `${ width }px` } height={ `${ height }px` } ><path d="M12 0h10c6.627 0 12 5.373 12 12v110.625C77.196 49.047 157.239-.285 248.793.001 385.18.428 496.213 112.009 496 248.396 495.786 385.181 384.834 496 248 496c-63.926 0-122.202-24.187-166.178-63.908-5.113-4.618-5.354-12.561-.482-17.433l7.069-7.069c4.503-4.503 11.749-4.714 16.482-.454C142.782 441.238 192.935 462 248 462c117.744 0 214-95.331 214-214 0-117.744-95.331-214-214-214-82.862 0-154.737 47.077-190.289 116H172c6.627 0 12 5.373 12 12v10c0 6.627-5.373 12-12 12H12c-6.627 0-12-5.373-12-12V12C0 5.373 5.373 0 12 0Z"/></svg>; case 'redo': return <svg viewBox="0 0 512 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M492 8h-10c-6.627 0-12 5.373-12 12v110.625C426.804 57.047 346.761 7.715 255.207 8.001 118.82 8.428 7.787 120.009 8 256.396 8.214 393.181 119.166 504 256 504c63.926 0 122.202-24.187 166.178-63.908 5.113-4.618 5.354-12.561.482-17.433l-7.069-7.069c-4.503-4.503-11.749-4.714-16.482-.454C361.218 449.238 311.065 470 256 470c-117.744 0-214-95.331-214-214 0-117.744 95.331-214 214-214 82.862 0 154.737 47.077 190.289 116H332c-6.627 0-12 5.373-12 12v10c0 6.627 5.373 12 12 12h160c6.627 0 12-5.373 12-12V20c0-6.627-5.373-12-12-12z"/></svg>; case 'remote-control': return <svg viewBox="0 0 16 14" width={ `${ width }px` } height={ `${ height }px` } ><path d="M.59.59A2 2 0 0 0 0 2v8a2 2 0 0 0 .59 1.41A2 2 0 0 0 2 12h5.5a.5.5 0 0 0 0-1H2a1 1 0 0 1-.71-.29A1 1 0 0 1 1 10V5h13v1a.5.5 0 0 0 1 0V2a2 2 0 0 0-2-2H2A2 2 0 0 0 .59.59ZM14 4H1V2a1 1 0 0 1 .29-.71A1 1 0 0 1 2 1h11a1 1 0 0 1 1 1ZM2.85 2.85a.48.48 0 0 1-.7 0 .48.48 0 0 1 0-.7.48.48 0 0 1 .7 0 .48.48 0 0 1 0 .7Zm1.5 0a.5.5 0 1 1-.7-.7.5.5 0 1 1 .7.7Zm1.5 0a.5.5 0 1 0-.7-.7.5.5 0 1 0 .7.7ZM15 13a3.48 3.48 0 1 0-2.47 1A3.46 3.46 0 0 0 15 13Zm-4.2-4.46a.31.31 0 0 1 .19 0l3.33 1.34a.33.33 0 0 1 .15.11.34.34 0 0 1 0 .37.3.3 0 0 1-.13.12l-.92.46 1 1a.35.35 0 0 1 .1.24.34.34 0 0 1-.1.23.33.33 0 0 1-.23.1.35.35 0 0 1-.24-.1l-1-1-.46.92a.3.3 0 0 1-.12.13.34.34 0 0 1-.37 0 .33.33 0 0 1-.11-.15L10.52 9a.31.31 0 0 1 0-.19.33.33 0 0 1 .26-.26Z"/></svg>; @@ -363,6 +382,7 @@ const SVG = (props: Props) => { case 'server': return <svg viewBox="0 0 512 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M376 256c0-13.255 10.745-24 24-24s24 10.745 24 24-10.745 24-24 24-24-10.745-24-24zm-40 24c13.255 0 24-10.745 24-24s-10.745-24-24-24-24 10.745-24 24 10.745 24 24 24zm176-128c0 12.296-4.629 23.507-12.232 32 7.603 8.493 12.232 19.704 12.232 32v80c0 12.296-4.629 23.507-12.232 32 7.603 8.493 12.232 19.704 12.232 32v80c0 26.51-21.49 48-48 48H48c-26.51 0-48-21.49-48-48v-80c0-12.296 4.629-23.507 12.232-32C4.629 319.507 0 308.296 0 296v-80c0-12.296 4.629-23.507 12.232-32C4.629 175.507 0 164.296 0 152V72c0-26.51 21.49-48 48-48h416c26.51 0 48 21.49 48 48v80zm-480 0c0 8.822 7.178 16 16 16h416c8.822 0 16-7.178 16-16V72c0-8.822-7.178-16-16-16H48c-8.822 0-16 7.178-16 16v80zm432 48H48c-8.822 0-16 7.178-16 16v80c0 8.822 7.178 16 16 16h416c8.822 0 16-7.178 16-16v-80c0-8.822-7.178-16-16-16zm16 160c0-8.822-7.178-16-16-16H48c-8.822 0-16 7.178-16 16v80c0 8.822 7.178 16 16 16h416c8.822 0 16-7.178 16-16v-80zm-80-224c13.255 0 24-10.745 24-24s-10.745-24-24-24-24 10.745-24 24 10.745 24 24 24zm-64 0c13.255 0 24-10.745 24-24s-10.745-24-24-24-24 10.745-24 24 10.745 24 24 24zm64 240c-13.255 0-24 10.745-24 24s10.745 24 24 24 24-10.745 24-24-10.745-24-24-24zm-64 0c-13.255 0-24 10.745-24 24s10.745 24 24 24 24-10.745 24-24-10.745-24-24-24z"/></svg>; case 'share-alt': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M13.5 1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM11 2.5a2.5 2.5 0 1 1 .603 1.628l-6.718 3.12a2.499 2.499 0 0 1 0 1.504l6.718 3.12a2.5 2.5 0 1 1-.488.876l-6.718-3.12a2.5 2.5 0 1 1 0-3.256l6.718-3.12A2.5 2.5 0 0 1 11 2.5zm-8.5 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm11 5.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z"/></svg>; case 'shield-lock': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M5.338 1.59a61.44 61.44 0 0 0-2.837.856.481.481 0 0 0-.328.39c-.554 4.157.726 7.19 2.253 9.188a10.725 10.725 0 0 0 2.287 2.233c.346.244.652.42.893.533.12.057.218.095.293.118a.55.55 0 0 0 .101.025.615.615 0 0 0 .1-.025c.076-.023.174-.061.294-.118.24-.113.547-.29.893-.533a10.726 10.726 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067c-.53 0-1.552.223-2.662.524zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.775 11.775 0 0 1-2.517 2.453 7.159 7.159 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7.158 7.158 0 0 1-1.048-.625 11.777 11.777 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 62.456 62.456 0 0 1 5.072.56z"/><path d="M9.5 6.5a1.5 1.5 0 0 1-1 1.415l.385 1.99a.5.5 0 0 1-.491.595h-.788a.5.5 0 0 1-.49-.595l.384-1.99a1.5 1.5 0 1 1 2-1.415z"/></svg>; + case 'signpost-split': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M7 7V1.414a1 1 0 0 1 2 0V2h5a1 1 0 0 1 .8.4l.975 1.3a.5.5 0 0 1 0 .6L14.8 5.6a1 1 0 0 1-.8.4H9v10H7v-5H2a1 1 0 0 1-.8-.4L.225 9.3a.5.5 0 0 1 0-.6L1.2 7.4A1 1 0 0 1 2 7h5zm1 3V8H2l-.75 1L2 10h6zm0-5h6l.75-1L14 3H8v2z"/></svg>; case 'signup': return <svg viewBox="0 0 25 42" width={ `${ width }px` } height={ `${ height }px` } ><path d="M3.5 35A3.501 3.501 0 1 1 0 38.5C0 36.568 1.568 35 3.5 35Zm9-9A3.501 3.501 0 1 1 9 29.5c0-1.932 1.568-3.5 3.5-3.5Zm-9 0A3.501 3.501 0 1 1 0 29.5C0 27.568 1.568 26 3.5 26Zm18-8a3.5 3.5 0 1 1-.002 6.998A3.5 3.5 0 0 1 21.5 18Zm-9 0a3.5 3.5 0 1 1-.002 6.998A3.5 3.5 0 0 1 12.5 18Zm-9 0a3.5 3.5 0 1 1-.002 6.998A3.5 3.5 0 0 1 3.5 18Zm9-9a3.501 3.501 0 1 1-.002 6.998A3.501 3.501 0 0 1 12.5 9Zm-9 0a3.501 3.501 0 1 1-.002 6.998A3.501 3.501 0 0 1 3.5 9Zm0-9C5.432 0 7 1.568 7 3.5S5.432 7 3.5 7A3.501 3.501 0 0 1 0 3.5C0 1.568 1.568 0 3.5 0Z" fill="#FFF"/></svg>; case 'skip-forward-fill': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M15.5 3.5a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-1 0V8.753l-6.267 3.636c-.54.313-1.233-.066-1.233-.697v-2.94l-6.267 3.636C.693 12.703 0 12.324 0 11.693V4.308c0-.63.693-1.01 1.233-.696L7.5 7.248v-2.94c0-.63.693-1.01 1.233-.696L15 7.248V4a.5.5 0 0 1 .5-.5z"/></svg>; case 'skip-forward': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M15.5 3.5a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-1 0V8.752l-6.267 3.636c-.52.302-1.233-.043-1.233-.696v-2.94l-6.267 3.636C.713 12.69 0 12.345 0 11.692V4.308c0-.653.713-.998 1.233-.696L7.5 7.248v-2.94c0-.653.713-.998 1.233-.696L15 7.248V4a.5.5 0 0 1 .5-.5zM1 4.633v6.734L6.804 8 1 4.633zm7.5 0v6.734L14.304 8 8.5 4.633z"/></svg>; @@ -371,10 +391,12 @@ const SVG = (props: Props) => { case 'sliders': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M11.5 2a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM9.05 3a2.5 2.5 0 0 1 4.9 0H16v1h-2.05a2.5 2.5 0 0 1-4.9 0H0V3h9.05zM4.5 7a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM2.05 8a2.5 2.5 0 0 1 4.9 0H16v1H6.95a2.5 2.5 0 0 1-4.9 0H0V8h2.05zm9.45 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm-2.45 1a2.5 2.5 0 0 1 4.9 0H16v1h-2.05a2.5 2.5 0 0 1-4.9 0H0v-1h9.05z"/></svg>; case 'social/slack': return <svg aria-labelledby="simpleicons-slack-icon" viewBox="0 0 24 24" width={ `${ width }px` } height={ `${ height }px` } ><path d="m9.879 10.995 1.035 3.085 3.205-1.074-1.035-3.074-3.205 1.08v-.017z"/><path d="m18.824 14.055-1.555.521.54 1.61a1.246 1.246 0 0 1-1.221 1.637 1.26 1.26 0 0 1-1.155-.849l-.54-1.607-3.21 1.073.539 1.608a1.249 1.249 0 0 1-1.229 1.639 1.266 1.266 0 0 1-1.156-.85l-.539-1.619-1.561.524c-.15.045-.285.061-.435.061a1.269 1.269 0 0 1-1.155-.855 1.235 1.235 0 0 1 .78-1.575l1.56-.525L7.5 11.76l-1.551.525a1.264 1.264 0 0 1-.428.064 1.247 1.247 0 0 1-1.141-.848 1.25 1.25 0 0 1 .796-1.574l1.56-.52-.54-1.605a1.248 1.248 0 0 1 .796-1.575 1.239 1.239 0 0 1 1.574.783l.539 1.608L12.3 7.544l-.54-1.605a1.256 1.256 0 0 1 .789-1.574 1.249 1.249 0 0 1 1.575.791l.54 1.621 1.555-.51a1.247 1.247 0 0 1 1.575.779 1.244 1.244 0 0 1-.784 1.575l-1.557.524 1.035 3.086 1.551-.516a1.248 1.248 0 0 1 1.575.795c.22.66-.135 1.365-.779 1.574l-.011-.029zm4.171-5.356C20.52.456 16.946-1.471 8.699 1.005.456 3.479-1.471 7.051 1.005 15.301c2.475 8.245 6.046 10.17 14.296 7.694 8.245-2.475 10.17-6.046 7.694-14.296z"/></svg>; case 'social/trello': return <svg aria-labelledby="simpleicons-trello-icon" viewBox="0 0 24 24" width={ `${ width }px` } height={ `${ height }px` } ><path d="M21 0H3a3 3 0 0 0-3 3v18a3 3 0 0 0 3 3h18c1.656 0 3-1.344 3-3V3a3 3 0 0 0-3-3zM10.44 18.18A1.44 1.44 0 0 1 9 19.62H4.56c-.795 0-1.44-.646-1.44-1.44V4.56c0-.795.645-1.44 1.44-1.44H9c.795 0 1.44.645 1.44 1.44v13.62zm10.44-6c0 .794-.645 1.44-1.44 1.44H15c-.795 0-1.44-.646-1.44-1.44V4.56c0-.795.646-1.44 1.44-1.44h4.44c.795 0 1.44.645 1.44 1.44v7.62z"/></svg>; + case 'speedometer2': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 4a.5.5 0 0 1 .5.5V6a.5.5 0 0 1-1 0V4.5A.5.5 0 0 1 8 4zM3.732 5.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707zM2 10a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 10zm9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5zm.754-4.246a.389.389 0 0 0-.527-.02L7.547 9.31a.91.91 0 1 0 1.302 1.258l3.434-4.297a.389.389 0 0 0-.029-.518z"/><path d="M0 10a8 8 0 1 1 15.547 2.661c-.442 1.253-1.845 1.602-2.932 1.25C11.309 13.488 9.475 13 8 13c-1.474 0-3.31.488-4.615.911-1.087.352-2.49.003-2.932-1.25A7.988 7.988 0 0 1 0 10zm8-7a7 7 0 0 0-6.603 9.329c.203.575.923.876 1.68.63C4.397 12.533 6.358 12 8 12s3.604.532 4.923.96c.757.245 1.477-.056 1.68-.631A7 7 0 0 0 8 3z"/></svg>; case 'spinner': return <svg viewBox="0 0 512 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M288 32c0 17.673-14.327 32-32 32s-32-14.327-32-32 14.327-32 32-32 32 14.327 32 32zm-32 416c-17.673 0-32 14.327-32 32s14.327 32 32 32 32-14.327 32-32-14.327-32-32-32zm256-192c0-17.673-14.327-32-32-32s-32 14.327-32 32 14.327 32 32 32 32-14.327 32-32zm-448 0c0-17.673-14.327-32-32-32S0 238.327 0 256s14.327 32 32 32 32-14.327 32-32zm33.608 126.392c-17.673 0-32 14.327-32 32s14.327 32 32 32 32-14.327 32-32-14.327-32-32-32zm316.784 0c-17.673 0-32 14.327-32 32s14.327 32 32 32 32-14.327 32-32-14.327-32-32-32zM97.608 65.608c-17.673 0-32 14.327-32 32 0 17.673 14.327 32 32 32s32-14.327 32-32c0-17.673-14.327-32-32-32z"/></svg>; case 'star-solid': return <svg viewBox="0 0 576 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M259.3 17.8 194 150.2 47.9 171.5c-26.2 3.8-36.7 36.1-17.7 54.6l105.7 103-25 145.5c-4.5 26.3 23.2 46 46.4 33.7L288 439.6l130.7 68.7c23.2 12.2 50.9-7.4 46.4-33.7l-25-145.5 105.7-103c19-18.5 8.5-50.8-17.7-54.6L382 150.2 316.7 17.8c-11.7-23.6-45.6-23.9-57.4 0z"/></svg>; case 'star': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M2.866 14.85c-.078.444.36.791.746.593l4.39-2.256 4.389 2.256c.386.198.824-.149.746-.592l-.83-4.73 3.523-3.356c.329-.314.158-.888-.283-.95l-4.898-.696L8.465.792a.513.513 0 0 0-.927 0L5.354 5.12l-4.898.696c-.441.062-.612.636-.283.95l3.523 3.356-.83 4.73zm4.905-2.767-3.686 1.894.694-3.957a.565.565 0 0 0-.163-.505L1.71 6.745l4.052-.576a.525.525 0 0 0 .393-.288l1.847-3.658 1.846 3.658a.525.525 0 0 0 .393.288l4.052.575-2.906 2.77a.564.564 0 0 0-.163.506l.694 3.957-3.686-1.894a.503.503 0 0 0-.461 0z"/></svg>; case 'step-forward': return <svg viewBox="0 0 448 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M372 31h-8c-6.6 0-12 5.4-12 12v190.3c-1.1-1.2-2.2-2.4-3.5-3.4l-232-191.4C95.9 21.3 64 35.6 64 63v384c0 27.4 31.9 41.8 52.5 24.6l232-192.6c1.3-1.1 2.4-2.2 3.5-3.4V467c0 6.6 5.4 12 12 12h8c6.6 0 12-5.4 12-12V43c0-6.6-5.4-12-12-12zm-40.5 223.4L96.2 446.8l-.1.1-.1.1V63l.1.1.2.1 235.2 191.2z"/></svg>; + case 'stop-record-circle': return <svg viewBox="0 0 24 24" width={ `${ width }px` } height={ `${ height }px` } ><path clipRule="evenodd" fillRule="evenodd" d="M8 16h8V8H8v8Zm4-14C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2Z"/></svg>; case 'stopwatch': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8.5 5.6a.5.5 0 1 0-1 0v2.9h-3a.5.5 0 0 0 0 1H8a.5.5 0 0 0 .5-.5V5.6z"/><path d="M6.5 1A.5.5 0 0 1 7 .5h2a.5.5 0 0 1 0 1v.57c1.36.196 2.594.78 3.584 1.64a.715.715 0 0 1 .012-.013l.354-.354-.354-.353a.5.5 0 0 1 .707-.708l1.414 1.415a.5.5 0 1 1-.707.707l-.353-.354-.354.354a.512.512 0 0 1-.013.012A7 7 0 1 1 7 2.071V1.5a.5.5 0 0 1-.5-.5zM8 3a6 6 0 1 0 .001 12A6 6 0 0 0 8 3z"/></svg>; case 'store': return <svg viewBox="0 0 16 16" width={ `${ width }px` } height={ `${ height }px` } ><path d="M4 4v2H2V4h2zm1 7V9a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1zm0-5V4a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1zm5 5V9a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1zm0-5V4a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1zM9 4v2H7V4h2zm5 0h-2v2h2V4zM4 9v2H2V9h2zm5 0v2H7V9h2zm5 0v2h-2V9h2zm-3-5a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V4zm1 4a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1h-2z"/></svg>; case 'sync-alt': return <svg viewBox="0 0 512 512" width={ `${ width }px` } height={ `${ height }px` } ><path d="M8 454.06V320a24 24 0 0 1 24-24h134.06c21.38 0 32.09 25.85 17 41l-41.75 41.75A166.82 166.82 0 0 0 256.16 424c77.41-.07 144.31-53.14 162.78-126.85a12 12 0 0 1 11.65-9.15h57.31a12 12 0 0 1 11.81 14.18C478.07 417.08 377.19 504 256 504a247.14 247.14 0 0 1-171.31-68.69L49 471c-15.15 15.15-41 4.44-41-16.94z"/><path d="M12.3 209.82C33.93 94.92 134.81 8 256 8a247.14 247.14 0 0 1 171.31 68.69L463 41c15.12-15.12 41-4.41 41 17v134a24 24 0 0 1-24 24H345.94c-21.38 0-32.09-25.85-17-41l41.75-41.75A166.8 166.8 0 0 0 255.85 88c-77.46.07-144.33 53.18-162.79 126.85A12 12 0 0 1 81.41 224H24.1a12 12 0 0 1-11.8-14.18z"/></svg>; diff --git a/frontend/app/components/ui/SegmentSelection/SegmentSelection.js b/frontend/app/components/ui/SegmentSelection/SegmentSelection.js index d3c71b6cf..5a5cef77f 100644 --- a/frontend/app/components/ui/SegmentSelection/SegmentSelection.js +++ b/frontend/app/components/ui/SegmentSelection/SegmentSelection.js @@ -38,9 +38,9 @@ class SegmentSelection extends React.Component { className )} > - {list.map((item) => ( + {list.map((item, i) => ( <div - key={item.name} + key={`${item.name}-${i}`} className={cn(styles.item, 'w-full', { 'opacity-25 cursor-default': item.disabled })} data-active={this.props.value && this.props.value.value === item.value} onClick={() => !item.disabled && this.setActiveItem(item)} @@ -48,7 +48,7 @@ class SegmentSelection extends React.Component { {item.icon && ( <Icon name={item.icon} - size={size === 'extraSmall' || icons ? 14 : 20} + size={size === 'extraSmall' || size === 'small' || icons ? 14 : 20} marginRight={item.name ? '6' : ''} /> )} diff --git a/frontend/app/components/ui/SegmentSelection/segmentSelection.module.css b/frontend/app/components/ui/SegmentSelection/segmentSelection.module.css index 6d4db35b4..0cebeaa22 100644 --- a/frontend/app/components/ui/SegmentSelection/segmentSelection.module.css +++ b/frontend/app/components/ui/SegmentSelection/segmentSelection.module.css @@ -10,7 +10,8 @@ & .item { color: $gray-medium; font-weight: medium; - padding: 10px; + padding: 0 6px; + height: 33px; flex: 1; text-align: center; cursor: pointer; @@ -70,6 +71,7 @@ .small .item { padding: 4px 8px; + height: 26px; } .extraSmall .item { diff --git a/frontend/app/components/ui/SideMenuitem/SideMenuitem.js b/frontend/app/components/ui/SideMenuitem/SideMenuitem.js index 8538a9a63..c62b84787 100644 --- a/frontend/app/components/ui/SideMenuitem/SideMenuitem.js +++ b/frontend/app/components/ui/SideMenuitem/SideMenuitem.js @@ -12,6 +12,7 @@ function SideMenuitem({ title, active = false, disabled = false, + tooltipTitle = '', onClick, deleteHandler = null, leading = null, @@ -20,8 +21,8 @@ function SideMenuitem({ return ( <Tooltip disabled={ !disabled } - title={ 'No recordings' } - placement="left" + title={ tooltipTitle } + placement="top" > <div className={ cn( diff --git a/frontend/app/components/ui/Toggler/toggler.module.css b/frontend/app/components/ui/Toggler/toggler.module.css index 26d82880c..0b2d7ac68 100644 --- a/frontend/app/components/ui/Toggler/toggler.module.css +++ b/frontend/app/components/ui/Toggler/toggler.module.css @@ -13,7 +13,7 @@ & span { padding-left: 10px; - color: $gray-medium; + /* color: $gray-dark; */ } } .switch input { diff --git a/frontend/app/components/ui/Tooltip/FloatingTooltip.tsx b/frontend/app/components/ui/Tooltip/FloatingTooltip.tsx index c157940de..a15382767 100644 --- a/frontend/app/components/ui/Tooltip/FloatingTooltip.tsx +++ b/frontend/app/components/ui/Tooltip/FloatingTooltip.tsx @@ -13,7 +13,7 @@ import { useInteractions, FloatingPortal, arrow, - computePosition, + } from '@floating-ui/react-dom-interactions'; import type { Placement } from '@floating-ui/react-dom-interactions'; import { INDEXES } from 'App/constants/zindex'; diff --git a/frontend/app/components/ui/Tooltip/Tooltip.tsx b/frontend/app/components/ui/Tooltip/Tooltip.tsx index 5cc7a021c..fcb5e1687 100644 --- a/frontend/app/components/ui/Tooltip/Tooltip.tsx +++ b/frontend/app/components/ui/Tooltip/Tooltip.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import { useTooltipState, TooltipAnchor, FloatingTooltip, FloatingArrow } from './FloatingTooltip'; +import { useTooltipState, TooltipAnchor, FloatingTooltip } from './FloatingTooltip'; import type { Placement } from '@floating-ui/react-dom-interactions'; import cn from 'classnames'; interface Props { - title?: any; - children: any; + title?: React.ReactNode; + children: React.ReactNode; disabled?: boolean; open?: boolean; placement?: Placement; @@ -13,6 +13,7 @@ interface Props { delay?: number; style?: any; offset?: number; + anchorClassName?: string; } function Tooltip(props: Props) { const { @@ -21,6 +22,7 @@ function Tooltip(props: Props) { open = false, placement, className = '', + anchorClassName = '', delay = 500, style = {}, offset = 5, @@ -38,7 +40,7 @@ function Tooltip(props: Props) { return ( <div className="relative"> - <TooltipAnchor state={state}>{props.children}</TooltipAnchor> + <TooltipAnchor className={anchorClassName} state={state}>{props.children}</TooltipAnchor> <FloatingTooltip state={state} className={cn('bg-gray-darkest color-white rounded py-1 px-2 animate-fade', className)} diff --git a/frontend/app/components/ui/ui.stories.js b/frontend/app/components/ui/ui.stories.js index e8c276c27..eb20ca86b 100644 --- a/frontend/app/components/ui/ui.stories.js +++ b/frontend/app/components/ui/ui.stories.js @@ -1,5 +1,4 @@ import { storiesOf } from '@storybook/react'; -import { List } from 'immutable'; import SideMenuitem from './SideMenuitem'; import { Avatar, ErrorItem, ErrorFrame, ErrorDetails, TimelinePointer } from 'UI'; import Error from 'Types/session/error'; diff --git a/frontend/app/constants/alertConditions.js b/frontend/app/constants/alertConditions.ts similarity index 93% rename from frontend/app/constants/alertConditions.js rename to frontend/app/constants/alertConditions.ts index 09dfefc58..0c19fd51e 100644 --- a/frontend/app/constants/alertConditions.js +++ b/frontend/app/constants/alertConditions.ts @@ -3,4 +3,4 @@ export default [ { value: '>=', label: 'above or equal to' }, { value: '<', label: 'below' }, { value: '<=', label: 'below or equal to' }, -]; +] as const; diff --git a/frontend/app/constants/alertMetrics.js b/frontend/app/constants/alertMetrics.ts similarity index 99% rename from frontend/app/constants/alertMetrics.js rename to frontend/app/constants/alertMetrics.ts index 01fc24acb..b7ee3ce4f 100644 --- a/frontend/app/constants/alertMetrics.js +++ b/frontend/app/constants/alertMetrics.ts @@ -18,4 +18,4 @@ export default [ { value: 'performance.crashes.count', label: 'performance.crashes.count', unit: '' }, { value: 'errors.javascript.count', label: 'errors.javascript.count', unit: '' }, { value: 'errors.backend.count', label: 'errors.backend.count', unit: '' }, -]; +] as const; diff --git a/frontend/app/constants/card.ts b/frontend/app/constants/card.ts new file mode 100644 index 000000000..004b5839a --- /dev/null +++ b/frontend/app/constants/card.ts @@ -0,0 +1,227 @@ +import { IconNames } from 'App/components/ui/SVG'; +import { FilterKey } from 'Types/filter/filterType'; +import { MetricType } from 'App/components/Dashboard/components/MetricTypeItem/MetricTypeItem'; + +export interface CardType { + title: string; + icon?: IconNames; + description: string; + slug: string; + subTypes?: CardType[]; + disabled?: boolean; +} + +export const LIBRARY = 'library'; +export const TIMESERIES = 'timeseries'; +export const TABLE = 'table'; +export const CLICKMAP = 'clickMap'; +export const FUNNEL = 'funnel'; +export const ERRORS = 'errors'; +export const PERFORMANCE = 'performance'; +export const RESOURCE_MONITORING = 'resources'; +export const WEB_VITALS = 'webVitals'; +export const USER_PATH = 'userPath'; +export const RETENTION = 'retention'; +export const FEATURE_ADOPTION = 'featureAdoption'; +export const INSIGHTS = 'insights'; + +export interface Option { + label: string; + icon: string; + value: string; + description: string; + disabled?: boolean; +} + +export const TYPES: CardType[] = [ + { + title: 'Add from Library', + icon: 'grid', + description: 'Select an existing card from your library', + slug: LIBRARY, + }, + { + title: 'Timeseries', + icon: 'graph-up', + description: 'Combine captured events and filters to track trends over time.', + slug: TIMESERIES, + subTypes: [{ title: 'Session Count', slug: 'sessionCount', description: '' }], + }, + { + title: 'Clickmap', + icon: 'puzzle-piece', + description: 'See where users click and where they get frustrated.', + slug: CLICKMAP, + subTypes: [{ title: 'Visited URL', slug: FilterKey.CLICKMAP_URL, description: '' }], + }, + { + title: 'Table', + icon: 'list-alt', + description: 'Create custom tables of users, sessions, errors, issues and more.', + slug: TABLE, + subTypes: [ + { title: 'Users', slug: FilterKey.USERID, description: '' }, + { title: 'Sessions', slug: FilterKey.SESSIONS, description: '' }, + { title: 'JS Errors', slug: FilterKey.ERRORS, description: '' }, + { title: 'Issues', slug: FilterKey.ISSUE, description: '' }, + { title: 'Browser', slug: FilterKey.USER_BROWSER, description: '' }, + { title: 'Devices', slug: FilterKey.USER_DEVICE, description: '' }, + { title: 'Countries', slug: FilterKey.USER_COUNTRY, description: '' }, + { title: 'URLs', slug: FilterKey.LOCATION, description: '' }, + ], + }, + { + title: 'Funnel', + icon: 'funnel', + description: 'Find out where users are dropping and understand why.', + slug: FUNNEL, + }, + { + title: 'Error Tracking', + icon: 'exclamation-circle', + description: 'Track API errors across domains and origins.', + slug: ERRORS, + subTypes: [ + { title: 'Errors by Origin', slug: FilterKey.RESOURCES_BY_PARTY, description: '' }, + { title: 'Errors per Domain', slug: FilterKey.ERRORS_PER_DOMAINS, description: '' }, + { title: 'Errors by type', slug: FilterKey.ERRORS_PER_TYPE, description: '' }, + { title: 'Calls with Errors', slug: FilterKey.CALLS_ERRORS, description: '' }, + { title: 'Top 4xx Domains', slug: FilterKey.DOMAINS_ERRORS_4XX, description: '' }, + { title: 'Top 5xx Domains', slug: FilterKey.DOMAINS_ERRORS_5XX, description: '' }, + { + title: 'Impacted Sessions by JS Errors', + slug: FilterKey.IMPACTED_SESSIONS_BY_JS_ERRORS, + description: '', + }, + ], + }, + { + title: 'Performance Tracking', + icon: 'speedometer2', + description: 'Uncover slowdowns and monitor CPU and memory consumption.', + slug: PERFORMANCE, + subTypes: [ + { title: 'CPU Load', slug: FilterKey.CPU, description: '' }, + { title: 'Crashes', slug: FilterKey.CRASHES, description: '' }, + { title: 'Frame Rate', slug: FilterKey.FPS, description: '' }, + { title: 'DOM Building Time', slug: FilterKey.PAGES_DOM_BUILD_TIME, description: '' }, + { title: 'Memory Consumption', slug: FilterKey.MEMORY_CONSUMPTION, description: '' }, + { title: 'Page Response Time', slug: FilterKey.PAGES_RESPONSE_TIME, description: '' }, + { + title: 'Page Response Time Distribution', + slug: FilterKey.PAGES_RESPONSE_TIME_DISTRIBUTION, + description: '', + }, + { + title: 'Resources vs Visually Complete', + slug: FilterKey.RESOURCES_VS_VISUALLY_COMPLETE, + description: '', + }, + { title: 'Sessions per Browser', slug: FilterKey.SESSIONS_PER_BROWSER, description: '' }, + { title: 'Slowest Domains', slug: FilterKey.SLOWEST_DOMAINS, description: '' }, + { title: 'Speed Index by Location', slug: FilterKey.SPEED_LOCATION, description: '' }, + { title: 'Time to Render', slug: FilterKey.TIME_TO_RENDER, description: '' }, + { + title: 'Sessions Impacted by Slow Pages', + slug: FilterKey.IMPACTED_SESSIONS_BY_SLOW_PAGES, + description: '', + }, + ], + }, + { + title: 'Resource Monitoring', + icon: 'files', + description: 'Identify missing resources and those slowing down your app.', + slug: RESOURCE_MONITORING, + subTypes: [ + { + title: 'Breakdown of Loaded Resources', + slug: FilterKey.BREAKDOWN_OF_LOADED_RESOURCES, + description: '', + }, + { title: 'Missing Resources', slug: FilterKey.MISSING_RESOURCES, description: '' }, + { + title: 'Resource Type vs Response End', + slug: FilterKey.RESOURCE_TYPE_VS_RESPONSE_END, + description: '', + }, + { title: 'Resource Fetch Time', slug: FilterKey.RESOURCE_FETCH_TIME, description: '' }, + { title: 'Slowest Resources', slug: FilterKey.SLOWEST_RESOURCES, description: '' }, + ], + }, + { + title: 'Web Vitals', + icon: 'activity', + description: 'Keep an eye on your web vitals and how they evolve over time.', + slug: WEB_VITALS, + subTypes: [ + { title: 'CPU Load', slug: FilterKey.AVG_CPU, description: '' }, + { title: 'DOM Content Loaded', slug: FilterKey.AVG_DOM_CONTENT_LOADED, description: '' }, + { + title: 'DOM Content Loaded Start', + slug: FilterKey.AVG_DOM_CONTENT_LOAD_START, + description: '', + }, + { + title: 'First Meaningful Paint', + slug: FilterKey.AVG_FIRST_CONTENTFUL_PIXEL, + description: '', + }, + { title: 'First Paint', slug: FilterKey.AVG_FIRST_PAINT, description: '' }, + { title: 'Frame Rate', slug: FilterKey.AVG_FPS, description: '' }, + { title: 'Image Load Time', slug: FilterKey.AVG_IMAGE_LOAD_TIME, description: '' }, + { title: 'Page Load Time', slug: FilterKey.AVG_PAGE_LOAD_TIME, description: '' }, + { title: 'DOM Build Time', slug: FilterKey.AVG_PAGES_DOM_BUILD_TIME, description: '' }, + { title: 'Pages Response Time', slug: FilterKey.AVG_PAGES_RESPONSE_TIME, description: '' }, + { title: 'Request Load Time', slug: FilterKey.AVG_REQUEST_LOADT_IME, description: '' }, + { title: 'Response Time ', slug: FilterKey.AVG_RESPONSE_TIME, description: '' }, + { title: 'Session Duration', slug: FilterKey.AVG_SESSION_DURATION, description: '' }, + { title: 'Time Till First Byte', slug: FilterKey.AVG_TILL_FIRST_BYTE, description: '' }, + { title: 'Time to be Interactive', slug: FilterKey.AVG_TIME_TO_INTERACTIVE, description: '' }, + { title: 'Time to Render', slug: FilterKey.AVG_TIME_TO_RENDER, description: '' }, + { title: 'JS Heap Size', slug: FilterKey.AVG_USED_JS_HEAP_SIZE, description: '' }, + { title: 'Visited Pages', slug: FilterKey.AVG_VISITED_PAGES, description: '' }, + { title: 'Captured Requests', slug: FilterKey.COUNT_REQUESTS, description: '' }, + { title: 'Captured Sessions', slug: FilterKey.COUNT_SESSIONS, description: '' }, + ], + }, + // { + // title: 'Path Analysis', + // icon: 'signpost-split', + // description: 'See where users are flowing and explore their journeys.', + // slug: USER_PATH, + // }, + // { + // title: 'Retention', + // icon: 'arrow-repeat', + // description: 'Get an understanding of how many users are returning.', + // slug: RETENTION, + // }, + // { + // title: 'Feature Adoption', + // icon: 'card-checklist', + // description: 'See which features are used the most and how often.', + // slug: FEATURE_ADOPTION, + // }, + { + title: 'Insights', + icon: 'lightbulb', + description: 'Uncover new issues impacting user experience.', + slug: INSIGHTS, + }, + // { + // title: 'Form Analysis', + // icon: 'card-checklist', + // description: 'Understand why users are not completing forms.', + // slug: FEATURE_ADOPTION, + // }, +]; + +export const DROPDOWN_OPTIONS = TYPES.filter((i: MetricType) => i.slug !== LIBRARY).map( + (i: MetricType) => ({ + label: i.title, + icon: i.icon, + value: i.slug, + description: i.description, + }) +); diff --git a/frontend/app/constants/countryShortName.js b/frontend/app/constants/countryShortName.js index 05948ccd6..ef2a61eab 100644 --- a/frontend/app/constants/countryShortName.js +++ b/frontend/app/constants/countryShortName.js @@ -63,8 +63,6 @@ export default (countryName) => { return 'ATG'; case 'American Samoa': return 'ASM'; - case 'Brunei Darussalam': - return 'BRN'; case 'Palestine, State Of': return 'PSE'; case 'Saint Kitts and Nevis': diff --git a/frontend/app/constants/filterOptions.js b/frontend/app/constants/filterOptions.js index 224a32ae6..3fc7e0bc1 100644 --- a/frontend/app/constants/filterOptions.js +++ b/frontend/app/constants/filterOptions.js @@ -1,7 +1,7 @@ -import { FilterKey, IssueType } from 'Types/filter/filterType'; +import { FilterKey, IssueType, IssueCategory } from 'Types/filter/filterType'; // TODO remove text property from options export const options = [ - { key: 'on', label: 'on', value: 'on' }, + { key: 'on', label: 'on', value: 'on' }, { key: 'notOn', label: 'not on', value: 'notOn' }, { key: 'onAny', label: 'on any', value: 'onAny' }, { key: 'is', label: 'is', value: 'is' }, @@ -13,9 +13,9 @@ export const options = [ { key: 'contains', label: 'contains', value: 'contains' }, { key: 'notContains', label: 'not contains', value: 'notContains' }, { key: 'hasAnyValue', label: 'has any value', value: 'hasAnyValue' }, - { key: 'hasNoValue', label: 'has no value', value: 'hasNoValue' }, + { key: 'hasNoValue', label: 'has no value', value: 'hasNoValue' }, { key: 'isSignedUp', label: 'is signed up', value: 'isSignedUp' }, - { key: 'notSignedUp', label: 'not signed up', value: 'notSignedUp' }, + { key: 'notSignedUp', label: 'not signed up', value: 'notSignedUp' }, { key: 'before', label: 'before', value: 'before' }, { key: 'after', label: 'after', value: 'after' }, { key: 'inRage', label: 'in rage', value: 'inRage' }, @@ -32,17 +32,20 @@ export const options = [ ]; const filterKeys = ['is', 'isNot']; +const stringFilterKeysLimited = ['is', 'isAny', 'isNot']; const stringFilterKeys = ['is', 'isAny', 'isNot', 'contains', 'startsWith', 'endsWith', 'notContains']; const stringFilterKeysPerformance = ['is', 'inAnyPage', 'isNot', 'contains', 'startsWith', 'endsWith', 'notContains']; const targetFilterKeys = ['on', 'notOn', 'onAny', 'contains', 'startsWith', 'endsWith', 'notContains']; const signUpStatusFilterKeys = ['isSignedUp', 'notSignedUp']; const rangeFilterKeys = ['before', 'after', 'on', 'inRange', 'notInRange', 'withInLast', 'notWithInLast']; +const pageUrlFilter = ['contains', 'startsWith', 'endsWith'] const getOperatorsByKeys = (keys) => { return options.filter(option => keys.includes(option.key)); }; export const baseOperators = options.filter(({key}) => filterKeys.includes(key)); +export const stringOperatorsLimited = options.filter(({key}) => stringFilterKeysLimited.includes(key)); export const stringOperators = options.filter(({key}) => stringFilterKeys.includes(key)); export const stringOperatorsPerformance = options.filter(({key}) => stringFilterKeysPerformance.includes(key)); export const targetOperators = options.filter(({key}) => targetFilterKeys.includes(key)); @@ -50,6 +53,7 @@ export const booleanOperators = [ { key: 'true', label: 'true', value: 'true' }, { key: 'false', label: 'false', value: 'false' }, ] +export const pageUrlOperators = options.filter(({key}) => pageUrlFilter.includes(key)) export const customOperators = [ { key: '=', label: '=', value: '=' }, @@ -86,6 +90,7 @@ export const metricOf = [ { label: 'Devices', value: FilterKey.USER_DEVICE, type: 'table' }, { label: 'Countries', value: FilterKey.USER_COUNTRY, type: 'table' }, { label: 'URLs', value: FilterKey.LOCATION, type: 'table' }, + ] export const methodOptions = [ @@ -97,7 +102,7 @@ export const methodOptions = [ { label: 'HEAD', value: 'HEAD' }, { label: 'OPTIONS', value: 'OPTIONS' }, { label: 'TRACE', value: 'TRACE' }, - { label: 'CONNECT', value: 'CONNECT' }, + { label: 'CONNECT', value: 'CONNECT' }, ] export const issueOptions = [ @@ -115,10 +120,23 @@ export const issueOptions = [ { label: 'Error', value: IssueType.JS_EXCEPTION }, ] +export const issueCategories = [ + { label: 'Resources', value: IssueCategory.RESOURCES }, + { label: 'Network Request', value: IssueCategory.NETWORK }, + { label: 'Click Rage', value: IssueCategory.RAGE }, + { label: 'JS Errors', value: IssueCategory.ERRORS }, +] + +export const issueCategoriesMap = issueCategories.reduce((acc, {value, label}) => { + acc[value] = label; + return acc; +}, {}) + export default { options, baseOperators, stringOperators, + stringOperatorsLimited, targetOperators, booleanOperators, customOperators, @@ -127,5 +145,7 @@ export default { metricTypes, metricOf, issueOptions, + issueCategories, methodOptions, + pageUrlOperators, } diff --git a/frontend/app/constants/index.js b/frontend/app/constants/index.js index 0ed215d29..b1b8c0b46 100644 --- a/frontend/app/constants/index.js +++ b/frontend/app/constants/index.js @@ -20,4 +20,5 @@ export { WEBHOOK as CHANNEL_WEBHOOK } from './schedule'; export { default } from './filterOptions'; -// export { default as storageKeys } from './storageKeys'; \ No newline at end of file +// export { default as storageKeys } from './storageKeys'; +export const ENTERPRISE_REQUEIRED = "This feature requires an enterprise license."; \ No newline at end of file diff --git a/frontend/app/constants/schedule.js b/frontend/app/constants/schedule.js index 8e08a32a8..176b3fc28 100644 --- a/frontend/app/constants/schedule.js +++ b/frontend/app/constants/schedule.js @@ -2,7 +2,7 @@ export const MINUTES = [ { value: 5, label: '5 Minutes' }, { value: 15, label: '15 Minutes' }, { value: 30, label: '30 Minutes' }, - { value: 60, label: '60 Minutes' }, + { value: 60, label: '60 Minutes' }, ]; export const HOURS = [ ...Array(24).keys() ].map(i => ({ value: i, label: `${ i > 9 ? '' : '0' }${ i }:00` })); @@ -21,10 +21,11 @@ export const DAYS = [ export const EMAIL = 'email'; export const SLACK = 'slack'; +export const TEAMS = 'msteams'; export const WEBHOOK = 'webhook'; export const CHANNEL = [ { value: EMAIL, label: 'Email' }, { value: SLACK, label: 'Slack' }, { value: WEBHOOK, label: 'Webhook' }, -] \ No newline at end of file +] diff --git a/frontend/app/date.ts b/frontend/app/date.ts index 3403abfd6..eff9898b4 100644 --- a/frontend/app/date.ts +++ b/frontend/app/date.ts @@ -1,7 +1,6 @@ // @flow import { DateTime, Duration } from 'luxon'; // TODO -import { toJS } from 'mobx'; import { Timezone } from 'MOBX/types/sessionSettings'; export const durationFormatted = (duration: Duration):string => { @@ -83,7 +82,7 @@ export function formatDateTimeDefault(timestamp: number): string { * @param {Object} timezone fixed offset like UTC+6 * @returns {String} formatted date (or time if its today) */ -export function formatTimeOrDate(timestamp: number, timezone: Timezone, isFull = false): string { +export function formatTimeOrDate(timestamp: number, timezone?: Timezone, isFull = false): string { var date = DateTime.fromMillis(timestamp) if (timezone) { if (timezone.value === 'UTC') date = date.toUTC(); diff --git a/frontend/app/dev/console.js b/frontend/app/dev/console.js index 57fd400ca..b040a595a 100644 --- a/frontend/app/dev/console.js +++ b/frontend/app/dev/console.js @@ -7,6 +7,11 @@ export const options = { }, enableCrash: false, verbose: false, + exceptionsLogs: [], +} + +export const clearLogs = () => { + options.exceptionsLogs = [] } const storedString = localStorage.getItem(KEY) diff --git a/frontend/app/duck/ReducerModule.js.dev b/frontend/app/duck/ReducerModule.js.dev deleted file mode 100644 index c80c65a37..000000000 --- a/frontend/app/duck/ReducerModule.js.dev +++ /dev/null @@ -1,55 +0,0 @@ - -redux -> other storage ::<< Entities + Lists + relations <|> methods:: crud. request declaration -> request realisation with middleware -< (uses) MODEL - - - -!request declaration - - - -action/request formatter => ReducerModule Fabrique => - - -class ReducerModule { - _ns = "common" - _switch = {} - _n = 0 - - constructor(namespace) { - this._ns = namespace - } - - /** - Action: state => newState | { reduce: state, action => newState, creator: () => {objects to action} } - */ - actions(actns): this { - Object.keys(actns).map(key => { - const type = `${this._namespace}/${key.toUpperCase()}`; - this._switch[ type ] = actns[ key ]; - }); - return this; - } - - requests(reqsts): this { - Object.keys(reqsts).map(key => { - const type = `${this._namespace}/${key.toUpperCase()}`; - this._switch[ type ] = actns[ key ]; - }); - return this; - } - - get actionTypes() { - - } - - get actionCreators() { - - } - - get reducer() { - return (state, action = {}) => { - const reduce = this._switch[ action.type ]; - return reduce ? reduce(state, action) : state; - } - } -} \ No newline at end of file diff --git a/frontend/app/duck/alerts.js b/frontend/app/duck/alerts.js index 6591ae7e7..e69de29bb 100644 --- a/frontend/app/duck/alerts.js +++ b/frontend/app/duck/alerts.js @@ -1,63 +0,0 @@ -import Alert from 'Types/alert'; -import { Map } from 'immutable'; -import crudDuckGenerator from './tools/crudDuck'; -import withRequestState, { RequestTypes } from 'Duck/requestStateCreator'; -import { reduceDucks } from 'Duck/tools'; - -const name = 'alert' -const idKey = 'alertId'; -const crudDuck = crudDuckGenerator(name, Alert, { idKey: idKey }); -export const { fetchList, init, edit, remove } = crudDuck.actions; -const FETCH_TRIGGER_OPTIONS = new RequestTypes(`${name}/FETCH_TRIGGER_OPTIONS`); -const CHANGE_SEARCH = `${name}/CHANGE_SEARCH` - -const initialState = Map({ - definedPercent: 0, - triggerOptions: [], - alertsSearch: '', -}); - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - // case GENERATE_LINK.SUCCESS: - // return state.update( - // 'list', - // list => list - // .map(member => { - // if(member.id === action.id) { - // return Member({...member.toJS(), invitationLink: action.data.invitationLink }) - // } - // return member - // }) - // ); - case CHANGE_SEARCH: - return state.set('alertsSearch', action.search); - case FETCH_TRIGGER_OPTIONS.SUCCESS: - return state.set('triggerOptions', action.data.map(({ name, value }) => ({ label: name, value }))); - } - return state; -}; - -export function save(instance) { - return { - types: crudDuck.actionTypes.SAVE.toArray(), - call: client => client.post( instance[idKey] ? `/alerts/${ instance[idKey] }` : '/alerts', instance.toData()), - }; -} - -export function changeSearch(search) { - return { - type: CHANGE_SEARCH, - search, - }; -} - -export function fetchTriggerOptions() { - return { - types: FETCH_TRIGGER_OPTIONS.toArray(), - call: client => client.get('/alerts/triggers'), - }; -} - -// export default crudDuck.reducer; -export default reduceDucks(crudDuck, { initialState, reducer }).reducer; diff --git a/frontend/app/duck/announcements.js b/frontend/app/duck/announcements.js deleted file mode 100644 index 3a7612ee7..000000000 --- a/frontend/app/duck/announcements.js +++ /dev/null @@ -1,45 +0,0 @@ -import { List, Map } from 'immutable'; -import Announcement from 'Types/announcement'; -import { RequestTypes } from './requestStateCreator'; - -import { mergeReducers } from './funcTools/tools'; -import { createRequestReducer } from './funcTools/request'; -import { - createCRUDReducer, - getCRUDRequestTypes, - createFetchList -} from './funcTools/crud'; - -const name = 'announcement'; -const idKey = 'id'; - -const SET_LAST_READ = new RequestTypes('announcement/SET_LAST_READ'); - -const initialState = Map({ - list: List() -}); - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - case SET_LAST_READ.SUCCESS: - return state.update('list', (list) => list.map(i => ({...i.toJS(), viewed: true }))); - } - return state; -}; - -export function setLastRead() { - return { - types: SET_LAST_READ.toArray(), - call: client => client.get(`/announcements/view`), - }; -} - -export const fetchList = createFetchList(name); - -export default mergeReducers( - reducer, - createCRUDReducer(name, Announcement, idKey), - createRequestReducer({ - ...getCRUDRequestTypes(name), - }), -); \ No newline at end of file diff --git a/frontend/app/duck/assignments.js b/frontend/app/duck/assignments.js index 3abb99fb7..427937e05 100644 --- a/frontend/app/duck/assignments.js +++ b/frontend/app/duck/assignments.js @@ -2,10 +2,9 @@ import { List, Map, Set } from 'immutable'; import Assignment from 'Types/session/assignment'; import Activity from 'Types/session/activity'; import withRequestState, { RequestTypes } from './requestStateCreator'; -import { createListUpdater, createItemInListUpdater } from './funcTools/tools'; +import { createListUpdater } from './funcTools/tools'; import { editType, initType } from './funcTools/crud/types'; import { createInit, createEdit } from './funcTools/crud'; -import IssuesType from 'Types/issue/issuesType' const idKey = 'id'; const name = 'assignment'; @@ -22,8 +21,8 @@ const INIT = initType(name); const initialState = Map({ list: List(), - instance: Assignment(), - activeIssue: Assignment(), + instance: new Assignment(), + activeIssue: new Assignment(), issueTypes: List(), issueTypeIcons: Set(), users: List(), @@ -33,22 +32,23 @@ const initialState = Map({ const reducer = (state = initialState, action = {}) => { const users = state.get('users'); - var issueTypes = [] + let issueTypes = [] switch (action.type) { case INIT: action.instance.issueType = issueTypes.length > 0 ? issueTypes[0].id : ''; - return state.set('instance', Assignment(action.instance)); + return state.set('instance', new Assignment(action.instance)); case EDIT: - return state.mergeIn([ 'instance' ], action.instance); + const inst = state.get('instance') + return state.set('instance', new Assignment({ ...inst, ...action.instance })); case FETCH_PROJECTS.SUCCESS: return state.set('projects', List(action.data)).set('projectsFetched', true); case FETCH_ASSIGNMENTS.SUCCESS: - return state.set('list', List(action.data).map(Assignment)); + return state.set('list', List(action.data).map(as => new Assignment(as))); case FETCH_ASSIGNMENT.SUCCESS: - return state.set('activeIssue', Assignment({ ...action.data, users})); + return state.set('activeIssue', new Assignment({ ...action.data, users})); case FETCH_META.SUCCESS: issueTypes = action.data.issueTypes - var issueTypeIcons = {} + const issueTypeIcons = {} issueTypes.forEach(iss => { issueTypeIcons[iss.id] = iss.iconUrl }) @@ -56,12 +56,12 @@ const reducer = (state = initialState, action = {}) => { .set('users', List(action.data.users)) .set('issueTypeIcons', issueTypeIcons) case ADD_ACTIVITY.SUCCESS: - const instance = Assignment(action.data); + const instance = new Assignment(action.data); return listUpdater(state, instance); case ADD_MESSAGE.SUCCESS: const user = users.filter(user => user.id === action.data.author).first(); - const activity = Activity({ type: 'message', user, ...action.data,}); - return state.updateIn([ 'activeIssue', 'activities' ], list => list.push(activity)); + const activity = new Activity({ type: 'message', user, ...action.data,}); + return state.update([ 'activeIssue' ], issue => issue.activities.push(activity)); default: return state; } @@ -79,7 +79,7 @@ export default withRequestState({ export const init = createInit(name); export const edit = createEdit(name); -export function fetchProjects(sessionId) { +export function fetchProjects() { return { types: FETCH_PROJECTS.toArray(), call: client => client.get(`/integrations/issues/list_projects`) @@ -100,13 +100,6 @@ export function fetchAssignments(sessionId) { } } -export function fetchAssigment(sessionId, id) { - return { - types: FETCH_ASSIGNMENT.toArray(), - call: client => client.get(`/sessions/${ sessionId }/assign/${ id }`) - } -} - export function addActivity(sessionId, params) { const data = { ...params, assignee: params.assignee, issueType: params.issueType } return { diff --git a/frontend/app/duck/components/player.js b/frontend/app/duck/components/player.js index 550f6e4df..e3b39bf52 100644 --- a/frontend/app/duck/components/player.js +++ b/frontend/app/duck/components/player.js @@ -10,7 +10,6 @@ export const PERFORMANCE = 6; export const GRAPHQL = 7; export const FETCH = 8; export const EXCEPTIONS = 9; -export const LONGTASKS = 10; export const INSPECTOR = 11; export const OVERVIEW = 12; diff --git a/frontend/app/duck/components/targetDefiner.js b/frontend/app/duck/components/targetDefiner.js index 9f28e8c5c..c58726aa9 100644 --- a/frontend/app/duck/components/targetDefiner.js +++ b/frontend/app/duck/components/targetDefiner.js @@ -1,7 +1,6 @@ import { Map } from 'immutable'; import Target from 'Types/target'; import TargetCustom from 'Types/targetCustom'; -import withRequestState, { RequestTypes } from 'Duck/requestStateCreator'; const EDIT = 'targetDefiner/EDIT'; const SHOW = 'targetDefiner/SHOW'; diff --git a/frontend/app/duck/config.js b/frontend/app/duck/config.js deleted file mode 100644 index a445e3f50..000000000 --- a/frontend/app/duck/config.js +++ /dev/null @@ -1,61 +0,0 @@ -import { Map } from 'immutable'; -import { saveType, fetchType, editType } from './funcTools/crud/types'; -import { mergeReducers, success, array } from './funcTools/tools'; -import { createRequestReducer } from './funcTools/request'; - -const name = 'config' - -const FETCH = fetchType(name); -const SAVE = saveType(name); -const EDIT = editType(name); - -const FETCH_SUCCESS = success(FETCH); -const SAVE_SUCCESS = success(SAVE); - -const initialState = Map({ - options: { - weeklyReport: false - }, -}); - -const reducer = (state = initialState, action = {}) => { - switch(action.type) { - case FETCH_SUCCESS: - return state.set('options', action.data) - case SAVE_SUCCESS: - return state - case EDIT: - return state.set('options', action.config) - default: - return state; - } -} - -export const fetch = () => { - return { - types: array(FETCH), - call: client => client.get(`/config/weekly_report`), - } -} - -export const save = (config) => { - return { - types: array(SAVE), - call: client => client.post(`/config/weekly_report`, config), - } -} - -export const edit = (config) => { - return { - type: EDIT, - config - } -} - -export default mergeReducers( - reducer, - createRequestReducer({ - fetchRequest: FETCH, - saveRequest: SAVE, - }), -) \ No newline at end of file diff --git a/frontend/app/duck/customMetrics.js b/frontend/app/duck/customMetrics.js index e6713acb4..c740f7a57 100644 --- a/frontend/app/duck/customMetrics.js +++ b/frontend/app/duck/customMetrics.js @@ -1,8 +1,8 @@ import { List, Map } from 'immutable'; import CustomMetric, { FilterSeries } from 'Types/customMetric' -import { createFetch, fetchListType, fetchType, saveType, removeType, editType, createRemove, createEdit } from './funcTools/crud'; +import { fetchListType, fetchType, saveType, removeType, editType, createRemove, createEdit } from './funcTools/crud'; import { createRequestReducer, ROOT_KEY } from './funcTools/request'; -import { array, request, success, failure, createListUpdater, mergeReducers } from './funcTools/tools'; +import { array, success, createListUpdater, mergeReducers } from './funcTools/tools'; import Filter from 'Types/filter'; import Session from 'Types/session'; @@ -28,11 +28,6 @@ const INIT = `${name}/INIT`; const SET_ACTIVE_WIDGET = `${name}/SET_ACTIVE_WIDGET`; const REMOVE = removeType(name); const UPDATE_SERIES = `${name}/UPDATE_SERIES`; -const SET_ALERT_METRIC_ID = `${name}/SET_ALERT_METRIC_ID`; - -function chartWrapper(chart = []) { - return chart.map(point => ({ ...point, count: Math.max(point.count, 0) })); -} const updateItemInList = createListUpdater(idKey); const updateInstance = (state, instance) => state.getIn([ "instance", idKey ]) === instance[ idKey ] @@ -97,7 +92,7 @@ function reducer(state = initialState, action = {}) { const { data } = action; return state.set("list", List(data.map(CustomMetric))); case success(FETCH_SESSION_LIST): - return state.set("sessionList", List(action.data.map(item => ({ ...item, sessions: item.sessions.map(Session) })))); + return state.set("sessionList", List(action.data.map(item => ({ ...item, sessions: item.sessions.map(s => new Session(s)) })))); case SET_ACTIVE_WIDGET: return state.set("activeWidget", action.widget).set('sessionList', List()); } @@ -117,12 +112,6 @@ export default mergeReducers( export const edit = createEdit(name); export const remove = createRemove(name); -export const updateSeries = (index, series) => ({ - type: UPDATE_SERIES, - index, - series, -}); - export function fetch(id) { return { id, @@ -147,34 +136,6 @@ export function fetchList() { }; } -export function setAlertMetricId(id) { - return { - type: SET_ALERT_METRIC_ID, - id, - }; -} - -export const addSeries = (series = null) => (dispatch, getState) => { - const instance = getState().getIn([ 'customMetrics', 'instance' ]); - const seriesIndex = instance.series.size; - const newSeries = series || { - name: `Series ${seriesIndex + 1}`, - filter: new Filter({ filters: [], eventsOrder: 'then' }), - }; - - dispatch({ - type: ADD_SERIES, - series: newSeries, - }); -} - -export const removeSeries = (index) => (dispatch, getState) => { - dispatch({ - type: REMOVE_SERIES, - index, - }); -} - export const init = (instance = null, forceNull = false) => (dispatch, getState) => { dispatch({ type: INIT, diff --git a/frontend/app/duck/dashboard.js b/frontend/app/duck/dashboard.js index a99f985e3..9e40505c6 100644 --- a/frontend/app/duck/dashboard.js +++ b/frontend/app/duck/dashboard.js @@ -1,123 +1,22 @@ -import { List, Map, getIn } from 'immutable'; -import { - WIDGET_LIST, - WIDGET_MAP, - WIDGET_KEYS, -} from 'Types/dashboard'; -import Period, { LAST_24_HOURS, LAST_7_DAYS } from 'Types/app/period'; -import { ALL } from 'Types/app/platform'; +import { List, Map } from 'immutable'; import { createRequestReducer } from './funcTools/request'; -import { mergeReducers, success, array } from './funcTools/tools'; +import { mergeReducers, } from './funcTools/tools'; import { RequestTypes } from 'Duck/requestStateCreator'; -const SET_PERIOD = 'dashboard/SET_PERIOD'; -const SET_PLATFORM = 'dashboard/SET_PLATFORM'; const SET_SHOW_ALERTS = 'dashboard/SET_SHOW_ALERTS'; -const SET_COMPARING = 'dashboard/SET_COMPARING'; -const SET_FILTERS = 'dashboard/SET_FILTERS'; -const REMOVE_FILTER = 'dashboard/REMOVE_FILTER'; -const CLEAR_FILTERS = 'dashboard/CLEAR_FILTERS'; const FETCH_PERFORMANCE_SEARCH = 'dashboard/FETCH_PERFORMANCE_SEARCH'; -const FETCH_PERFORMANCE_SEARCH_SUCCESS = success(FETCH_PERFORMANCE_SEARCH); const ON_BOARD = new RequestTypes('plan/ON_BOARD'); -const FETCH_META_OPTIONS = 'dashboard/FETCH_META_OPTIONS'; -const FETCH_META_OPTIONS_SUCCESS = success(FETCH_META_OPTIONS); - -export const FETCH_WIDGET_TYPES = {}; -WIDGET_KEYS.forEach(key => { - FETCH_WIDGET_TYPES[ key ] = `dashboard/FETCH_WIDGET-${ key }-`; //workaround TODO - FETCH_WIDGET_TYPES[ '_' + key ] = `dashboard/FETCH_WIDGET-${ '_' + key }-`; //workaround TODO -}); -const FETCH_WIDGET_SUCCESS_LIST = WIDGET_KEYS.map(key => success(FETCH_WIDGET_TYPES[ key ])).concat(WIDGET_KEYS.map(key => success(FETCH_WIDGET_TYPES[ '_' + key ]))); - -const widgetInitialStates = {}; -WIDGET_LIST.forEach(({ key, dataWrapper }) => { - widgetInitialStates[ key ] = dataWrapper(); - widgetInitialStates[ '_' + key ] = dataWrapper(); -}); - const initialState = Map({ - ...widgetInitialStates, - period: Period({ rangeName: LAST_7_DAYS }), - periodCompare: Period({ rangeName: LAST_7_DAYS }), - filters: List(), - filtersCompare: List(), - platform: ALL, - performanceChart: [], showAlerts: false, - comparing: false, - metaOptions: [], - boarding: List(), - boardingCompletion: 0, + boarding: undefined, + boardingCompletion: undefined, }); -const getValue = ({ avgPageLoadTime, avgRequestLoadTime, avgImageLoadTime }) => avgPageLoadTime || avgRequestLoadTime || avgImageLoadTime; - -const getCountry = item => { - switch(item.location) { - case 'us-east-2': - case 'us-east-1': - return { - userCountry: 'US', - avg: Math.round(item.avg) - }; - case 'europe-west1-d': - return { - userCountry: 'EU', - avg: Math.round(item.avg) - }; - default: - return ''; - } -} - const reducer = (state = initialState, action = {}) => { - let isCompare; - if (FETCH_WIDGET_SUCCESS_LIST.includes(action.type)) { - const key = action.type.split('-')[ 1 ]; - const _key = key.startsWith('_') ? key.replace('_', '') : key; - const dataWrapper = WIDGET_LIST.find(w => w.key === _key).dataWrapper; - return state.set(action.compare ? key : key, dataWrapper(action.data, action.period)); - } switch (action.type) { - case SET_PERIOD: - return state.set(action.compare ? 'periodCompare' : 'period', Period(action.period)); - case SET_PLATFORM: - return state.set("platform", action.platform); - case FETCH_PERFORMANCE_SEARCH_SUCCESS: - const timestamps = List(getIn(action.data, [ 0, "chart" ])).map(({ timestamp }) => ({ timestamp })); - const chart = List(action.data) - .reduce((zippedChartData, resource, index) => zippedChartData - .zipWith((chartPoint, resourcePoint) => ({ - ...chartPoint, - [ `resource${ index }` ]: getValue(resourcePoint), - }), List(resource.chart)), - timestamps - ) - .toJS(); - return state.set('performanceChart', formatChartTime(chart, state.get("period"))); case SET_SHOW_ALERTS: return state.set('showAlerts', action.state); - case SET_COMPARING: - return state.set('comparing', action.status) - .set('filtersCompare', List()).set('periodCompare', state.get("period")); - case SET_FILTERS: - isCompare = action.key === 'compare'; - return state.update(isCompare ? 'filtersCompare' : 'filters', list => list.push(action.filter)) - case REMOVE_FILTER: - isCompare = action.key === 'compare'; - return state.update( - isCompare ? 'filtersCompare' : 'filters', - list => list.filter(filter => filter.key !== action.filterKey) - ); - case CLEAR_FILTERS: - isCompare = action.key === 'compare'; - return state.set(isCompare ? 'filtersCompare' : 'filters', List()); - - case FETCH_META_OPTIONS_SUCCESS: - return state.set('metaOptions', action.data.map(i => ({ ...i, icon: 'id-card', placeholder: 'Search for ' + i.name}))); - case ON_BOARD.SUCCESS: const tasks = List(action.data); const completion = tasks.filter(task => task.done).size * 100 / tasks.size; @@ -129,82 +28,10 @@ const reducer = (state = initialState, action = {}) => { export default mergeReducers( reducer, createRequestReducer({ - fetchWidget: FETCH_WIDGET_TYPES, performanceSearchRequest: FETCH_PERFORMANCE_SEARCH, }), ); -export function setPeriod(compare, period) { - return { - type: SET_PERIOD, - compare, - period, - } -} - -export function setPlatform(platform) { - return { - type: SET_PLATFORM, - platform, - }; -} - -export function setComparing(status) { - return { - type: SET_COMPARING, - status, - }; -} - -export function setFilters(key, filter) { - return { - type: SET_FILTERS, - key, - filter - }; -} - -export function removeFilter(key, filterKey) { - return { - type: REMOVE_FILTER, - key, - filterKey - }; -} - -export function clearFilters(key) { - return { - type: CLEAR_FILTERS, - key - }; -} - - -const toUnderscore = s => s.split(/(?=[A-Z])/).join('_').toLowerCase(); -export function fetchWidget(widgetKey, period, platform, _params, filters) { - let path = `/dashboard/${ toUnderscore(widgetKey) }`; - const widget = WIDGET_MAP[widgetKey]; - const params = period.toTimestamps(); - params.filters = filters ? - filters.map(f => ({key: f.key, value: f.value ? f.value : f.text})).toJS() : []; - // if (platform !== ALL) { - // params.platform = platform; - // } - - return { - types: array(FETCH_WIDGET_TYPES[ _params.compare ? '_' + widgetKey : widgetKey ]), - call: client => client.post(path, {...params, ..._params}), - period, - compare: _params && _params.compare - }; -} - -export function fetchPerformanseSearch(params) { - return { - types: array(FETCH_PERFORMANCE_SEARCH), - call: client => client.post('/dashboard/performance/search', params), - }; -} export function setShowAlerts(state) { return { @@ -213,13 +40,6 @@ export function setShowAlerts(state) { } } -export function fetchMetadataOptions() { - return { - types: array(FETCH_META_OPTIONS), - call: client => client.get('/dashboard/metadata'), - }; -} - export function getOnboard() { return { types: ON_BOARD.toArray(), diff --git a/frontend/app/duck/environments.js b/frontend/app/duck/environments.js deleted file mode 100644 index 8356c4281..000000000 --- a/frontend/app/duck/environments.js +++ /dev/null @@ -1,7 +0,0 @@ -import Environment from 'Types/environment'; -import crudDuckGenerator from './tools/crudDuck'; - -const crudDuck = crudDuckGenerator('environment', Environment); -export const { fetchList, fetch, init, edit, save, remove } = crudDuck.actions; - -export default crudDuck.reducer; diff --git a/frontend/app/duck/errors.js b/frontend/app/duck/errors.js index 32cbf4c40..80d731bee 100644 --- a/frontend/app/duck/errors.js +++ b/frontend/app/duck/errors.js @@ -1,9 +1,9 @@ import { List, Map } from 'immutable'; import { clean as cleanParams } from 'App/api_client'; import ErrorInfo, { RESOLVED, UNRESOLVED, IGNORED, BOOKMARK } from 'Types/errorInfo'; -import { createFetch, fetchListType, fetchType } from './funcTools/crud'; +import { fetchListType, fetchType } from './funcTools/crud'; import { createRequestReducer, ROOT_KEY } from './funcTools/request'; -import { array, request, success, failure, createListUpdater, mergeReducers } from './funcTools/tools'; +import { array, success, failure, createListUpdater, mergeReducers } from './funcTools/tools'; import { reduceThenFetchResource } from './search' const name = "error"; @@ -66,6 +66,8 @@ function reducer(state = initialState, action = {}) { } else { return state.set("instance", ErrorInfo(action.data)); } + case failure(FETCH): + return state.set("instance", ErrorInfo()); case success(FETCH_TRACE): return state.set("instanceTrace", List(action.data.trace)).set('sourcemapUploaded', action.data.sourcemapUploaded); case success(FETCH_LIST): diff --git a/frontend/app/duck/events.js b/frontend/app/duck/events.js deleted file mode 100644 index 000bd24b7..000000000 --- a/frontend/app/duck/events.js +++ /dev/null @@ -1,81 +0,0 @@ -import { List, Map, Set } from 'immutable'; -import withRequestState, { RequestTypes } from 'Duck/requestStateCreator'; -import Event from 'Types/filter/event'; -import CustomFilter from 'Types/filter/customFilter'; -import { KEYS } from 'Types/filter/customFilter'; -import logger from 'App/logger'; -import { countries } from 'App/constants'; -import { getRE } from 'App/utils'; - -const FETCH_LIST = new RequestTypes('events/FETCH_LIST'); -const TOGGLE_SELECT = 'events/TOGGLE_SELECT'; -const SET_SELECTED = 'events/SET_SELECTED'; - -const countryOptions = Object.keys(countries).map(c => ({filterKey: KEYS.USER_COUNTRY, label: KEYS.USER_COUNTRY, type: KEYS.USER_COUNTRY, value: c, actualValue: countries[c], isFilter: true })); - -const initialState = Map({ - list: List(), - store: Set(), - - // replace? - selected: Set(), -}); - -const filterKeys = ['METADATA', KEYS.USERID, KEYS.USER_COUNTRY, KEYS.USER_BROWSER, KEYS.USER_OS, KEYS.USER_DEVICE, KEYS.REFERRER] - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - case FETCH_LIST.SUCCESS: { - const regCountry = getRE(action.params.q, 'i'); - const countryOptionsFiltered = List(countryOptions).filter(({ actualValue }) => regCountry.test(actualValue)).take(5); - - const eventList = List(action.data).concat(countryOptionsFiltered).map(item => ( - filterKeys.includes(item.type) ? - CustomFilter({...item, isFilter: true }) : - Event({...item, key: item.type, filterKey: item.type, label: item.type}) ) - ); - - return state - .set('list', eventList) - .update('store', store => store.concat(eventList)); - } - // TODO: use ids. or make a set-hoc? - case TOGGLE_SELECT: { - const { event, flag } = action; - const shouldBeInSet = typeof flag === 'boolean' - ? flag - : !state.get('selected').contains(event); - return state.update('selected', set => (shouldBeInSet - ? set.add(event) - : set.remove(event))); - } - case SET_SELECTED: - return state.set('selected', Set(action.events)); - } - return state; -}; - -export default withRequestState(FETCH_LIST, reducer); - -export function fetchList(params) { - return { - types: FETCH_LIST.toArray(), - call: client => client.get('/events/search', params), - params, - }; -} - -export function toggleSelect(event, flag) { - return { - type: TOGGLE_SELECT, - event, - flag, - }; -} - -export function setSelected(events) { - return { - type: SET_SELECTED, - events, - }; -} diff --git a/frontend/app/duck/filters.js b/frontend/app/duck/filters.js index 8d9dbbff9..544fcb93c 100644 --- a/frontend/app/duck/filters.js +++ b/frontend/app/duck/filters.js @@ -2,12 +2,12 @@ import { List, Map, Set } from 'immutable'; import { errors as errorsRoute, isRoute } from "App/routes"; import Filter from 'Types/filter'; import SavedFilter from 'Types/filter/savedFilter'; -import Event, { TYPES } from 'Types/filter/event'; +import Event from 'Types/filter/event'; import CustomFilter, { KEYS } from 'Types/filter/customFilter'; import withRequestState, { RequestTypes } from './requestStateCreator'; import { fetchList as fetchSessionList } from './sessions'; import { fetchList as fetchErrorsList } from './errors'; -import { fetchListType, fetchType, saveType, editType, initType, removeType } from './funcTools/crud/types'; +import { editType } from './funcTools/crud/types'; const ERRORS_ROUTE = errorsRoute(); diff --git a/frontend/app/duck/funcTools/tools.js b/frontend/app/duck/funcTools/tools.js index e2be77371..139c824ac 100644 --- a/frontend/app/duck/funcTools/tools.js +++ b/frontend/app/duck/funcTools/tools.js @@ -27,11 +27,6 @@ export function createItemInListUpdater(idKey = 'id', shouldAdd = true) { } } -export function createItemInListFilter(idKey = 'id') { - return id => - list => list.filter(item => item[ idKey ] !== id) -} - export const request = type => `${ type }_REQUEST`; export const success = type => `${ type }_SUCCESS`; export const failure = type => `${ type }_FAILURE`; diff --git a/frontend/app/duck/funnelFilters.js b/frontend/app/duck/funnelFilters.js index 298cdd445..c9f40846a 100644 --- a/frontend/app/duck/funnelFilters.js +++ b/frontend/app/duck/funnelFilters.js @@ -1,14 +1,11 @@ -import { fromJS, List, Map, Set } from 'immutable'; -import { errors as errorsRoute, isRoute } from "App/routes"; +import { List, Map, Set } from 'immutable'; +import { errors as errorsRoute } from "App/routes"; import Filter from 'Types/filter'; import SavedFilter from 'Types/filter/savedFilter'; -import Event, { TYPES } from 'Types/filter/event'; +import Event from 'Types/filter/event'; import CustomFilter from 'Types/filter/customFilter'; import withRequestState, { RequestTypes } from './requestStateCreator'; -import { fetchList as fetchSessionList } from './sessions'; -import { fetchList as fetchErrorsList } from './errors'; import { fetch as fetchFunnel, fetchInsights, fetchIssuesFiltered, fetchSessionsFiltered } from './funnels'; -import logger from 'App/logger'; const ERRORS_ROUTE = errorsRoute(); diff --git a/frontend/app/duck/funnels.js b/frontend/app/duck/funnels.js index 3abfcc450..330828882 100644 --- a/frontend/app/duck/funnels.js +++ b/frontend/app/duck/funnels.js @@ -99,12 +99,12 @@ const reducer = (state = initialState, action = {}) => { .set('criticalIssuesCount', action.data.issues.criticalIssuesCount) case FETCH_SESSIONS_SUCCESS: return state - .set('sessions', List(action.data.sessions).map(Session)) + .set('sessions', List(action.data.sessions).map(s => new Session(s))) .set('total', action.data.total) case FETCH_ISSUE_SUCCESS: return state .set('issue', FunnelIssue(action.data.issue)) - .set('sessions', List(action.data.sessions.sessions).map(Session)) + .set('sessions', List(action.data.sessions.sessions).map(s => new Session(s))) .set('sessionsTotal', action.data.sessions.total) case RESET_ISSUE: return state.set('isses', FunnelIssue()) @@ -145,10 +145,6 @@ const reducer = (state = initialState, action = {}) => { return state.update('list', itemInListUpdater(CustomField(action.data))) case REMOVE_SUCCESS: return state.update('list', list => list.filter(item => item.index !== action.index)); - case INIT: - return state.set('instance', Funnel(action.instance)); - case EDIT: - return state.mergeIn([ 'instance' ], action.instance); case APPLY_FILTER: return state.mergeIn([ action.filterType ], Array.isArray(action.filter) ? action.filter : Map(action.filter)); case APPLY_ISSUE_FILTER: diff --git a/frontend/app/duck/index.js b/frontend/app/duck/index.ts similarity index 55% rename from frontend/app/duck/index.js rename to frontend/app/duck/index.ts index 5ad487c93..d9412eedc 100644 --- a/frontend/app/duck/index.js +++ b/frontend/app/duck/index.ts @@ -1,75 +1,43 @@ +// @ts-ignore import { combineReducers } from 'redux-immutable'; -import jwt from './jwt'; import user from './user'; import sessions from './sessions'; -import issues from './issues'; import assignments from './assignments'; -import target from './target'; -import targetCustom from './targetCustom'; -import runs from './runs'; import filters from './filters'; import funnelFilters from './funnelFilters'; -import tests from './tests'; -import steps from './steps'; -import schedules from './schedules'; -import events from './events'; -import environments from './environments'; -import variables from './variables'; import templates from './templates'; -import alerts from './alerts'; -import notifications from './notifications'; import dashboard from './dashboard'; import components from './components'; import sources from './sources'; import members from './member'; import site from './site'; import customFields from './customField'; -import webhooks from './webhook'; import integrations from './integrations'; -import watchdogs from './watchdogs'; import rehydrate from './rehydrate'; -import announcements from './announcements'; import errors from './errors'; import funnels from './funnels'; -import config from './config'; import roles from './roles'; import customMetrics from './customMetrics'; import search from './search'; import liveSearch from './liveSearch'; -export default combineReducers({ - jwt, +const rootReducer = combineReducers({ user, sessions, - issues, assignments, - target, - targetCustom, - runs, filters, funnelFilters, - tests, - steps, - schedules, - events, - environments, - variables, + templates, - alerts, - notifications, dashboard, components, members, site, customFields, - webhooks, - watchdogs, rehydrate, - announcements, errors, funnels, - config, roles, customMetrics, search, @@ -77,3 +45,7 @@ export default combineReducers({ ...integrations, ...sources, }); + +export type RootStore = ReturnType<typeof rootReducer> + +export default rootReducer \ No newline at end of file diff --git a/frontend/app/duck/integrations/actions.js b/frontend/app/duck/integrations/actions.js index 9ab831c41..1f926014f 100644 --- a/frontend/app/duck/integrations/actions.js +++ b/frontend/app/duck/integrations/actions.js @@ -1,4 +1,3 @@ -import { array } from '../funcTools/tools'; import { fetchListType, fetchType, saveType, editType, initType, removeType } from '../funcTools/types'; export function fetchList(name) { diff --git a/frontend/app/duck/integrations/index.js b/frontend/app/duck/integrations/index.js index 0274f7c80..dec847235 100644 --- a/frontend/app/duck/integrations/index.js +++ b/frontend/app/duck/integrations/index.js @@ -12,6 +12,7 @@ import GithubConfig from 'Types/integrations/githubConfig'; import IssueTracker from 'Types/integrations/issueTracker'; import slack from './slack'; import integrations from './integrations'; +import teams from './teams' import { createIntegrationReducer } from './reducer'; @@ -29,6 +30,7 @@ export default { github: createIntegrationReducer('github', GithubConfig), issues: createIntegrationReducer('issues', IssueTracker), slack, + teams, integrations, }; diff --git a/frontend/app/duck/integrations/integrations.js b/frontend/app/duck/integrations/integrations.js index 7f6999ea8..c21952b9b 100644 --- a/frontend/app/duck/integrations/integrations.js +++ b/frontend/app/duck/integrations/integrations.js @@ -1,5 +1,4 @@ import { Map } from 'immutable'; -import withRequestState from 'Duck/requestStateCreator'; import { fetchListType } from '../funcTools/types'; import { createRequestReducer } from '../funcTools/request'; diff --git a/frontend/app/duck/integrations/slack.js b/frontend/app/duck/integrations/slack.js index 192bdd0cf..24f25be11 100644 --- a/frontend/app/duck/integrations/slack.js +++ b/frontend/app/duck/integrations/slack.js @@ -7,6 +7,7 @@ const SAVE = new RequestTypes('slack/SAVE'); const UPDATE = new RequestTypes('slack/UPDATE'); const REMOVE = new RequestTypes('slack/REMOVE'); const FETCH_LIST = new RequestTypes('slack/FETCH_LIST'); +const SEND_MSG = new RequestTypes('slack/SEND_MSG'); const EDIT = 'slack/EDIT'; const INIT = 'slack/INIT'; const idKey = 'webhookId'; @@ -39,6 +40,7 @@ export default withRequestState( { fetchRequest: FETCH_LIST, saveRequest: SAVE, + updateRequest: UPDATE, removeRequest: REMOVE, }, reducer @@ -61,7 +63,7 @@ export function save(instance) { export function update(instance) { return { types: UPDATE.toArray(), - call: (client) => client.put(`/integrations/slack/${instance.webhookId}`, instance.toData()), + call: (client) => client.post(`/integrations/slack/${instance.webhookId}`, instance.toData()), }; } @@ -86,3 +88,12 @@ export function remove(id) { id, }; } + +// https://api.openreplay.com/5587/integrations/slack/notify/315/sessions/7856803626558104 +// +export function sendSlackMsg({ integrationId, entity, entityId, data }) { + return { + types: SEND_MSG.toArray(), + call: (client) => client.post(`/integrations/slack/notify/${integrationId}/${entity}/${entityId}`, data) + } +} diff --git a/frontend/app/duck/integrations/teams.js b/frontend/app/duck/integrations/teams.js new file mode 100644 index 000000000..f9e22412e --- /dev/null +++ b/frontend/app/duck/integrations/teams.js @@ -0,0 +1,100 @@ +import { Map, List } from 'immutable'; +import withRequestState, { RequestTypes } from 'Duck/requestStateCreator'; +import Config from 'Types/integrations/slackConfig'; +import { createItemInListUpdater } from '../funcTools/tools'; + +const SAVE = new RequestTypes('msteams/SAVE'); +const UPDATE = new RequestTypes('msteams/UPDATE'); +const REMOVE = new RequestTypes('msteams/REMOVE'); +const FETCH_LIST = new RequestTypes('msteams/FETCH_LIST'); +const SEND_MSG = new RequestTypes('msteams/SEND_MSG'); + +const EDIT = 'msteams/EDIT'; +const INIT = 'msteams/INIT'; +const idKey = 'webhookId'; +const itemInListUpdater = createItemInListUpdater(idKey); + +const initialState = Map({ + instance: Config(), + list: List(), +}); + +const reducer = (state = initialState, action = {}) => { + switch (action.type) { + case FETCH_LIST.SUCCESS: + return state.set('list', List(action.data).map(Config)); + case UPDATE.SUCCESS: + case SAVE.SUCCESS: + const config = Config(action.data); + return state.update('list', itemInListUpdater(config)).set('instance', config); + case REMOVE.SUCCESS: + return state.update('list', (list) => list.filter((item) => item.webhookId !== action.id)).set('instance', Config()); + case EDIT: + return state.mergeIn(['instance'], action.instance); + case INIT: + return state.set('instance', Config(action.instance)); + } + return state; +}; + +export default withRequestState( + { + fetchRequest: FETCH_LIST, + saveRequest: SAVE, + updateRequest: UPDATE, + removeRequest: REMOVE, + }, + reducer +); + +export function fetchList() { + return { + types: FETCH_LIST.toArray(), + call: (client) => client.get('/integrations/msteams/channels'), + }; +} + +export function save(instance) { + return { + types: SAVE.toArray(), + call: (client) => client.post(`/integrations/msteams`, instance.toData()), + }; +} + +export function update(instance) { + return { + types: UPDATE.toArray(), + call: (client) => client.post(`/integrations/msteams/${instance.webhookId}`, instance.toData()), + }; +} + +export function edit(instance) { + return { + type: EDIT, + instance, + }; +} + +export function init(instance) { + return { + type: INIT, + instance, + }; +} + +export function remove(id) { + return { + types: REMOVE.toArray(), + call: (client) => client.delete(`/integrations/msteams/${id}`), + id, + }; +} + +// https://api.openreplay.com/5587/integrations/msteams/notify/315/sessions/7856803626558104 +// +export function sendMsTeamsMsg({ integrationId, entity, entityId, data }) { + return { + types: SEND_MSG.toArray(), + call: (client) => client.post(`/integrations/msteams/notify/${integrationId}/${entity}/${entityId}`, data) + } +} diff --git a/frontend/app/duck/issues.js b/frontend/app/duck/issues.js index 6c2c70733..ce68d31e4 100644 --- a/frontend/app/duck/issues.js +++ b/frontend/app/duck/issues.js @@ -2,14 +2,13 @@ import Assignment from 'Types/session/assignment'; import Activity from 'Types/session/activity'; import { List, Map, Set } from 'immutable'; import withRequestState, { RequestTypes } from 'Duck/requestStateCreator'; -import { createListUpdater, createItemInListUpdater } from './funcTools/tools'; +import { createListUpdater } from './funcTools/tools'; import { editType, initType } from './funcTools/crud/types'; import { createInit, createEdit } from './funcTools/crud'; const idKey = 'id'; const name = 'assignment'; const listUpdater = createListUpdater(idKey); -const itemInListUpdater = createItemInListUpdater(idKey); const FETCH_ASSIGNMENTS = new RequestTypes('asignment/FETCH_ASSIGNMENTS'); const FETCH_ISSUE = new RequestTypes('asignment/FETCH_ISSUE'); @@ -23,8 +22,8 @@ const RESET_ACTIVE_ISSUE = 'assignment/RESET_ACTIVE_ISSUE'; const initialState = Map({ list: List(), - instance: Assignment(), - activeIssue: Assignment(), + instance: new Assignment(), + activeIssue: new Assignment(), issueTypes: List(), issueTypeIcons: Set(), users: List(), @@ -39,9 +38,9 @@ const reducer = (state = initialState, action = {}) => { case FETCH_PROJECTS.SUCCESS: return state.set('projects', List(action.data)); case FETCH_ASSIGNMENTS.SUCCESS: - return state.set('list', List(action.data).map(Assignment)); + return state.set('list', action.data.map(as => new Assignment(as))); case ADD_ACTIVITY.SUCCESS: - const instance = Assignment(action.data); + const instance = new Assignment(action.data); return listUpdater(state, instance); case FETCH_META.SUCCESS: issueTypes = action.data.issueTypes; @@ -53,16 +52,16 @@ const reducer = (state = initialState, action = {}) => { .set('users', List(action.data.users)) .set('issueTypeIcons', issueTypeIcons) case FETCH_ISSUE.SUCCESS: - return state.set('activeIssue', Assignment({ ...action.data, users})); + return state.set('activeIssue', new Assignment({ ...action.data, users})); case RESET_ACTIVE_ISSUE: - return state.set('activeIssue', Assignment()); + return state.set('activeIssue', new Assignment()); case ADD_MESSAGE.SUCCESS: const user = users.filter(user => user.id === action.data.author).first(); - const activity = Activity({ type: 'message', user, ...action.data,}); + const activity = new Activity({ type: 'message', user, ...action.data,}); return state.updateIn([ 'activeIssue', 'activities' ], list => list.push(activity)); case INIT: action.instance.issueType = issueTypes.length > 0 ? issueTypes[0].id : ''; - return state.set('instance', Assignment(action.instance)); + return state.set('instance', new Assignment(action.instance)); case EDIT: return state.mergeIn([ 'instance' ], action.instance); default: @@ -101,13 +100,6 @@ export function fetchProjects() { } } -export function fetchIssue(sessionId, id) { - return { - types: FETCH_ISSUE.toArray(), - call: client => client.get(`/sessions/${ sessionId }/assign/jira/${ id }`) - } -} - export function fetchMeta(projectId) { return { types: FETCH_META.toArray(), diff --git a/frontend/app/duck/jwt.js b/frontend/app/duck/jwt.js deleted file mode 100644 index 4d4147f34..000000000 --- a/frontend/app/duck/jwt.js +++ /dev/null @@ -1,19 +0,0 @@ -export const UPDATE = 'jwt/UPDATE'; -export const DELETE = 'jwt/DELETE'; - -export default (state = null, action = {}) => { - switch (action.type) { - case UPDATE: - return action.data; - case DELETE: - return null; - } - return state; -}; - -export function setJwt(data) { - return { - type: UPDATE, - data, - }; -} diff --git a/frontend/app/duck/liveSearch.js b/frontend/app/duck/liveSearch.js index 7838a740a..499f6c740 100644 --- a/frontend/app/duck/liveSearch.js +++ b/frontend/app/duck/liveSearch.js @@ -1,9 +1,9 @@ -import { List, Map } from 'immutable'; +import { List, Map } from 'immutable'; import { fetchListType, fetchType, editType } from './funcTools/crud'; import { createRequestReducer } from './funcTools/request'; import { mergeReducers, success } from './funcTools/tools'; import Filter from 'Types/filter'; -import { liveFiltersMap, filtersMap } from 'Types/filter/newFilter'; +import { liveFiltersMap } from 'Types/filter/newFilter'; import { filterMap, checkFilterValue, hasFilterApplied } from './search'; import Session from 'Types/session'; @@ -23,6 +23,7 @@ const initialState = Map({ instance: new Filter({ filters: [], sort: '' }), filterSearchList: {}, currentPage: 1, + total: 0, }); function reducer(state = initialState, action = {}) { @@ -35,7 +36,7 @@ function reducer(state = initialState, action = {}) { return state.set('currentPage', action.page); case success(FETCH_SESSION_LIST): const { sessions, total } = action.data; - const list = List(sessions).map(Session); + const list = List(sessions).map(s => new Session(s)); return state .set('list', list) .set('total', total); @@ -63,6 +64,11 @@ export default mergeReducers( }), ); +export const customSetSessions = (data) => ({ + type: success(FETCH_SESSION_LIST), + data +}) + const reduceThenFetchResource = actionCreator => (...args) => (dispatch, getState) => { dispatch(actionCreator(...args)); const filter = getState().getIn([ 'liveSearch', 'instance']).toData(); @@ -138,4 +144,4 @@ export function fetchFilterSearch(params) { call: client => client.get('/events/search', params), params, }; -} \ No newline at end of file +} diff --git a/frontend/app/duck/member.js b/frontend/app/duck/member.js index 31cccb395..ce12c1659 100644 --- a/frontend/app/duck/member.js +++ b/frontend/app/duck/member.js @@ -1,7 +1,7 @@ import { Map } from 'immutable'; import Member from 'Types/member'; import crudDuckGenerator from './tools/crudDuck'; -import withRequestState, { RequestTypes } from 'Duck/requestStateCreator'; +import { RequestTypes } from 'Duck/requestStateCreator'; import { reduceDucks } from 'Duck/tools'; const GENERATE_LINK = new RequestTypes('member/GENERATE_LINK'); @@ -37,12 +37,4 @@ export function save(instance) { }; } -export function generateInviteLink(instance) { - return { - types: GENERATE_LINK.toArray(), - call: client => client.get(`/client/members/${ instance.id }/reset`), - id: instance.id - }; -} - export default reduceDucks(crudDuck, { initialState, reducer }).reducer; diff --git a/frontend/app/duck/notifications.js b/frontend/app/duck/notifications.js deleted file mode 100644 index 129349792..000000000 --- a/frontend/app/duck/notifications.js +++ /dev/null @@ -1,63 +0,0 @@ -import { List, Map } from 'immutable'; -import Notification from 'Types/notification'; -import { mergeReducers, success, array, request, createListUpdater } from './funcTools/tools'; -import { createRequestReducer } from './funcTools/request'; -import { - createCRUDReducer, - getCRUDRequestTypes, - createFetchList, -} from './funcTools/crud'; - -const name = 'notification'; -const idKey = 'notificationId'; -const SET_VIEWED = 'notifications/SET_VIEWED'; -const CLEAR_ALL = 'notifications/CLEAR_ALL'; -const SET_VIEWED_SUCCESS = success(SET_VIEWED); -const CLEAR_ALL_SUCCESS = success(CLEAR_ALL); - -const listUpdater = createListUpdater(idKey); - -const initialState = Map({ - list: List(), -}); - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - case SET_VIEWED_SUCCESS: - if (!action.data) return state; - const item = state.get('list').find(item => item[ idKey ] === action.id) - return listUpdater(state, Notification({...item.toJS(), createdAt: item.createdAt.ts, viewed: true })); - case CLEAR_ALL_SUCCESS: - if (!action.data) return state; - return state.update('list', list => list.map(l => Notification({...l.toJS(), createdAt: l.createdAt.ts, viewed: true }))) - } - return state; -}; - -export const fetchList = createFetchList(name); - -export default mergeReducers( - reducer, - createCRUDReducer(name, Notification, idKey), - createRequestReducer({ - setViewed: SET_VIEWED, - clearAll: CLEAR_ALL, - ...getCRUDRequestTypes(name), - }), -); - -export function setViewed(id) { - return { - types: array(SET_VIEWED), - call: client => client.get(`/notifications/${ id }/view`), - id, - }; -} - -export function clearAll(params) { - return { - types: array(CLEAR_ALL), - call: client => client.post('/notifications/view', params), - }; -} - diff --git a/frontend/app/duck/rehydrate.js b/frontend/app/duck/rehydrate.js index 4dd424ba4..df0b4ff50 100644 --- a/frontend/app/duck/rehydrate.js +++ b/frontend/app/duck/rehydrate.js @@ -1,7 +1,7 @@ import { List, Map } from 'immutable'; import RehydrateJob from 'Types/rehydrateJob'; -import { mergeReducers, success, array } from './funcTools/tools'; +import { mergeReducers } from './funcTools/tools'; import { createRequestReducer } from './funcTools/request'; import { createCRUDReducer, diff --git a/frontend/app/duck/runs.js b/frontend/app/duck/runs.js deleted file mode 100644 index 30b8051ac..000000000 --- a/frontend/app/duck/runs.js +++ /dev/null @@ -1,7 +0,0 @@ -import Run from 'Types/run'; -import crudDuckGenerator from './tools/crudDuck'; - -const crudDuck = crudDuckGenerator('run', Run); -export const { fetchList, fetch, init, edit, save, remove } = crudDuck.actions; - -export default crudDuck.reducer; diff --git a/frontend/app/duck/schedules.js b/frontend/app/duck/schedules.js deleted file mode 100644 index 3f13e9188..000000000 --- a/frontend/app/duck/schedules.js +++ /dev/null @@ -1,14 +0,0 @@ -import Schedule from 'Types/schedule'; -import crudDuckGenerator from './tools/crudDuck'; - -const crudDuck = crudDuckGenerator('scheduler', Schedule); -export const { fetchList, fetch, init, edit, remove } = crudDuck.actions; - -export function save(instance) { // TODO: fix the crudDuckGenerator - return { - types: crudDuck.actionTypes.SAVE.toArray(), - call: client => client.post(`/schedulers${!!instance.schedulerId ? '/' + instance.schedulerId : '' }`, instance), - }; -} - -export default crudDuck.reducer; diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index 569eec435..31f028bc3 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -1,7 +1,7 @@ import { List, Map } from 'immutable'; import { fetchListType, fetchType, saveType, removeType, editType } from './funcTools/crud'; import { createRequestReducer, ROOT_KEY } from './funcTools/request'; -import { array, success, createListUpdater, mergeReducers } from './funcTools/tools'; +import { array, success, mergeReducers } from './funcTools/tools'; import Filter from 'Types/filter'; import SavedFilter from 'Types/filter/savedFilter'; import { errors as errorsRoute, isRoute } from 'App/routes'; @@ -71,6 +71,7 @@ function reducer(state = initialState, action = {}) { case EDIT: return state.mergeIn(['instance'], action.instance).set('currentPage', 1); case APPLY: + return action.fromUrl ? state.set('instance', Filter(action.filter)) : state.mergeIn(['instance'], action.filter).set('currentPage', 1); case success(FETCH): return state.set('instance', action.data); @@ -242,6 +243,12 @@ export const applyFilter = reduceThenFetchResource((filter, force = false) => ({ force, })); +export const updateFilter = (filter, force = false) => ({ + type: APPLY, + filter, + force, +}); + export const updateCurrentPage = reduceThenFetchResource((page) => ({ type: UPDATE_CURRENT_PAGE, page, @@ -318,9 +325,17 @@ export function fetchFilterSearch(params) { } export const clearSearch = () => (dispatch, getState) => { - // const filter = getState().getIn(['search', 'instance']); - // dispatch(applySavedSearch(new SavedFilter({}))); - dispatch(edit(new Filter({ filters: [] }))); + const instance = getState().getIn(['search', 'instance']); + dispatch( + edit( + new Filter({ + rangeValue: instance.rangeValue, + startDate: instance.startDate, + endDate: instance.endDate, + filters: [], + }) + ) + ); return dispatch({ type: CLEAR_SEARCH, }); @@ -359,6 +374,7 @@ export const addFilterByKeyAndValue = defaultFilter.sourceOperator = sourceOperator; defaultFilter.source = source; } + dispatch(addFilter(defaultFilter)); }; diff --git a/frontend/app/duck/sessions.js b/frontend/app/duck/sessions.ts similarity index 71% rename from frontend/app/duck/sessions.js rename to frontend/app/duck/sessions.ts index ecce3d713..b87afaaa4 100644 --- a/frontend/app/duck/sessions.js +++ b/frontend/app/duck/sessions.ts @@ -1,6 +1,7 @@ import { List, Map } from 'immutable'; import Session from 'Types/session'; import ErrorStack from 'Types/session/errorStack'; +import { Location } from 'Types/session/event' import Watchdog from 'Types/watchdog'; import { clean as cleanParams } from 'App/api_client'; import withRequestState, { RequestTypes } from './requestStateCreator'; @@ -9,7 +10,6 @@ import { LAST_7_DAYS } from 'Types/app/period'; import { getDateRangeFromValue } from 'App/dateRange'; const name = 'sessions'; -const INIT = 'sessions/INIT'; const FETCH_LIST = new RequestTypes('sessions/FETCH_LIST'); const FETCH_AUTOPLAY_LIST = new RequestTypes('sessions/FETCH_AUTOPLAY_LIST'); const FETCH = new RequestTypes('sessions/FETCH'); @@ -38,6 +38,8 @@ const SET_SESSION_PATH = 'sessions/SET_SESSION_PATH'; const LAST_PLAYED_SESSION_ID = `${name}/LAST_PLAYED_SESSION_ID`; const SET_ACTIVE_TAB = 'sessions/SET_ACTIVE_TAB'; +const CLEAR_CURRENT_SESSION = 'sessions/CLEAR_CURRENT_SESSION' + const range = getDateRangeFromValue(LAST_7_DAYS); const defaultDateFilters = { url: '', @@ -46,10 +48,10 @@ const defaultDateFilters = { endDate: range.end.unix() * 1000, }; -const initialState = Map({ - list: List(), +const initObj = { + list: [], sessionIds: [], - current: Session(), + current: new Session(), total: 0, keyMap: Map(), wdTypeCount: Map(), @@ -60,8 +62,9 @@ const initialState = Map({ eventsIndex: [], sourcemapUploaded: true, filteredEvents: null, + eventsQuery: '', showChatWindow: false, - liveSessions: List(), + liveSessions: [], visitedEvents: List(), insights: List(), insightFilters: defaultDateFilters, @@ -70,35 +73,35 @@ const initialState = Map({ timelinePointer: null, sessionPath: {}, lastPlayedSessionId: null, - timeLineTooltip: { time: 0, offset: 0, isVisible: false }, + timeLineTooltip: { time: 0, offset: 0, isVisible: false, timeStr: '' }, createNoteTooltip: { time: 0, isVisible: false, isEdit: false, note: null }, -}); +} -const reducer = (state = initialState, action = {}) => { +const initialState = Map(initObj); + +interface IAction extends Record<string, any>{ + type: string; + data: any; +} + +const reducer = (state = initialState, action: IAction) => { switch (action.type) { - case INIT: - return state.set('current', Session(action.session)); - // case FETCH_LIST.REQUEST: - // return action.clear ? state.set('list', List()) : state; case FETCH_ERROR_STACK.SUCCESS: - return state.set('errorStack', List(action.data.trace).map(ErrorStack)).set('sourcemapUploaded', action.data.sourcemapUploaded); + return state.set('errorStack', List(action.data.trace).map(es => new ErrorStack(es))).set('sourcemapUploaded', action.data.sourcemapUploaded); case FETCH_LIVE_LIST.SUCCESS: - const liveList = List(action.data.sessions).map((s) => new Session({ ...s, live: true })); + const liveList = action.data.sessions.map((s) => new Session({ ...s, live: true })); return state.set('liveSessions', liveList); case FETCH_LIST.SUCCESS: const { sessions, total } = action.data; - const list = List(sessions).map(Session); + const list = sessions.map(s => new Session(s)); return state .set('list', list) - .set('sessionIds', list.map(({ sessionId }) => sessionId).toJS()) - .set( - 'favoriteList', - list.filter(({ favorite }) => favorite) - ) + .set('sessionIds', list.map(({ sessionId }) => sessionId)) + .set('favoriteList', list.filter(({ favorite }) => favorite)) .set('total', total); case FETCH_AUTOPLAY_LIST.SUCCESS: - let sessionIds = state.get('sessionIds'); + let sessionIds = state.get('sessionIds') as []; sessionIds = sessionIds.concat(action.data.map(i => i.sessionId + '')) return state.set('sessionIds', sessionIds.filter((i, index) => sessionIds.indexOf(i) === index )) case SET_AUTOPLAY_VALUES: { @@ -110,29 +113,36 @@ const reducer = (state = initialState, action = {}) => { case SET_EVENT_QUERY: { const events = state.get('current').events; const query = action.filter.query; - // const filter = action.filter.filter; const searchRe = getRE(query, 'i'); - let filteredEvents = query ? events.filter((e) => searchRe.test(e.url) || searchRe.test(e.value) || searchRe.test(e.label)) : null; - // if (filter) { - // filteredEvents = filteredEvents ? filteredEvents.filter(e => e.type === filter) : events.filter(e => e.type === filter); - // } - return state.set('filteredEvents', filteredEvents); + const filteredEvents = query ? events.filter( + (e) => searchRe.test(e.url) + || searchRe.test(e.value) + || searchRe.test(e.label) + || searchRe.test(e.type) + || (e.type === 'LOCATION' && searchRe.test('visited')) + ) : null; + + return state.set('filteredEvents', filteredEvents).set('eventsQuery', query); + } + case CLEAR_CURRENT_SESSION: { + return state.set('current', new Session()) + .set('eventsIndex', []) + .set('visitedEvents', List()) + .set('host', ''); } case FETCH.SUCCESS: { - // TODO: more common.. or TEMP + // TODO: more common.. or TEMP filters', 'appliedFilter const events = action.filter.events; - // const filters = action.filter.filters; - const current = state.get('list').find(({ sessionId }) => sessionId === action.data.sessionId) || Session(); - const session = Session(action.data); + const session = new Session(action.data); - const matching = []; + const matching: number[] = []; - const visitedEvents = []; - const tmpMap = {}; + const visitedEvents: Location[] = []; + const tmpMap = new Set(); session.events.forEach((event) => { - if (event.type === 'LOCATION' && !tmpMap.hasOwnProperty(event.url)) { - tmpMap[event.url] = event.url; + if (event.type === 'LOCATION' && !tmpMap.has(event.url)) { + tmpMap.add(event.url); visitedEvents.push(event); } }); @@ -151,23 +161,34 @@ const reducer = (state = initialState, action = {}) => { }); }); return state - .set('current', current.merge(session)) + .set('current', session) .set('eventsIndex', matching) .set('visitedEvents', visitedEvents) .set('host', visitedEvents[0] && visitedEvents[0].host); } case FETCH_FAVORITE_LIST.SUCCESS: - return state.set('favoriteList', List(action.data).map(Session)); + return state.set('favoriteList', action.data.map(s => new Session(s))); case TOGGLE_FAVORITE.SUCCESS: { const id = action.sessionId; - const session = state.get('list').find(({ sessionId }) => sessionId === id); + let mutableState = state + const list = state.get('list') as unknown as Session[] + const sessionIdx = list.findIndex(({ sessionId }) => sessionId === id); + const session = list[sessionIdx] + const current = state.get('current') as unknown as Session; const wasInFavorite = state.get('favoriteList').findIndex(({ sessionId }) => sessionId === id) > -1; - return state - .update('current', (currentSession) => (currentSession.sessionId === id ? currentSession.set('favorite', !wasInFavorite) : currentSession)) - .update('list', (list) => list.map((listSession) => (listSession.sessionId === id ? listSession.set('favorite', !wasInFavorite) : listSession))) - .update('favoriteList', (list) => session ? - wasInFavorite ? list.filter(({ sessionId }) => sessionId !== id) : list.push(session.set('favorite', true)) : list + if (session && !wasInFavorite) { + session.favorite = true + mutableState = mutableState.updateIn(['list', sessionIdx], () => session) + } + if (current.sessionId === id) { + mutableState = mutableState.update('current', + (s: Session) => ({ ...s, favorite: !wasInFavorite}) + ) + } + return mutableState + .update('favoriteList', (list: Session[]) => session ? + wasInFavorite ? list.filter(({ sessionId }) => sessionId !== id) : list.push(session) : list ); } case SORT: { @@ -176,19 +197,12 @@ const reducer = (state = initialState, action = {}) => { diff = diff === 0 ? s1.startedAt - s2.startedAt : diff; return action.sign * diff; }; - return state.update('list', (list) => list.sort(comparator)).update('favoriteList', (list) => list.sort(comparator)); - } - case REDEFINE_TARGET: { - // TODO: update for list - const { label, path } = action.target; - return state.updateIn(['current', 'events'], (list) => - list.map((event) => (event.target && event.target.path === path ? event.setIn(['target', 'label'], label) : event)) - ); + return state.update('list', (list: Session[]) => list.sort(comparator)).update('favoriteList', (list: Session[]) => list.sort(comparator)); } case SET_ACTIVE_TAB: const allList = action.tab.type === 'all' ? state.get('list') : state.get('list').filter((s) => s.issueTypes.includes(action.tab.type)); - return state.set('activeTab', action.tab).set('sessionIds', allList.map(({ sessionId }) => sessionId).toJS()); + return state.set('activeTab', action.tab).set('sessionIds', allList.map(({ sessionId }) => sessionId)); case SET_TIMEZONE: return state.set('timezone', action.timezone); case TOGGLE_CHAT_WINDOW: @@ -209,38 +223,44 @@ const reducer = (state = initialState, action = {}) => { case SET_EDIT_NOTE_TOOLTIP: return state.set('createNoteTooltip', action.noteTooltip); case FILTER_OUT_NOTE: - return state.updateIn(['current', 'notesWithEvents'], (list) => - list.filter(evt => !evt.noteId || evt.noteId !== action.noteId) + return state.updateIn(['current'], + (session: Session) => ({ + ...session, + notesWithEvents: session.notesWithEvents.filter(item => { + if ('noteId' in item) { + return item.noteId !== action.noteId + } + return true + }) + }) ) case ADD_NOTE: - return state.updateIn(['current', 'notesWithEvents'], (list) => - list.push(action.note).sort((a, b) => { - const aTs = a.time || a.timestamp - const bTs = b.time || b.timestamp + const session = state.get('current') as Session; + session.notesWithEvents = [...session.notesWithEvents, action.note].sort((a, b) => { + const aTs = a.time || a.timestamp + const bTs = b.time || b.timestamp - return aTs - bTs - }) - ) + return aTs - bTs + }); + return state.set('current', session); case UPDATE_NOTE: - const index = state.getIn(['current', 'notesWithEvents']).findIndex(item => item.noteId === action.note.noteId) - return state.setIn(['current', 'notesWithEvents', index], action.note) + const currSession = state.get('current') as Session; + const noteIndex = currSession.notesWithEvents.findIndex(item => item.noteId === action.note.noteId) + currSession.notesWithEvents[noteIndex] = action.note; + return state.set('current', currSession); case SET_SESSION_PATH: return state.set('sessionPath', action.path); case LAST_PLAYED_SESSION_ID: - return updateListItem(state, action.sessionId, { viewed: true }).set('lastPlayedSessionId', action.sessionId); + const sessionList = state.get('list') as unknown as Session[]; + const sIndex = sessionList.findIndex(({ sessionId }) => sessionId === action.sessionId); + if (sIndex === -1) return state; + + return state.updateIn(['list', sIndex], (session: Session) => ({ ...session, viewed: true })); default: return state; } }; -function updateListItem(state, sourceSessionId, instance) { - const list = state.get('list'); - const index = list.findIndex(({ sessionId }) => sessionId === sourceSessionId); - if (index === -1) return state; - - return state.updateIn(['list', index], (session) => session.merge(instance)); -} - export default withRequestState( { _: [FETCH, FETCH_LIST], @@ -253,13 +273,6 @@ export default withRequestState( reducer ); -function init(session) { - return { - type: INIT, - session, - }; -} - export const fetchList = (params = {}, force = false) => (dispatch) => { @@ -308,6 +321,19 @@ export const fetch = }); }; +export function clearCurrentSession() { + return { + type: CLEAR_CURRENT_SESSION + } +} + +export const setCustomSession = (session, filter) => + (dispatch, getState) => { dispatch({ + type: FETCH.SUCCESS, + filter: getState().getIn(['filters', 'appliedFilter']), + data: session, +})} + export function toggleFavorite(sessionId) { return { types: TOGGLE_FAVORITE.toArray(), @@ -316,13 +342,6 @@ export function toggleFavorite(sessionId) { }; } -export function fetchFavoriteList() { - return { - types: FETCH_FAVORITE_LIST.toArray(), - call: (client) => client.get('/sessions/favorite'), - }; -} - export function fetchInsights(params) { return { types: FETCH_INSIGHTS.toArray(), @@ -353,13 +372,6 @@ export function sort(sortKey, sign = 1, listName = 'list') { }; } -export function redefineTarget(target) { - return { - type: REDEFINE_TARGET, - target, - }; -} - export const setAutoplayValues = (sessionId) => { return { type: SET_AUTOPLAY_VALUES, @@ -454,4 +466,4 @@ export function updateLastPlayedSession(sessionId) { type: LAST_PLAYED_SESSION_ID, sessionId, }; -} \ No newline at end of file +} diff --git a/frontend/app/duck/site.js b/frontend/app/duck/site.js index 54d1e1688..bc978128d 100644 --- a/frontend/app/duck/site.js +++ b/frontend/app/duck/site.js @@ -10,7 +10,6 @@ import { import { createCRUDReducer, getCRUDRequestTypes, - createFetchList, createInit, createEdit, createRemove, @@ -62,11 +61,12 @@ const reducer = (state = initialState, action = {}) => { return state.setIn([ 'instance', 'gdpr' ], gdpr); case FETCH_LIST_SUCCESS: let siteId = state.get("siteId"); - const siteExists = action.data.map(s => s.projectId).includes(siteId); - if (action.siteIdFromPath) { + const siteIds = action.data.map(s => parseInt(s.projectId)) + const siteExists = siteIds.includes(siteId); + if (action.siteIdFromPath && siteIds.includes(parseInt(action.siteIdFromPath))) { siteId = action.siteIdFromPath; } else if (!siteId || !siteExists) { - siteId = !!action.data.find(s => s.projectId === parseInt(storedSiteId)) + siteId = siteIds.includes(parseInt(storedSiteId)) ? storedSiteId : action.data[0].projectId; } @@ -103,11 +103,12 @@ export function fetchGDPR(siteId) { } } -export function saveGDPR(siteId, gdpr) { - return { +export const saveGDPR = (siteId, gdpr) => (dispatch, getState) => { + const g = getState().getIn(['site', 'instance', 'gdpr']); + return dispatch({ types: array(SAVE_GDPR), - call: client => client.post(`/${ siteId }/gdpr`, gdpr.toData()), - }; + call: client => client.post(`/${ siteId }/gdpr`, g.toData()), + }); } export function fetchList(siteId) { diff --git a/frontend/app/duck/steps.js b/frontend/app/duck/steps.js deleted file mode 100644 index 02bfdbb90..000000000 --- a/frontend/app/duck/steps.js +++ /dev/null @@ -1,77 +0,0 @@ -import { List, Map } from 'immutable'; -import { RequestTypes } from 'Duck/requestStateCreator'; -import Step from 'Types/step'; -import Event from 'Types/filter/event'; -import { getRE } from 'App/utils'; -import Test from 'Types/appTest'; -import { countries } from 'App/constants'; -import { KEYS } from 'Types/filter/customFilter'; - -const countryOptions = Object.keys(countries).map(c => ({filterKey: KEYS.USER_COUNTRY, label: KEYS.USER_COUNTRY, type: KEYS.USER_COUNTRY, value: c, actualValue: countries[c], isFilter: true })); - -const INIT = 'steps/INIT'; -const EDIT = 'steps/EDIT'; - -const SET_TEST = 'steps/SET_TEST'; -const FETCH_LIST = new RequestTypes('steps/FETCH_LIST'); - -const initialState = Map({ - list: List(), - test: Test(), - instance: Step(), - editingIndex: null, -}); - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - case FETCH_LIST.SUCCESS: { - return state.set('list', List(action.data).map(i => { - const type = i.type === 'navigate' ? i.type : 'location'; - return {...i, type: type.toUpperCase()} - })) - } - case INIT: - return state - .set('instance', Step(action.instance)) - .set('editingIndex', action.index) - .set('test', Test()); - case EDIT: - return state.mergeIn([ 'instance' ], action.instance); - case SET_TEST: - return state.set('test', Test(action.test)); - } - return state; -}; - -export default reducer; - -export function init(instance, index) { - return { - type: INIT, - instance, - index, - }; -} - -export function edit(instance) { - return { - type: EDIT, - instance, - }; -} - -export function setTest(test) { - return { - type: SET_TEST, - test, - }; -} - - -export function fetchList(params) { - return { - types: FETCH_LIST.toArray(), - call: client => client.get('/tests/steps/search', params), - params, - }; -} diff --git a/frontend/app/duck/target.js b/frontend/app/duck/target.js deleted file mode 100644 index b1f0e337b..000000000 --- a/frontend/app/duck/target.js +++ /dev/null @@ -1,33 +0,0 @@ -import { Map } from 'immutable'; -import Target from 'Types/target'; -import { RequestTypes } from 'Duck/requestStateCreator'; -import crudDuckGenerator from 'Duck/tools/crudDuck'; -import { reduceDucks } from 'Duck/tools'; - -const FETCH_DEFINED = new RequestTypes('targets/FETCH_DEFINED'); - -const initialState = Map({ - definedPercent: 0, -}); - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - case FETCH_DEFINED.SUCCESS: - return state.set( - 'definedPercent', - Math.round((action.data.labeled / action.data.total) * 100), - ); - } - return state; -}; - -const crudDuck = crudDuckGenerator('target', Target); -export const { fetchList, init, edit, save, remove } = crudDuck.actions; -export default reduceDucks(crudDuck, { initialState, reducer }).reducer; - -export function fetchDefinedTargetsCount() { - return { - types: FETCH_DEFINED.toArray(), - call: client => client.get('/targets/count'), - }; -} diff --git a/frontend/app/duck/targetCustom.js b/frontend/app/duck/targetCustom.js deleted file mode 100644 index fd63ed657..000000000 --- a/frontend/app/duck/targetCustom.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Map } from 'immutable'; -import TargetCustom from 'Types/targetCustom'; -import crudDuckGenerator from 'Duck/tools/crudDuck'; -import { reduceDucks } from 'Duck/tools'; - - -const crudDuck = crudDuckGenerator('customTarget', TargetCustom, { endpoints: { - fetchList: '/targets_temp', - save: '/targets_temp', - remove: '/targets_temp', -}}); -export const { fetchList, init, edit, save, remove } = crudDuck.actions; -export default crudDuck.reducer; diff --git a/frontend/app/duck/tests/index.js b/frontend/app/duck/tests/index.js deleted file mode 100644 index eda7edc43..000000000 --- a/frontend/app/duck/tests/index.js +++ /dev/null @@ -1,186 +0,0 @@ -import { List, Map, Set } from 'immutable'; -import Test from 'Types/appTest'; -import stepFromJS from 'Types/step'; -import crudDuckGenerator from 'Duck/tools/crudDuck'; -import { reduceDucks } from 'Duck/tools'; -import runsDuck from './runs'; -import Run from 'Types/run'; - -const sampleRun = Run({"runId":8,"testId":7,"name":"test import","createdAt":1601481986264,"createdBy":283,"starter":"on-demand","state":"failed","steps":[{"label":"Open URL","order":0,"title":"navigate","status":"passed","startedAt":1601647536513,"finishedAt":1601647546211,"screenshot":"https://parrot-tests.s3.eu-central-1.amazonaws.com/115/7/8/screenshots/1601647546211.jpg","executionTime":9698},{"label":"Open URL","order":1,"title":"Visit OpenReplay","status":"passed","startedAt":1601647548354,"finishedAt":1601647556991,"screenshot":"https://parrot-tests.s3.eu-central-1.amazonaws.com/115/7/8/screenshots/1601647556991.jpg","executionTime":8637},{"info":"Unhandled promise rejection: TimeoutError: waiting for selector \"[name=\"email\"]\" failed: timeout 30000ms exceeded","input":"failed","label":"Send Keys to Element","order":2,"title":"input","status":"failed","startedAt":1601647559091,"finishedAt":1601647589099,"screenshot":"https://parrot-tests.s3.eu-central-1.amazonaws.com/115/7/8/screenshots/1601647589099.jpg","executionTime":30008}],"browser":"chrome","meta":{"startedAt":1601487715818},"location":"FR","startedAt":1601647524205,"finishedAt":1601647591217,"network":[{"url":"http://yahoo.fr/","method":"GET","duration":1760,"requestID":"769C483871CB3D35DF4BB1CD7D3258C4","timestamp":1601647537533,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null,"response":{"data":[{"key":"user_id","index":1},{"key":"virtual_number","index":2}]}},{"url":"http://fr.yahoo.com/","method":"GET","duration":1112,"requestID":"769C483871CB3D35DF4BB1CD7D3258C4","timestamp":1601647539293,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null,"payload":{"data":[{"key":"user_id","index":1},{"key":"virtual_number","index":2}]}},{"url":"https://fr.yahoo.com/","method":"GET","duration":1204,"requestID":"769C483871CB3D35DF4BB1CD7D3258C4","timestamp":1601647540405,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://guce.yahoo.com/consent?brandType=eu&gcrumb=bxFB6Ac&lang=fr-FR&done=https%3A%2F%2Ffr.yahoo.com%2F","method":"GET","duration":1173,"requestID":"769C483871CB3D35DF4BB1CD7D3258C4","timestamp":1601647541609,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://consent.yahoo.com/v2/collectConsent?sessionId=3_cc-session_fab600d0-8323-4b52-88c1-5698e6288f48","method":"GET","duration":1169,"requestID":"769C483871CB3D35DF4BB1CD7D3258C4","timestamp":1601647542782,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64)AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/css/site-ltr-b1aa14b0.css","method":"GET","duration":1179,"requestID":"56.2","timestamp":1601647543958,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/rz/p/yahoo_frontpage_en-US_s_f_p_bestfit_frontpage.png","method":"GET","duration":1189,"requestID":"56.3","timestamp":1601647543959,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/js/site-ee81be05.js","method":"GET","duration":1194,"requestID":"56.5","timestamp":1601647543961,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/rz/p/yahoo_frontpage_en-US_s_f_w_bestfit_frontpage.png","method":"GET","duration":1189,"requestID":"56.4","timestamp":1601647543961,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/images/fr-FR-home_11f60c18d02223c8.jpeg","method":"GET","duration":1068,"requestID":"56.7","timestamp":1601647545141,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://s.yimg.com/oa/build/css/site-ltr-b1aa14b0.css","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"http://yahoo.fr/","method":"GET","duration":1312,"requestID":"7CA8FE6239B07643872BF48C30D639D3","timestamp":1601647549363,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"http://fr.yahoo.com/","method":"GET","duration":1005,"requestID":"7CA8FE6239B07643872BF48C30D639D3","timestamp":1601647550675,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://fr.yahoo.com/","method":"GET","duration":1037,"requestID":"7CA8FE6239B07643872BF48C30D639D3","timestamp":1601647551680,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://guce.yahoo.com/consent?brandType=eu&gcrumb=ESjhlqw&lang=fr-FR&done=https%3A%2F%2Ffr.yahoo.com%2F","method":"GET","duration":1045,"requestID":"7CA8FE6239B07643872BF48C30D639D3","timestamp":1601647552717,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://consent.yahoo.com/v2/collectConsent?sessionId=3_cc-session_3b367c91-9f88-498b-96a5-728947dda245","method":"GET","duration":1115,"requestID":"7CA8FE6239B07643872BF48C30D639D3","timestamp":1601647553762,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/css/site-ltr-b1aa14b0.css","method":"GET","duration":1052,"requestID":"56.14","timestamp":1601647554885,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/rz/p/yahoo_frontpage_en-US_s_f_p_bestfit_frontpage.png","method":"GET","duration":1060,"requestID":"56.15","timestamp":1601647554886,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/js/site-ee81be05.js","method":"GET","duration":1065,"requestID":"56.17","timestamp":1601647554886,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/rz/p/yahoo_frontpage_en-US_s_f_w_bestfit_frontpage.png","method":"GET","duration":1063,"requestID":"56.16","timestamp":1601647554886,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/images/fr-FR-home_11f60c18d02223c8.jpeg","method":"GET","duration":1046,"requestID":"56.19","timestamp":1601647555944,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://s.yimg.com/oa/build/css/site-ltr-b1aa14b0.css","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null}],"environmentId":null,"tenantId":115,"consoleLogs":[{"_type":"warning","_text":"A cookie associated with a resource at http://openreplay.com/ was set with `SameSite=None` but without `Secure`. It has been blocked, as Chrome now only delivers cookies marked `SameSite=None` if they are also marked `Secure`. You can review cookies in developer tools under Application>Storage>Cookies and see more details at https://www.chromestatus.com/feature/5633521622188032.","_args":[],"_location":{"url":"https://app.openreplay.com/"},"timestamp":1602089840909},{"_type":"warning","_text":"A cookie associated with a resource at http://app.openreplay.com/ was set with `SameSite=None` but without `Secure`. It has been blocked, as Chrome now only delivers cookies marked `SameSite=None` if they are also marked `Secure`. You can review cookies in developer tools under Application>Storage>Cookies and see more details at https://www.chromestatus.com/feature/5633521622188032.","_args":[],"_location":{"url":"https://app.openreplay.com/"},"timestamp":1602089840918}]}); - -const ADD_STEPS = 'tests/ADD_STEPS'; -const MOVE_STEP = 'tests/MOVE_STEP'; -const REMOVE_STEP = 'tests/REMOVE_STEP'; -const COPY_STEP = 'tests/COPY_STEP'; -const EDIT_STEP = 'tests/EDIT_STEP'; -const TOGGLE_STEP = 'tests/TOGGLE_STEP'; -const ADD_TAG = 'tests/ADD_TAG'; -const REMOVE_TAG = 'tests/REMOVE_TAG'; -const TOGGLE_TAG = 'tests/TOGGLE_TAG'; -const SET_MODIFIED = 'tests/SET_MODIFIED'; -const SET_QUERY = 'tests/SET_QUERY'; - -const MOVE_TEST = 'tests/MOVE_TEST'; - -const initialState = Map({ - tags: Set(), - query: '', - modified: false, - sampleRun: sampleRun, -}); - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - case SET_MODIFIED: - return state.set('modified', action.state); - case SET_QUERY: - return state.set('query', action.query); - case ADD_STEPS: - // TODO check frameworks - return state - .updateIn([ 'instance', 'steps' ], list => list.concat(action.steps.map(stepFromJS))).set('modified', true); - case MOVE_STEP: { - const { fromI, toI } = action; - return state - .updateIn([ 'instance', 'steps' ], list => - list.remove(fromI).insert(toI, list.get(fromI))).set('modified', true); - } - case REMOVE_STEP: - return state.removeIn([ 'instance', 'steps', action.index ]).set('modified', true); - case COPY_STEP: { - // Use fromJS to make another key. - const copiedStep = stepFromJS(state - .getIn([ 'instance', 'steps', action.index ]) - .set('imported', false)); - return state - .updateIn([ 'instance', 'steps' ], steps => - steps.insert(action.index + 1, copiedStep)).set('modified', true); - } - case EDIT_STEP: - return state.mergeIn([ 'instance', 'steps', action.index ], action.step).set('modified', true); - case TOGGLE_STEP: - return state.updateIn([ 'instance', 'steps', action.index, 'isDisabled' ], isDisabled => !isDisabled).set('modified', true); - case ADD_TAG: - return state.updateIn([ 'instance', 'tags' ], tags => tags.add(action.tag)).set('modified', true); - case REMOVE_TAG: - return state.updateIn([ 'instance', 'tags' ], tags => tags.remove(action.tag)).set('modified', true); - case TOGGLE_TAG: { - const { tag, flag } = action; - const adding = typeof flag === 'boolean' - ? flag - : !state.hasIn([ 'tags', tag ]); - return state.update('tags', tags => (adding - ? tags.add(tag) - : tags.remove(tag))); - } - case MOVE_TEST: { - const { fromI, toI } = action; - return state - .updateIn([ 'list' ], list => - list.remove(fromI).insert(toI, list.get(fromI))); - } - } - return state; -}; - -const crudDuck = crudDuckGenerator('test', Test); -export const { fetchList, fetch, init, edit, save, remove } = crudDuck.actions; -export { runTest, stopRun, checkRun, generateTest, stopAllRuns, resetErrors } from './runs'; -export default reduceDucks(crudDuck, { reducer, initialState }, runsDuck).reducer; - -export function addSteps(stepOrSteps) { - const steps = Array.isArray(stepOrSteps) || List.isList(stepOrSteps) - ? stepOrSteps - : [ stepOrSteps ]; - return { - type: ADD_STEPS, - steps, - }; -} - -export function moveStep(fromI, toI) { - return { - type: MOVE_STEP, - fromI, - toI, - }; -} - -export function removeStep(index) { - return { - type: REMOVE_STEP, - index, - }; -} - -export function copyStep(index) { - return { - type: COPY_STEP, - index, - }; -} - -export function editStep(index, step) { - return { - type: EDIT_STEP, - index, - step, - }; -} - -export function setModified(state) { - return { - type: SET_MODIFIED, - state, - }; -} - -export function toggleStep(index) { - return { - type: TOGGLE_STEP, - index, - }; -} - -export const addTag = (tag) => (dispatch) => { - return new Promise((resolve) => { - dispatch({ - type: ADD_TAG, - tag, - }) - resolve() - }) -} - -export const removeTag = (tag) => (dispatch) => { - return new Promise((resolve) => { - dispatch({ - type: REMOVE_TAG, - tag, - }); - resolve() - }) -} - -export function toggleTag(tag, flag) { - return { - type: TOGGLE_TAG, - tag, - flag, - }; -} - -export function setQuery(query) { - return { - type: SET_QUERY, - query - }; -} - -export function moveTest(fromI, toI) { - return { - type: MOVE_TEST, - fromI, - toI, - }; -} diff --git a/frontend/app/duck/tests/runs.js b/frontend/app/duck/tests/runs.js deleted file mode 100644 index e5f1ecc3d..000000000 --- a/frontend/app/duck/tests/runs.js +++ /dev/null @@ -1,118 +0,0 @@ -import { Map } from 'immutable'; -import Test from 'Types/appTest'; -import Run, { RUNNING, STOPPED } from 'Types/run'; -import requestDuckGenerator, { RequestTypes } from 'Duck/tools/requestDuck'; -import { reduceDucks } from 'Duck/tools'; - -const GEN_TEST = new RequestTypes('tests/GEN_TEST'); -const RUN_TEST = new RequestTypes('tests/RUN_TEST'); -const STOP_RUN = new RequestTypes('tests/STOP_RUN'); -const STOP_ALL_RUNS = new RequestTypes('tests/STOP_ALL_RUNS'); -const CHECK_RUN = new RequestTypes('tests/CHECK_RUN'); -const RESET_ERRORS = 'tests/RESET_ERRORS'; - -const updateRunInTest = run => (test) => { - const runIndex = test.runHistory - .findLastIndex(({ runId }) => run.runId === runId); - return runIndex === -1 - ? test.update('runHistory', list => list.push(run)) - : test.mergeIn([ 'runHistory', runIndex ], run); -}; - -const updateRun = (state, testId, run) => { - const testIndex = state.get('list').findIndex(test => test.testId === testId); - if (testIndex === -1) return state; - const updater = updateRunInTest(run); - return state - .updateIn([ 'list', testIndex ], updater) - .updateIn([ 'instance' ], test => (test.testId === testId - ? updater(test) - : test)); -}; - -const initialState = Map({}); - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - case GEN_TEST.SUCCESS: - return state.set('instance', Test(action.data).set('generated', true)); - case RUN_TEST.SUCCESS: { - const test = state.get('list').find(({ testId }) => testId === action.testId); - const run = Run({ - runId: action.data.id, state: RUNNING, testId: action.testId, name: test.name - }); - return updateRun(state, action.testId, run); - } - case STOP_RUN.SUCCESS: { - const { testId, runId } = action; - return updateRun(state, testId, { runId, state: STOPPED }); - } - case STOP_ALL_RUNS.SUCCESS: - return state.update('list', list => list.map(test => { - test.runHistory.map(run => run.state === RUNNING ? run.set('state', STOPPED) : run.state); - return test; - })).setIn(['runRequest', 'errors'], null); - case CHECK_RUN.SUCCESS: - return updateRun(state, action.testId, Run(action.data)); - case RESET_ERRORS: - return state.setIn(['runRequest', 'errors'], null); - } - return state; -}; - -const requestDuck = requestDuckGenerator({ - runRequest: RUN_TEST, - stopRunRequest: STOP_RUN, - stopAllRunsRequest: STOP_ALL_RUNS, - genTestRequest: GEN_TEST, -}); - -export default reduceDucks({ reducer, initialState }, requestDuck); - - -export function generateTest(sessionId, params) { - return { - types: GEN_TEST.toArray(), - call: client => client.post(`/sessions/${ sessionId }/gentest`, params), - }; -} - - -export function runTest(testId, params) { - return { - testId, - types: RUN_TEST.toArray(), - call: client => client.post(`/tests/${ testId }/execute`, params), - }; -} - -export function stopRun(testId, runId) { - return { - runId, - testId, - types: STOP_RUN.toArray(), - call: client => client.get(`/runs/${ runId }/stop`), - }; -} - -export function stopAllRuns() { - return { - types: STOP_ALL_RUNS.toArray(), - call: client => client.get(`/runs/all/stop`), - }; -} - -export function resetErrors() { - return { - type: RESET_ERRORS, - } -} - -export function checkRun(testId, runId) { - return { - runId, - testId, - types: CHECK_RUN.toArray(), - call: client => client.get(`/runs/${ runId }`), - }; -} diff --git a/frontend/app/duck/user.js b/frontend/app/duck/user.js index 0f0331151..c2c149de5 100644 --- a/frontend/app/duck/user.js +++ b/frontend/app/duck/user.js @@ -1,26 +1,26 @@ -import { List, Map, Record } from 'immutable'; +import { List, Map } from 'immutable'; import Client from 'Types/client'; +import { deleteCookie } from 'App/utils'; import Account from 'Types/account'; -import { DELETE } from './jwt'; import withRequestState, { RequestTypes } from './requestStateCreator'; export const LOGIN = new RequestTypes('user/LOGIN'); export const SIGNUP = new RequestTypes('user/SIGNUP'); export const RESET_PASSWORD = new RequestTypes('user/RESET_PASSWORD'); export const REQUEST_RESET_PASSWORD = new RequestTypes('user/REQUEST_RESET_PASSWORD'); -const FETCH_ACCOUNT = new RequestTypes('user/FETCH_ACCOUNT'); +export const FETCH_ACCOUNT = new RequestTypes('user/FETCH_ACCOUNT'); const FETCH_TENANTS = new RequestTypes('user/FETCH_TENANTS'); const UPDATE_ACCOUNT = new RequestTypes('user/UPDATE_ACCOUNT'); const RESEND_EMAIL_VERIFICATION = new RequestTypes('user/RESEND_EMAIL_VERIFICATION'); const FETCH_CLIENT = new RequestTypes('user/FETCH_CLIENT'); export const UPDATE_PASSWORD = new RequestTypes('user/UPDATE_PASSWORD'); const PUT_CLIENT = new RequestTypes('user/PUT_CLIENT'); +const RESET_ERRORS = 'user/RESET_ERRORS'; const PUSH_NEW_SITE = 'user/PUSH_NEW_SITE'; const SET_ONBOARDING = 'user/SET_ONBOARDING'; -const initialState = Map({ - // client: Client(), +export const initialState = Map({ account: Account(), siteId: null, passwordRequestError: false, @@ -28,7 +28,13 @@ const initialState = Map({ tenants: [], authDetails: {}, onboarding: false, - sites: List() + sites: List(), + jwt: null, + errors: List(), + loginRequest: { + loading: false, + errors: [] + }, }); const setClient = (state, data) => { @@ -36,12 +42,30 @@ const setClient = (state, data) => { return state.set('client', client) } +export const UPDATE_JWT = 'jwt/UPDATE'; +export const DELETE = new RequestTypes('jwt/DELETE') +export function setJwt(data) { + return { + type: UPDATE_JWT, + data, + }; +} + + const reducer = (state = initialState, action = {}) => { switch (action.type) { + case RESET_ERRORS: + return state.set('requestResetPassowrd', List()); + case UPDATE_JWT: + return state.set('jwt', action.data); + case LOGIN.REQUEST: + return state.set('loginRequest', { loading: true, errors: [] }) case RESET_PASSWORD.SUCCESS: - case UPDATE_PASSWORD.SUCCESS: case LOGIN.SUCCESS: - state.set('account', Account({...action.data.user })) + return state.set('account', Account({...action.data.user })).set('loginRequest', { loading: false, errors: [] }) + case UPDATE_PASSWORD.REQUEST: + case UPDATE_PASSWORD.SUCCESS: + return state.set('passwordErrors', List()) case SIGNUP.SUCCESS: state.set('account', Account(action.data.user)).set('onboarding', true); case REQUEST_RESET_PASSWORD.SUCCESS: @@ -51,10 +75,16 @@ const reducer = (state = initialState, action = {}) => { return state.set('account', Account(action.data)).set('passwordErrors', List()); case FETCH_TENANTS.SUCCESS: return state.set('authDetails', action.data); - // return state.set('tenants', action.data.map(i => ({ text: i.name, value: i.tenantId}))); case UPDATE_PASSWORD.FAILURE: return state.set('passwordErrors', List(action.errors)) - case DELETE: + case LOGIN.FAILURE: + console.log('login failed', action); + deleteCookie('jwt', '/', 'openreplay.com') + return state.set('loginRequest', { loading: false, errors: action.errors }); + case FETCH_ACCOUNT.FAILURE: + case DELETE.SUCCESS: + case DELETE.FAILURE: + deleteCookie('jwt', '/', 'openreplay.com') return initialState; case PUT_CLIENT.REQUEST: return state.mergeIn([ 'account' ], action.params); @@ -69,9 +99,10 @@ const reducer = (state = initialState, action = {}) => { return state; }; + export default withRequestState({ - loginRequest: LOGIN, signupRequest: SIGNUP, + // loginRequest: LOGIN, updatePasswordRequest: UPDATE_PASSWORD, requestResetPassowrd: REQUEST_RESET_PASSWORD, resetPassword: RESET_PASSWORD, @@ -80,7 +111,7 @@ export default withRequestState({ updateAccountRequest: UPDATE_ACCOUNT, }, reducer); -export const login = params => dispatch => dispatch({ +export const login = params => ({ types: LOGIN.toArray(), call: client => client.post('/login', params), }); @@ -112,20 +143,15 @@ export function fetchTenants() { } } -export const fetchUserInfo = () => dispatch => Promise.all([ - dispatch({ +export const fetchUserInfo = () => ({ types: FETCH_ACCOUNT.toArray(), call: client => client.get('/account'), - }), - // dispatch({ - // types: FETCH_CLIENT.toArray(), - // call: client => client.get('/client'), - // }), -]); + }); export function logout() { return { - type: DELETE, + types: DELETE.toArray(), + call: client => client.get('/logout') }; } @@ -165,3 +191,8 @@ export function setOnboarding(state = false) { }; } +export function resetErrors() { + return { + type: RESET_ERRORS, + }; +} \ No newline at end of file diff --git a/frontend/app/duck/variables.js b/frontend/app/duck/variables.js deleted file mode 100644 index 21a0131c4..000000000 --- a/frontend/app/duck/variables.js +++ /dev/null @@ -1,9 +0,0 @@ -import Variable from 'Types/variable'; -import crudDuckGenerator from './tools/crudDuck'; - -const crudDuck = crudDuckGenerator('variable', Variable); -export const { - fetchList, fetch, init, edit, save, remove, -} = crudDuck.actions; - -export default crudDuck.reducer; diff --git a/frontend/app/duck/watchdogs.js b/frontend/app/duck/watchdogs.js deleted file mode 100644 index 87966264a..000000000 --- a/frontend/app/duck/watchdogs.js +++ /dev/null @@ -1,101 +0,0 @@ -import { List, Map } from 'immutable'; -import Watchdog from 'Types/watchdog'; -import { mergeReducers, success, array, request } from './funcTools/tools'; -import { createRequestReducer } from './funcTools/request'; -import { - createCRUDReducer, - getCRUDRequestTypes, - createFetchList, - createInit, - createEdit, - createRemove, - createSave, -} from './funcTools/crud'; - -const name = 'issue_type'; -const idKey = 'id'; -const SET_ACTIVE_TAB = 'watchdogs/SET_ACTIVE_TAB'; -const FETCH_WATCHDOG_STATUS = 'watchdogs/FETCH_WATCHDOG_STATUS'; -const FETCH_WATCHDOG_STATUS_SUCCESS = success(FETCH_WATCHDOG_STATUS); -const FETCH_RULES = 'watchdogs/FETCH_RULES'; -const FETCH_RULES_SUCCESS = success(FETCH_RULES); -const SAVE_CAPTURE_RATE = 'watchdogs/SAVE_CAPTURE_RATE'; -const EDIT_CAPTURE_RATE = 'watchdogs/SAVE_CAPTURE_RATE'; - -const initialState = Map({ - activeTab: Map(), - instance: Watchdog(), - list: List(), - rules: List(), - captureRate: Map() -}); - -const reducer = (state = initialState, action = {}) => { - switch (action.type) { - case SET_ACTIVE_TAB: - return state.set('activeTab', action.instance); - case FETCH_RULES_SUCCESS: - return state.set('rules', action.data); - case FETCH_WATCHDOG_STATUS_SUCCESS: - case success(SAVE_CAPTURE_RATE): - return state.set('captureRate', Map(action.data)); - case request(SAVE_CAPTURE_RATE): - return state.mergeIn(['captureRate'], action.params); - case EDIT_CAPTURE_RATE: - return state.mergeIn(['captureRate'], {rate: action.rate}); - } - return state; -}; - - -export const fetchList = createFetchList(name); -export const init = createInit(name); -export const edit = createEdit(name); -export const save = createSave(name); -export const remove = createRemove(name); - -export function setActiveTab(instance) { - return { - type: SET_ACTIVE_TAB, - instance, - }; -} - -export const fetchRules = () => { - return { - types: array(FETCH_RULES), - call: client => client.get(`/watchdogs/rules`), - }; -} - -export default mergeReducers( - reducer, - createCRUDReducer(name, Watchdog, idKey), - createRequestReducer({ - fetchWatchdogStatus: FETCH_WATCHDOG_STATUS, - savingCaptureRate: SAVE_CAPTURE_RATE, - ...getCRUDRequestTypes(name), - }), -); - -export const saveCaptureRate = (params) => { - return { - params, - types: array(SAVE_CAPTURE_RATE), - call: client => client.post(`/sample_rate`, params), - } -} - -export const editCaptureRate = rate => { - return { - type: EDIT_CAPTURE_RATE, - rate - } -} - -export const fetchWatchdogStatus = () => { - return { - types: array(FETCH_WATCHDOG_STATUS), - call: client => client.get('/sample_rate'), - }; -} diff --git a/frontend/app/duck/webhook.js b/frontend/app/duck/webhook.js deleted file mode 100644 index 8dc323a75..000000000 --- a/frontend/app/duck/webhook.js +++ /dev/null @@ -1,7 +0,0 @@ -import Webhook from 'Types/webhook'; -import crudDuckGenerator from './tools/crudDuck'; - -const crudDuck = crudDuckGenerator('webhook', Webhook, { idKey: 'webhookId' }); -export const { fetchList, init, edit, save, remove } = crudDuck.actions; - -export default crudDuck.reducer; diff --git a/frontend/app/hooks/useCancelableTimeout.ts b/frontend/app/hooks/useCancelableTimeout.ts new file mode 100644 index 000000000..1ab4d032a --- /dev/null +++ b/frontend/app/hooks/useCancelableTimeout.ts @@ -0,0 +1,20 @@ +import { useRef, useEffect } from 'react' + + +export default function useCancelableTimeout( + onTimeout: ()=> void, + onCancel: ()=> void, + delay: number, +): [()=> void, ()=> void] { + const idRef = useRef<ReturnType<typeof setTimeout>>() + const triggerTimeout = () => { + clearTimeout(idRef.current) + idRef.current = setTimeout(onTimeout, delay) + } + const cancelTimeout = () => { + clearTimeout(idRef.current) + onCancel() + } + useEffect(() => () => clearTimeout(idRef.current), []) // auto-cancel without callback (on clean) + return [ triggerTimeout, cancelTimeout ] +} \ No newline at end of file diff --git a/frontend/app/hooks/useCellMeasurerCache.ts b/frontend/app/hooks/useCellMeasurerCache.ts new file mode 100644 index 000000000..692f2629f --- /dev/null +++ b/frontend/app/hooks/useCellMeasurerCache.ts @@ -0,0 +1,12 @@ +import { useMemo } from 'react' +import { CellMeasurerCache, CellMeasurerCacheParams } from 'react-virtualized'; +import useLatestRef from 'App/hooks/useLatestRef' + +export default function useCellMeasurerCache(itemList?: any[], options?: CellMeasurerCacheParams) { + const filteredListRef = itemList ? useLatestRef(itemList) : undefined + return useMemo(() => new CellMeasurerCache({ + fixedWidth: true, + keyMapper: filteredListRef ? (index) => filteredListRef.current[index] : undefined, + ...options + }), []) +} \ No newline at end of file diff --git a/frontend/app/hooks/useLatestRef.ts b/frontend/app/hooks/useLatestRef.ts new file mode 100644 index 000000000..8dda027de --- /dev/null +++ b/frontend/app/hooks/useLatestRef.ts @@ -0,0 +1,8 @@ +import { useRef, useEffect } from 'react' + + +export default function useLatestRef<T>(state: T) { + const ref = useRef<T>(state) + useEffect(() => { ref.current = state }, [ state ]) + return ref +} \ No newline at end of file diff --git a/frontend/app/hooks/useToggle.ts b/frontend/app/hooks/useToggle.ts index d4e820388..426e19b41 100644 --- a/frontend/app/hooks/useToggle.ts +++ b/frontend/app/hooks/useToggle.ts @@ -1,9 +1,9 @@ -import { useState, useCallback } from 'react'; +import { useState } from 'react'; export default function useToggle(defaultValue: boolean = false): [ boolean, () => void, () => void, () => void ] { const [ value, setValue ] = useState(defaultValue); - const toggle = useCallback(() => setValue(d => !d), []); - const setFalse = useCallback(() => setValue(false), []); - const setTrue = useCallback(() => setValue(true), []); + const toggle = () => setValue(d => !d) + const setFalse = () => setValue(false) + const setTrue = () => setValue(true) return [ value, toggle, setFalse, setTrue ]; } \ No newline at end of file diff --git a/frontend/app/initialize.js b/frontend/app/initialize.js index d0eb9fb6e..d0bda4e2c 100644 --- a/frontend/app/initialize.js +++ b/frontend/app/initialize.js @@ -9,6 +9,9 @@ import { StoreProvider, RootStore } from './mstore'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { DndProvider } from 'react-dnd'; +// @ts-ignore +window.getCommitHash = () => console.log(window.env.COMMIT_HASH) + document.addEventListener('DOMContentLoaded', () => { const container = document.getElementById('app'); const root = createRoot(container); @@ -16,9 +19,7 @@ document.addEventListener('DOMContentLoaded', () => { <Provider store={store}> <StoreProvider store={new RootStore()}> <DndProvider backend={HTML5Backend}> - {/* <React.StrictMode> */} - <Router /> - {/* </React.StrictMode> */} + <Router /> </DndProvider> </StoreProvider> </Provider> diff --git a/frontend/app/logger/index.js b/frontend/app/logger/index.js index 68611c540..353f186e9 100644 --- a/frontend/app/logger/index.js +++ b/frontend/app/logger/index.js @@ -13,11 +13,13 @@ function warn(...args) { if (!window.env.PRODUCTION || options.verbose) { console.warn(...args); } + options.exceptionsLogs.push(args) } function error(...args) { if (!window.env.PRODUCTION || options.verbose) { console.error(...args); + options.exceptionsLogs.push(args) } } @@ -33,6 +35,8 @@ function group(groupName, ...args) { console.groupCollapsed(groupName); } console.log(...args); + + options.exceptionsLogs.push(args) } } diff --git a/frontend/app/mstore/alertsStore.ts b/frontend/app/mstore/alertsStore.ts new file mode 100644 index 000000000..33665f861 --- /dev/null +++ b/frontend/app/mstore/alertsStore.ts @@ -0,0 +1,92 @@ +import { makeAutoObservable, action } from 'mobx'; +import Alert, { IAlert } from 'Types/alert'; +import { alertsService } from 'App/services'; + +export default class AlertsStore { + alerts: Alert[] = []; + triggerOptions: { label: string; value: string | number; unit?: string }[] = []; + alertsSearch = ''; + // @ts-ignore + instance: Alert = new Alert({}, false); + loading = false; + page: number = 1; + + constructor() { + makeAutoObservable(this); + } + + changeSearch = (value: string) => { + this.alertsSearch = value; + this.page = 1; + }; + + // TODO: remove it + updateKey(key: string, value: any) { + // @ts-ignore + this[key] = value; + } + + fetchList = async () => { + this.loading = true; + try { + const list = await alertsService.fetchList(); + this.alerts = list.map((alert) => new Alert(alert, true)); + } catch (e) { + console.error(e); + } finally { + this.loading = false; + } + }; + + save = async (inst: Alert) => { + this.loading = true; + try { + await alertsService.save(inst ? inst : this.instance); + this.instance.isExists = true; + } catch (e) { + console.error(e); + } finally { + this.loading = false; + } + }; + + remove = async (id: string) => { + this.loading = true; + try { + await alertsService.remove(id); + } catch (e) { + console.error(e); + } finally { + this.loading = false; + } + }; + + fetchTriggerOptions = async () => { + this.loading = true; + try { + const options = await alertsService.fetchTriggerOptions(); + this.triggerOptions = options.map(({ name, value }) => ({ label: name, value })); + } catch (e) { + console.error(e); + } finally { + this.loading = false; + } + }; + + init = (inst: Partial<IAlert> | Alert) => { + this.instance = inst instanceof Alert ? inst : new Alert(inst, false); + }; + + edit = (diff: Partial<Alert>) => { + const key = Object.keys(diff)[0]; + const oldInst = this.instance; + // @ts-ignore + oldInst[key] = diff[key]; + + this.instance = oldInst; + }; + + changeUnit = ({ value }: { value: string }) => { + this.instance.change = value; + }; +} diff --git a/frontend/app/mstore/assistMultiviewStore.ts b/frontend/app/mstore/assistMultiviewStore.ts new file mode 100644 index 000000000..e05a5e433 --- /dev/null +++ b/frontend/app/mstore/assistMultiviewStore.ts @@ -0,0 +1,120 @@ +import { makeAutoObservable } from 'mobx'; +import { sessionService } from 'App/services'; +import Filter from 'Types/filter'; +import Session from 'Types/session'; +import { List } from 'immutable'; + +type MultiSessions = [ + LiveSessionListItem?, + LiveSessionListItem?, + LiveSessionListItem?, + LiveSessionListItem? +]; +export interface LiveSessionListItem extends Record<string, any> { + key: number | string; +} + +export default class AssistMultiviewStore { + sessions: MultiSessions = []; + activeSession: LiveSessionListItem = null; + onChangeCb: (sessions: MultiSessions) => void; + + constructor() { + makeAutoObservable(this); + } + + isActive(sessionId: string): boolean { + return this.activeSessionId === sessionId; + } + + setOnChange(cb: (sessions: MultiSessions) => void) { + this.onChangeCb = cb; + } + + get sortedSessions() { + // @ts-ignore ??? + return this.sessions.slice().sort((a, b) => a.key - b.key); + } + + get activeSessionId() { + return this.activeSession?.sessionId || ''; + } + + addSession(session: Record<string, any>) { + if ( + this.sessions.length < 4 && + this.sessions.findIndex((s) => s.sessionId === session.sessionId) === -1 + ) { + const plainSession = session.toJS ? session.toJS() : session; + this.sessions.push({ ...plainSession, key: this.sessions.length }); + return this.onChangeCb(this.sessions); + } + } + + replaceSession(targetId: string, session: Record<string, any>) { + const targetIndex = this.sessions.findIndex((s) => s.sessionId === targetId); + if (targetIndex !== -1) { + const plainSession = session.toJS ? session.toJS() : session; + this.sessions[targetIndex] = { ...plainSession, key: targetIndex }; + return this.onChangeCb(this.sessions); + } + } + + removeSession(sessionId: string) { + this.sessions = this.sessions.filter( + (session) => session.sessionId !== sessionId + ) as MultiSessions; + return this.onChangeCb(this.sessions); + } + + setActiveSession(sessionId: string) { + this.activeSession = this.sessions.find((session) => session.sessionId === sessionId); + } + + setDefault(session: Record<string, any>) { + if (this.sessions.length === 0) { + const plainSession = session.toJS ? session.toJS() : session; + const firstItem = { ...plainSession, key: 0 }; + this.sessions = [firstItem]; + this.activeSession = firstItem; + + return this.onChangeCb?.(this.sessions); + } + } + + async fetchAgentTokenInfo(sessionId: string) { + const info = await sessionService.getSessionInfo(sessionId, true); + return this.setToken(sessionId, info.agentToken); + } + + async presetSessions(ids: string[]) { + // @ts-ignore + const filter = new Filter({ filters: [], sort: '' }).toData(); + const data = await sessionService.getLiveSessions(filter); + + const matchingSessions = data.sessions.filter( + (s: Record<string, any>) => ids.includes(s.sessionID) || ids.includes(s.sessionId) + ); + const immutMatchingSessions = List(matchingSessions).map(s => new Session(s)); + immutMatchingSessions.forEach((session: Record<string, any>) => { + this.addSession(session); + this.fetchAgentTokenInfo(session.sessionId); + }); + + return data; + } + + setToken(sessionId: string, token: string) { + const sessions = this.sessions; + const targetIndex = sessions.findIndex((s) => s.sessionId === sessionId); + sessions[targetIndex].agentToken = token; + + return (this.sessions = sessions); + } + + reset() { + this.sessions = []; + this.activeSession = null; + this.onChangeCb = undefined + } +} diff --git a/frontend/app/mstore/auditStore.ts b/frontend/app/mstore/auditStore.ts index efc34d65d..2603c1ac4 100644 --- a/frontend/app/mstore/auditStore.ts +++ b/frontend/app/mstore/auditStore.ts @@ -1,11 +1,10 @@ -import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx" +import { makeAutoObservable, runInAction, observable, action } from "mobx" import { auditService } from "App/services" import Audit from './types/audit' import Period, { LAST_7_DAYS } from 'Types/app/period'; import { toast } from 'react-toastify'; import { exportCSVFile } from 'App/utils'; -import { formatDateTimeDefault } from 'App/date'; -import { DateTime, Duration } from 'luxon'; // TODO +import { DateTime } from 'luxon'; // TODO export default class AuditStore { list: any[] = []; diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 9b2a98220..ad9aaa8f7 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -1,510 +1,443 @@ -import { - makeAutoObservable, - runInAction, - computed, -} from "mobx"; -import Dashboard from "./types/dashboard"; -import Widget from "./types/widget"; -import { dashboardService, metricService } from "App/services"; -import { toast } from "react-toastify"; -import Period, { - LAST_24_HOURS, - LAST_7_DAYS, -} from "Types/app/period"; -import { getChartFormatter } from "Types/dashboard/helper"; -import Filter from "./types/filter"; -import Funnel from "./types/funnel"; -import Session from "./types/session"; -import Error from "./types/error"; -import { FilterKey } from "Types/filter/filterType"; +import { makeAutoObservable, runInAction } from 'mobx'; +import Dashboard from './types/dashboard'; +import Widget from './types/widget'; +import { dashboardService, metricService } from 'App/services'; +import { toast } from 'react-toastify'; +import Period, { LAST_24_HOURS, LAST_7_DAYS } from 'Types/app/period'; +import Filter from './types/filter'; +import { getRE } from 'App/utils'; -export default class DashboardStore { - siteId: any = null; - dashboards: Dashboard[] = []; - selectedDashboard: Dashboard | null = null; - dashboardInstance: Dashboard = new Dashboard(); - selectedWidgets: Widget[] = []; - currentWidget: Widget = new Widget(); - widgetCategories: any[] = []; - widgets: Widget[] = []; - period: Record<string, any> = Period({ rangeName: LAST_24_HOURS }); - drillDownFilter: Filter = new Filter(); - drillDownPeriod: Record<string, any> = Period({ rangeName: LAST_7_DAYS }); - startTimestamp: number = 0; - endTimestamp: number = 0; - pendingRequests: number = 0; - - // Metrics - metricsPage: number = 1; - metricsPageSize: number = 10; - metricsSearch: string = ""; - - // Loading states - isLoading: boolean = true; - isSaving: boolean = false; - isDeleting: boolean = false; - loadingTemplates: boolean = false - fetchingDashboard: boolean = false; - sessionsLoading: boolean = false; - showAlertModal: boolean = false; - - // Pagination - page: number = 1 - pageSize: number = 10 - dashboardsSearch: string = '' - sort: any = {} - - constructor() { - makeAutoObservable(this); - - this.drillDownPeriod = Period({ rangeName: LAST_7_DAYS }); - const timeStamps = this.drillDownPeriod.toTimestamps(); - this.drillDownFilter.updateKey( - "startTimestamp", - timeStamps.startTimestamp - ); - this.drillDownFilter.updateKey("endTimestamp", timeStamps.endTimestamp); - } - - @computed - get sortedDashboards() { - return [...this.dashboards].sort((a, b) => b.createdAt - a.createdAt) - } - - toggleAllSelectedWidgets(isSelected: boolean) { - if (isSelected) { - const allWidgets = this.widgetCategories.reduce((acc, cat) => { - return acc.concat(cat.widgets); - }, []); - - this.selectedWidgets = allWidgets; - } else { - this.selectedWidgets = []; - } - } - - selectWidgetsByCategory(category: string) { - const selectedWidgetIds = this.selectedWidgets.map( - (widget: any) => widget.metricId - ); - const widgets = this.widgetCategories - .find((cat) => cat.name === category) - ?.widgets.filter( - (widget: any) => !selectedWidgetIds.includes(widget.metricId) - ); - this.selectedWidgets = this.selectedWidgets.concat(widgets) || []; - } - - removeSelectedWidgetByCategory = (category: any) => { - const categoryWidgetIds = category.widgets.map((w: Widget) => w.metricId); - this.selectedWidgets = this.selectedWidgets.filter( - (widget: any) => !categoryWidgetIds.includes(widget.metricId) - ); - }; - - toggleWidgetSelection = (widget: any) => { - const selectedWidgetIds = this.selectedWidgets.map( - (widget: any) => widget.metricId - ); - if (selectedWidgetIds.includes(widget.metricId)) { - this.selectedWidgets = this.selectedWidgets.filter( - (w: any) => w.metricId !== widget.metricId - ); - } else { - this.selectedWidgets.push(widget); - } - }; - - findByIds(ids: string[]) { - return this.dashboards.filter((d) => ids.includes(d.dashboardId)); - } - - initDashboard(dashboard: Dashboard) { - this.dashboardInstance = dashboard - ? new Dashboard().fromJson(dashboard) - : new Dashboard(); - this.selectedWidgets = []; - } - - updateKey(key: string, value: any) { - // @ts-ignore - this[key] = value; - } - - resetCurrentWidget() { - this.currentWidget = new Widget(); - } - - editWidget(widget: any) { - this.currentWidget.update(widget); - } - - fetchList(): Promise<any> { - this.isLoading = true; - - return dashboardService - .getDashboards() - .then((list: any) => { - runInAction(() => { - this.dashboards = list.map((d: Record<string, any>) => - new Dashboard().fromJson(d) - ); - }); - }) - .finally(() => { - runInAction(() => { - this.isLoading = false; - }); - }); - } - - fetch(dashboardId: string): Promise<any> { - this.setFetchingDashboard(true); - return dashboardService - .getDashboard(dashboardId) - .then((response) => { - this.selectedDashboard?.update({ - widgets: new Dashboard().fromJson(response).widgets, - }); - }) - .finally(() => { - this.setFetchingDashboard(false); - }); - } - - setFetchingDashboard(value: boolean) { - this.fetchingDashboard = value; - } - - save(dashboard: Dashboard): Promise<any> { - this.isSaving = true; - const isCreating = !dashboard.dashboardId; - - dashboard.metrics = this.selectedWidgets.map((w) => w.metricId); - - return new Promise((resolve, reject) => { - dashboardService - .saveDashboard(dashboard) - .then((_dashboard) => { - runInAction(() => { - if (isCreating) { - toast.success("Dashboard created successfully"); - this.addDashboard( - new Dashboard().fromJson(_dashboard) - ); - } else { - toast.success("Dashboard successfully updated "); - this.updateDashboard( - new Dashboard().fromJson(_dashboard) - ); - } - resolve(_dashboard); - }); - }) - .catch((error) => { - toast.error("Error saving dashboard"); - reject(); - }) - .finally(() => { - runInAction(() => { - this.isSaving = false; - }); - }); - }); - } - - saveMetric(metric: Widget, dashboardId: string): Promise<any> { - const isCreating = !metric.widgetId; - return dashboardService - .saveMetric(metric, dashboardId) - .then((metric) => { - runInAction(() => { - if (isCreating) { - this.selectedDashboard?.widgets.push(metric); - } else { - this.selectedDashboard?.widgets.map((w) => { - if (w.widgetId === metric.widgetId) { - w.update(metric); - } - }); - } - }); - }); - } - - deleteDashboard(dashboard: Dashboard): Promise<any> { - this.isDeleting = true; - return dashboardService - .deleteDashboard(dashboard.dashboardId) - .then(() => { - toast.success("Dashboard deleted successfully"); - runInAction(() => { - this.removeDashboard(dashboard); - }); - }) - .catch(() => { - toast.error("Dashboard could not be deleted"); - }) - .finally(() => { - runInAction(() => { - this.isDeleting = false; - }); - }); - } - - toJson() { - return { - dashboards: this.dashboards.map((d) => d.toJson()), - }; - } - - fromJson(json: any) { - runInAction(() => { - this.dashboards = json.dashboards.map((d: Record<string, any>) => - new Dashboard().fromJson(d) - ); - }); - return this; - } - - addDashboard(dashboard: Dashboard) { - this.dashboards.push(new Dashboard().fromJson(dashboard)); - } - - removeDashboard(dashboard: Dashboard) { - this.dashboards = this.dashboards.filter( - (d) => d.dashboardId !== dashboard.dashboardId - ); - } - - getDashboard(dashboardId: string): Dashboard | null { - return ( - this.dashboards.find((d) => d.dashboardId === dashboardId) || null - ); - } - - getDashboardByIndex(index: number) { - return this.dashboards[index]; - } - - getDashboardCount() { - return this.dashboards.length; - } - - updateDashboard(dashboard: Dashboard) { - const index = this.dashboards.findIndex( - (d) => d.dashboardId === dashboard.dashboardId - ); - if (index >= 0) { - this.dashboards[index] = dashboard; - if (this.selectedDashboard?.dashboardId === dashboard.dashboardId) { - this.selectDashboardById(dashboard.dashboardId); - } - } - } - - selectDashboardById = (dashboardId: any) => { - this.selectedDashboard = - this.dashboards.find((d) => d.dashboardId == dashboardId) || - new Dashboard(); - }; - - getDashboardById = (dashboardId: string) => { - const dashboard = this.dashboards.find((d) => d.dashboardId == dashboardId) - - if (dashboard) { - this.selectedDashboard = dashboard - return true; - } else { - this.selectedDashboard = null - return false; - } - } - - setSiteId = (siteId: any) => { - this.siteId = siteId; - }; - - fetchTemplates(hardRefresh: boolean): Promise<any> { - this.loadingTemplates = true - return new Promise((resolve, reject) => { - if (this.widgetCategories.length > 0 && !hardRefresh) { - resolve(this.widgetCategories); - } else { - metricService - .getTemplates() - .then((response) => { - const categories: any[] = []; - response.forEach((category: any) => { - const widgets: any[] = []; - category.widgets - .forEach((widget: any) => { - const w = new Widget().fromJson(widget); - widgets.push(w); - }); - const c: any = {}; - c.widgets = widgets; - c.name = category.category; - c.description = category.description; - categories.push(c); - }); - this.widgetCategories = categories; - resolve(this.widgetCategories); - }) - .catch((error) => { - reject(error); - }).finally(() => { - this.loadingTemplates = false - }); - } - }); - } - - deleteDashboardWidget(dashboardId: string, widgetId: string) { - this.isDeleting = true; - return dashboardService - .deleteWidget(dashboardId, widgetId) - .then(() => { - toast.success("Dashboard updated successfully"); - runInAction(() => { - this.selectedDashboard?.removeWidget(widgetId); - }); - }) - .finally(() => { - this.isDeleting = false; - }); - } - - addWidgetToDashboard(dashboard: Dashboard, metricIds: any): Promise<any> { - this.isSaving = true; - return dashboardService - .addWidget(dashboard, metricIds) - .then((response) => { - toast.success("Metric added to dashboard."); - }) - .catch(() => { - toast.error("Metric could not be added."); - }) - .finally(() => { - this.isSaving = false; - }); - } - - setPeriod(period: any) { - this.period = Period({ - start: period.start, - end: period.end, - rangeName: period.rangeName, - }); - } - - setDrillDownPeriod(period: any) { - this.drillDownPeriod = Period({ - start: period.start, - end: period.end, - rangeName: period.rangeName, - }); - } - - fetchMetricChartData( - metric: Widget, - data: any, - isWidget: boolean = false, - period: Record<string, any> - ): Promise<any> { - period = period.toTimestamps(); - const params = { ...period, ...data, key: metric.predefinedKey }; - - - if (metric.page && metric.limit) { - params["page"] = metric.page; - params["limit"] = metric.limit; - } - - return new Promise((resolve, reject) => { - this.pendingRequests += 1 - return metricService - .getMetricChartData(metric, params, isWidget) - .then((data: any) => { - if ( - metric.metricType === "predefined" && - metric.viewType === "overview" - ) { - const _data = { - ...data, - chart: getChartFormatter(period)(data.chart), - }; - metric.setData(_data); - resolve(_data); - } else if (metric.metricType === "funnel") { - const _data = { ...data }; - _data.funnel = new Funnel().fromJSON(data); - metric.setData(_data); - resolve(_data); - } else { - const _data = { - ...data, - }; - - // TODO refactor to widget class - if (metric.metricOf === FilterKey.SESSIONS) { - _data["sessions"] = data.sessions.map((s: any) => - new Session().fromJson(s) - ); - } else if (metric.metricOf === FilterKey.ERRORS) { - _data["errors"] = data.errors.map((s: any) => - new Error().fromJSON(s) - ); - } else { - if (data.hasOwnProperty("chart")) { - _data["chart"] = getChartFormatter(period)( - data.chart - ); - _data["namesMap"] = data.chart - .map((i: any) => Object.keys(i)) - .flat() - .filter( - (i: any) => i !== "time" && i !== "timestamp" - ) - .reduce((unique: any, item: any) => { - if (!unique.includes(item)) { - unique.push(item); - } - return unique; - }, []); - } else { - _data["chart"] = getChartFormatter(period)( - Array.isArray(data) ? data : [] - ); - _data["namesMap"] = Array.isArray(data) - ? data - .map((i) => Object.keys(i)) - .flat() - .filter( - (i) => - i !== "time" && - i !== "timestamp" - ) - .reduce((unique: any, item: any) => { - if (!unique.includes(item)) { - unique.push(item); - } - return unique; - }, []) - : []; - } - } - metric.setData(_data); - resolve(_data); - } - }) - .catch((err: any) => { - reject(err); - }).finally(() => { - setTimeout(() => { - this.pendingRequests = this.pendingRequests - 1 - }, 100) - }); - }); - } +interface DashboardFilter { + query?: string; + showMine?: boolean; +} +export default class DashboardStore { + siteId: any = null; + dashboards: Dashboard[] = []; + selectedDashboard: Dashboard | null = null; + dashboardInstance: Dashboard = new Dashboard(); + selectedWidgets: Widget[] = []; + currentWidget: Widget = new Widget(); + widgetCategories: any[] = []; + widgets: Widget[] = []; + period: Record<string, any> = Period({ rangeName: LAST_24_HOURS }); + drillDownFilter: Filter = new Filter(); + drillDownPeriod: Record<string, any> = Period({ rangeName: LAST_7_DAYS }); + startTimestamp: number = 0; + endTimestamp: number = 0; + pendingRequests: number = 0; + + filter: DashboardFilter = { showMine: false, query: '' }; + + // Metrics + metricsPage: number = 1; + metricsPageSize: number = 10; + metricsSearch: string = ''; + + // Loading states + isLoading: boolean = true; + isSaving: boolean = false; + isDeleting: boolean = false; + loadingTemplates: boolean = false; + fetchingDashboard: boolean = false; + sessionsLoading: boolean = false; + showAlertModal: boolean = false; + + // Pagination + page: number = 1; + pageSize: number = 10; + dashboardsSearch: string = ''; + sort: any = { by: 'desc' }; + + constructor() { + makeAutoObservable(this); + + this.resetDrillDownFilter(); + } + + get sortedDashboards() { + const sortOrder = this.sort.by; + return [...this.dashboards].sort((a, b) => + sortOrder === 'desc' ? b.createdAt - a.createdAt : a.createdAt - b.createdAt + ); + } + + resetDrillDownFilter() { + this.drillDownFilter = new Filter(); + this.drillDownPeriod = Period({ rangeName: LAST_7_DAYS }); + const timeStamps = this.drillDownPeriod.toTimestamps(); + this.drillDownFilter.updateKey('startTimestamp', timeStamps.startTimestamp); + this.drillDownFilter.updateKey('endTimestamp', timeStamps.endTimestamp); + } + + get filteredList() { + const filterRE = this.filter.query ? getRE(this.filter.query, 'i') : null; + return this.dashboards + .filter( + (dashboard) => + (this.filter.showMine ? !dashboard.isPublic : true) && + (!filterRE || + // @ts-ignore + ['name', 'owner', 'description'].some((key) => filterRE.test(dashboard[key]))) + ) + .sort((a, b) => + this.sort.by === 'desc' ? b.createdAt - a.createdAt : a.createdAt - b.createdAt + ); + } + + toggleAllSelectedWidgets(isSelected: boolean) { + if (isSelected) { + const allWidgets = this.widgetCategories.reduce((acc, cat) => { + return acc.concat(cat.widgets); + }, []); + + this.selectedWidgets = allWidgets; + } else { + this.selectedWidgets = []; + } + } + + selectWidgetsByCategory(category: string) { + const selectedWidgetIds = this.selectedWidgets.map((widget: any) => widget.metricId); + const widgets = this.widgetCategories + .find((cat) => cat.name === category) + ?.widgets.filter((widget: any) => !selectedWidgetIds.includes(widget.metricId)); + this.selectedWidgets = this.selectedWidgets.concat(widgets) || []; + } + + removeSelectedWidgetByCategory = (category: any) => { + const categoryWidgetIds = category.widgets.map((w: Widget) => w.metricId); + this.selectedWidgets = this.selectedWidgets.filter( + (widget: any) => !categoryWidgetIds.includes(widget.metricId) + ); + }; + + toggleWidgetSelection = (widget: any) => { + const selectedWidgetIds = this.selectedWidgets.map((widget: any) => widget.metricId); + if (selectedWidgetIds.includes(widget.metricId)) { + this.selectedWidgets = this.selectedWidgets.filter( + (w: any) => w.metricId !== widget.metricId + ); + } else { + this.selectedWidgets.push(widget); + } + }; + + findByIds(ids: string[]) { + return this.dashboards.filter((d) => ids.includes(d.dashboardId)); + } + + initDashboard(dashboard?: Dashboard) { + this.dashboardInstance = dashboard ? new Dashboard().fromJson(dashboard) : new Dashboard(); + this.selectedWidgets = []; + } + + updateKey(key: string, value: any) { + // @ts-ignore + this[key] = value; + } + + resetCurrentWidget() { + this.currentWidget = new Widget(); + } + + editWidget(widget: any) { + this.currentWidget.update(widget); + } + + fetchList(): Promise<any> { + this.isLoading = true; + + return dashboardService + .getDashboards() + .then((list: any) => { + runInAction(() => { + this.dashboards = list.map((d: Record<string, any>) => new Dashboard().fromJson(d)); + }); + }) + .finally(() => { + runInAction(() => { + this.isLoading = false; + }); + }); + } + + fetch(dashboardId: string): Promise<any> { + this.setFetchingDashboard(true); + return dashboardService + .getDashboard(dashboardId) + .then((response) => { + this.selectedDashboard?.update({ + widgets: new Dashboard().fromJson(response).widgets, + }); + }) + .finally(() => { + this.setFetchingDashboard(false); + }); + } + + setFetchingDashboard(value: boolean) { + this.fetchingDashboard = value; + } + + save(dashboard: Dashboard): Promise<any> { + this.isSaving = true; + const isCreating = !dashboard.dashboardId; + + dashboard.metrics = this.selectedWidgets.map((w) => w.metricId); + + return new Promise((resolve, reject) => { + dashboardService + .saveDashboard(dashboard) + .then((_dashboard: any) => { + runInAction(() => { + if (isCreating) { + toast.success('Dashboard created successfully'); + this.addDashboard(new Dashboard().fromJson(_dashboard)); + } else { + toast.success('Dashboard successfully updated '); + this.syncDashboardInfo(_dashboard.dashboardId!, _dashboard); + } + resolve(_dashboard); + }); + }) + .catch(() => { + toast.error('Error saving dashboard'); + reject(); + }) + .finally(() => { + runInAction(() => { + this.isSaving = false; + }); + }); + }); + } + + syncDashboardInfo(id: string, info: { name: string, description: string, isPublic: boolean, createdAt: number }) { + if (this.selectedDashboard !== null) { + this.selectedDashboard.updateInfo(info) + const index = this.dashboards.findIndex((d) => d.dashboardId === id); + this.dashboards[index].updateInfo(info); + } + } + + saveMetric(metric: Widget, dashboardId: string): Promise<any> { + const isCreating = !metric.widgetId; + return dashboardService.saveMetric(metric, dashboardId).then((metric) => { + runInAction(() => { + if (isCreating) { + this.selectedDashboard?.widgets.push(metric); + } else { + this.selectedDashboard?.widgets.map((w) => { + if (w.widgetId === metric.widgetId) { + w.update(metric); + } + }); + } + }); + }); + } + + deleteDashboard(dashboard: Dashboard): Promise<any> { + this.isDeleting = true; + return dashboardService + .deleteDashboard(dashboard.dashboardId) + .then(() => { + toast.success('Dashboard deleted successfully'); + runInAction(() => { + this.removeDashboard(dashboard); + }); + }) + .catch(() => { + toast.error('Dashboard could not be deleted'); + }) + .finally(() => { + runInAction(() => { + this.isDeleting = false; + }); + }); + } + + toJson() { + return { + dashboards: this.dashboards.map((d) => d.toJson()), + }; + } + + fromJson(json: any) { + runInAction(() => { + this.dashboards = json.dashboards.map((d: Record<string, any>) => + new Dashboard().fromJson(d) + ); + }); + return this; + } + + addDashboard(dashboard: Dashboard) { + this.dashboards.push(new Dashboard().fromJson(dashboard)); + } + + removeDashboard(dashboard: Dashboard) { + this.dashboards = this.dashboards.filter((d) => d.dashboardId !== dashboard.dashboardId); + } + + getDashboard(dashboardId: string | number): Dashboard | null { + return this.dashboards.find((d) => d.dashboardId == dashboardId) || null; + } + + getDashboardByIndex(index: number) { + return this.dashboards[index]; + } + + getDashboardCount() { + return this.dashboards.length; + } + + updateDashboard(dashboard: Dashboard) { + const index = this.dashboards.findIndex((d) => d.dashboardId === dashboard.dashboardId); + if (index >= 0) { + this.dashboards[index] = dashboard; + if (this.selectedDashboard?.dashboardId === dashboard.dashboardId) { + this.selectDashboardById(dashboard.dashboardId); + } + } + } + + selectDashboardById = (dashboardId: any) => { + this.selectedDashboard = + this.dashboards.find((d) => d.dashboardId == dashboardId) || new Dashboard(); + }; + + getDashboardById = (dashboardId: string) => { + const dashboard = this.dashboards.find((d) => d.dashboardId == dashboardId); + + if (dashboard) { + this.selectedDashboard = dashboard; + return true; + } else { + this.selectedDashboard = null; + return false; + } + }; + + setSiteId = (siteId: any) => { + this.siteId = siteId; + }; + + fetchTemplates(hardRefresh: boolean): Promise<any> { + this.loadingTemplates = true; + return new Promise((resolve, reject) => { + if (this.widgetCategories.length > 0 && !hardRefresh) { + resolve(this.widgetCategories); + } else { + metricService + .getTemplates() + .then((response) => { + const categories: any[] = []; + response.forEach((category: any) => { + const widgets: any[] = []; + category.widgets.forEach((widget: any) => { + const w = new Widget().fromJson(widget); + widgets.push(w); + }); + const c: any = {}; + c.widgets = widgets; + c.name = category.category; + c.description = category.description; + categories.push(c); + }); + this.widgetCategories = categories; + resolve(this.widgetCategories); + }) + .catch((error) => { + reject(error); + }) + .finally(() => { + this.loadingTemplates = false; + }); + } + }); + } + + deleteDashboardWidget(dashboardId: string, widgetId: string) { + this.isDeleting = true; + return dashboardService + .deleteWidget(dashboardId, widgetId) + .then(() => { + toast.success('Dashboard updated successfully'); + runInAction(() => { + this.selectedDashboard?.removeWidget(widgetId); + }); + }) + .finally(() => { + this.isDeleting = false; + }); + } + + addWidgetToDashboard(dashboard: Dashboard, metricIds: any): Promise<any> { + this.isSaving = true; + return dashboardService + .addWidget(dashboard, metricIds) + .then((response) => { + toast.success('Card added to dashboard.'); + }) + .catch(() => { + toast.error('Card could not be added.'); + }) + .finally(() => { + this.isSaving = false; + }); + } + + setPeriod(period: any) { + this.period = Period({ + start: period.start, + end: period.end, + rangeName: period.rangeName, + }); + } + + setDrillDownPeriod(period: any) { + this.drillDownPeriod = Period({ + start: period.start, + end: period.end, + rangeName: period.rangeName, + }); + } + + toggleAlertModal(val: boolean) { + this.showAlertModal = val; + } + + fetchMetricChartData( + metric: Widget, + data: any, + isWidget: boolean = false, + period: Record<string, any> + ): Promise<any> { + period = period.toTimestamps(); + const params = { ...period, ...data, key: metric.predefinedKey }; + + if (metric.page && metric.limit) { + params['page'] = metric.page; + params['limit'] = metric.limit; + } + + return new Promise((resolve, reject) => { + this.pendingRequests += 1; + return metricService + .getMetricChartData(metric, params, isWidget) + .then((data: any) => { + resolve(metric.setData(data, period)); + }) + .catch((err: any) => { + reject(err); + }) + .finally(() => { + setTimeout(() => { + this.pendingRequests = this.pendingRequests - 1; + }, 100); + }); + }); + } } diff --git a/frontend/app/mstore/errorStore.ts b/frontend/app/mstore/errorStore.ts index 32b095ade..b7aaed549 100644 --- a/frontend/app/mstore/errorStore.ts +++ b/frontend/app/mstore/errorStore.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx" +import { makeAutoObservable } from "mobx" import { errorService } from "App/services" import Error from "./types/error" diff --git a/frontend/app/mstore/funnelStore.ts b/frontend/app/mstore/funnelStore.ts index f0a9d5384..818c51e59 100644 --- a/frontend/app/mstore/funnelStore.ts +++ b/frontend/app/mstore/funnelStore.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx" +import { makeAutoObservable, action } from "mobx" import { funnelService } from "App/services" import Funnel, { IFunnel } from "./types/funnel"; import Session from './types/session'; diff --git a/frontend/app/mstore/index.tsx b/frontend/app/mstore/index.tsx index 1d6d758a1..e177d86d0 100644 --- a/frontend/app/mstore/index.tsx +++ b/frontend/app/mstore/index.tsx @@ -6,14 +6,7 @@ import RoleStore from './roleStore'; import APIClient from 'App/api_client'; import FunnelStore from './funnelStore'; import { - dashboardService, - metricService, - sessionService, - userService, - auditService, - funnelService, - errorService, - notesService, + services } from 'App/services'; import SettingsStore from './settingsStore'; import AuditStore from './auditStore'; @@ -22,6 +15,10 @@ import ErrorStore from './errorStore'; import SessionStore from './sessionStore'; import NotesStore from './notesStore'; import BugReportStore from './bugReportStore' +import RecordingsStore from './recordingsStore' +import AssistMultiviewStore from './assistMultiviewStore'; +import WeeklyReportStore from './weeklyReportConfigStore' +import AlertStore from './alertsStore' export class RootStore { dashboardStore: DashboardStore; @@ -35,7 +32,11 @@ export class RootStore { notificationStore: NotificationStore; sessionStore: SessionStore; notesStore: NotesStore; - bugReportStore: BugReportStore + bugReportStore: BugReportStore; + recordingsStore: RecordingsStore; + assistMultiviewStore: AssistMultiviewStore; + weeklyReportStore: WeeklyReportStore + alertsStore: AlertStore constructor() { this.dashboardStore = new DashboardStore(); @@ -50,18 +51,17 @@ export class RootStore { this.sessionStore = new SessionStore(); this.notesStore = new NotesStore(); this.bugReportStore = new BugReportStore(); + this.recordingsStore = new RecordingsStore(); + this.assistMultiviewStore = new AssistMultiviewStore(); + this.weeklyReportStore = new WeeklyReportStore(); + this.alertsStore = new AlertStore(); } initClient() { const client = new APIClient(); - dashboardService.initClient(client); - metricService.initClient(client); - funnelService.initClient(client); - sessionService.initClient(client); - userService.initClient(client); - auditService.initClient(client); - errorService.initClient(client); - notesService.initClient(client) + services.forEach(service => { + service.initClient(client); + }) } } diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index 11bcf8894..9ef49759d 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -1,153 +1,288 @@ -import { makeAutoObservable, computed } from "mobx" -import Widget from "./types/widget"; -import { metricService, errorService } from "App/services"; +import { makeAutoObservable } from 'mobx'; +import Widget from './types/widget'; +import { metricService, errorService } from 'App/services'; import { toast } from 'react-toastify'; -import Error from "./types/error"; +import Error from './types/error'; +import { + TIMESERIES, + TABLE, + FUNNEL, + ERRORS, + RESOURCE_MONITORING, + PERFORMANCE, + WEB_VITALS, + INSIGHTS, + CLICKMAP +} from 'App/constants/card'; +import { clickmapFilter } from 'App/types/filter/newFilter'; +import { getRE } from 'App/utils'; -export default class MetricStore { - isLoading: boolean = false - isSaving: boolean = false - - metrics: Widget[] = [] - instance = new Widget() - - page: number = 1 - pageSize: number = 10 - metricsSearch: string = "" - sort: any = {} - - sessionsPage: number = 1 - sessionsPageSize: number = 10 - - constructor() { - makeAutoObservable(this) - } - - @computed - get sortedWidgets() { - return [...this.metrics].sort((a, b) => b.lastModified - a.lastModified) - } - - // State Actions - init(metric?: Widget | null) { - this.instance.update(metric || new Widget()) - } - - updateKey(key: string, value: any) { - // @ts-ignore - this[key] = value - } - - merge(object: any) { - Object.assign(this.instance, object) - this.instance.updateKey('hasChanged', true) - } - - reset(id: string) { - const metric = this.findById(id) - if (metric) { - this.instance = metric - } - } - - addToList(metric: Widget) { - this.metrics.push(metric) - } - - updateInList(metric: Widget) { - // @ts-ignore - const index = this.metrics.findIndex((m: Widget) => m[Widget.ID_KEY] === metric[Widget.ID_KEY]) - if (index >= 0) { - this.metrics[index] = metric - } - } - - findById(id: string) { - // @ts-ignore - return this.metrics.find(m => m[Widget.ID_KEY] === id) - } - - removeById(id: string): void { - // @ts-ignore - this.metrics = this.metrics.filter(m => m[Widget.ID_KEY] !== id) - } - - get paginatedList(): Widget[] { - const start = (this.page - 1) * this.pageSize - const end = start + this.pageSize - return this.metrics.slice(start, end) - } - - // API Communication - save(metric: Widget, dashboardId?: string): Promise<any> { - const wasCreating = !metric.exists() - this.isSaving = true - return new Promise((resolve, reject) => { - metricService.saveMetric(metric, dashboardId) - .then((metric: any) => { - const _metric = new Widget().fromJson(metric) - if (wasCreating) { - toast.success('Metric created successfully') - this.addToList(_metric) - this.instance = _metric - } else { - toast.success('Metric updated successfully') - this.updateInList(_metric) - } - resolve(_metric) - }).catch(() => { - toast.error('Error saving metric') - reject() - }).finally(() => { - this.instance.updateKey('hasChanged', false) - this.isSaving = false - }) - }) - } - - fetchList() { - this.isLoading = true - return metricService.getMetrics() - .then((metrics: any[]) => { - this.metrics = metrics.map(m => new Widget().fromJson(m)) - }).finally(() => { - this.isLoading = false - }) - } - - fetch(id: string, period?: any) { - this.isLoading = true - return metricService.getMetric(id) - .then((metric: any) => { - return this.instance = new Widget().fromJson(metric, period) - }) - .finally(() => { - this.isLoading = false - }) - } - - delete(metric: Widget) { - this.isSaving = true - // @ts-ignore - return metricService.deleteMetric(metric[Widget.ID_KEY]) - .then(() => { - // @ts-ignore - this.removeById(metric[Widget.ID_KEY]) - toast.success('Metric deleted successfully') - }).finally(() => { - this.instance.updateKey('hasChanged', false) - this.isSaving = false - }) - } - - fetchError(errorId: any): Promise<any> { - return new Promise((resolve, reject) => { - errorService.one(errorId).then((error: any) => { - resolve(new Error().fromJSON(error)) - }).catch((error: any) => { - toast.error('Failed to fetch error details.') - reject(error) - }) - }) - } +interface MetricFilter { + query?: string; + showMine?: boolean; + type?: string; + dashboard?: []; +} +export default class MetricStore { + isLoading: boolean = false; + isSaving: boolean = false; + + metrics: Widget[] = []; + instance = new Widget(); + + page: number = 1; + pageSize: number = 10; + metricsSearch: string = ''; + sort: any = { by: 'desc' }; + + filter: MetricFilter = { type: 'all', dashboard: [], query: '' }; + + sessionsPage: number = 1; + sessionsPageSize: number = 10; + listView?: boolean = true; + clickMapFilter: boolean = false; + + clickMapSearch = ''; + clickMapLabel = ''; + + constructor() { + makeAutoObservable(this); + } + + get sortedWidgets() { + return [...this.metrics].sort((a, b) => + this.sort.by === 'desc' ? b.lastModified - a.lastModified : a.lastModified - b.lastModified + ); + } + + get filteredCards() { + const filterRE = this.filter.query ? getRE(this.filter.query, 'i') : null; + const dbIds = this.filter.dashboard ? this.filter.dashboard.map((i: any) => i.value) : []; + return this.metrics + .filter( + (card) => + (this.filter.showMine + ? card.owner === JSON.parse(localStorage.getItem('user')!).account.email + : true) && + (this.filter.type === 'all' || card.metricType === this.filter.type) && + (!dbIds.length || + card.dashboards.map((i) => i.dashboardId).some((id) => dbIds.includes(id))) && + // @ts-ignore + (!filterRE || ['name', 'owner'].some((key) => filterRE.test(card[key]))) + ) + .sort((a, b) => + this.sort.by === 'desc' ? b.lastModified - a.lastModified : a.lastModified - b.lastModified + ); + } + + // State Actions + init(metric?: Widget | null) { + this.instance.update(metric || new Widget()); + } + + updateKey(key: string, value: any) { + // @ts-ignore + this[key] = value; + + if (key === 'filter') { + this.page = 1; + } + } + + setClickMaps(val: boolean) { + this.clickMapFilter = val; + } + + changeClickMapSearch(val: string, label: string) { + this.clickMapSearch = val; + this.clickMapLabel = label; + } + + merge(obj: any, updateChangeFlag: boolean = true) { + const type = obj.metricType; + + // handle metricType change + if (obj.hasOwnProperty('metricType') && type !== this.instance.metricType) { + this.instance.series.forEach((s: any, i: number) => { + this.instance.series[i].filter.eventsOrderSupport = ['then', 'or', 'and'] + }) + this.changeType(type); + } + + if (obj.hasOwnProperty('metricOf') && obj.metricOf !== this.instance.metricOf && (obj.metricOf === 'sessions' || obj.metricOf === 'jsErrors')) { + obj.viewType = 'table' + } + + // handle metricValue change + if (obj.hasOwnProperty('metricValue') && obj.metricValue !== this.instance.metricValue) { + if (Array.isArray(obj.metricValue) && obj.metricValue.length > 1) { + obj.metricValue = obj.metricValue.filter((i: any) => i.value !== 'all'); + } + } + + Object.assign(this.instance, obj); + this.instance.updateKey('hasChanged', updateChangeFlag); + } + + changeType(value: string) { + const obj: any = { metricType: value }; + obj.series = this.instance.series; + + obj['metricValue'] = []; + + if (value === TABLE || value === TIMESERIES) { + obj['viewType'] = 'table'; + } + if (value === TIMESERIES) { + obj['viewType'] = 'lineChart'; + } + if ( + value === ERRORS || + value === RESOURCE_MONITORING || + value === PERFORMANCE || + value === WEB_VITALS || + value === CLICKMAP + ) { + obj['viewType'] = 'chart'; + } + + if (value === FUNNEL) { + obj['metricOf'] = 'sessionCount'; + obj.series[0].filter.eventsOrder = 'then' + obj.series[0].filter.eventsOrderSupport = ['then'] + } + + if (value === INSIGHTS) { + obj['metricOf'] = 'issueCategories'; + obj['viewType'] = 'list'; + } + + if (value === CLICKMAP) { + obj.series = obj.series.slice(0, 1); + if (this.instance.metricType !== CLICKMAP) { + obj.series[0].filter.removeFilter(0); + } + + if (obj.series[0] && obj.series[0].filter.filters.length < 1) { + obj.series[0].filter.addFilter({ + ...clickmapFilter, + value: [''], + }); + } + } + this.instance.update(obj); + } + + reset(id: string) { + const metric = this.findById(id); + if (metric) { + this.instance = metric; + } + } + + addToList(metric: Widget) { + this.metrics.push(metric); + } + + updateInList(metric: Widget) { + // @ts-ignore + const index = this.metrics.findIndex((m: Widget) => m[Widget.ID_KEY] === metric[Widget.ID_KEY]); + if (index >= 0) { + this.metrics[index] = metric; + } + } + + findById(id: string) { + // @ts-ignore + return this.metrics.find((m) => m[Widget.ID_KEY] === id); + } + + removeById(id: string): void { + // @ts-ignore + this.metrics = this.metrics.filter((m) => m[Widget.ID_KEY] !== id); + } + + get paginatedList(): Widget[] { + const start = (this.page - 1) * this.pageSize; + const end = start + this.pageSize; + return this.metrics.slice(start, end); + } + + // API Communication + async save(metric: Widget): Promise<Widget> { + this.isSaving = true; + try { + const metricData = await metricService.saveMetric(metric); + const _metric = new Widget().fromJson(metricData); + if (!metric.exists()) { + toast.success('Card created successfully'); + this.addToList(_metric); + } else { + toast.success('Card updated successfully'); + this.updateInList(_metric); + } + this.instance = _metric; + this.instance.updateKey('hasChanged', false); + return _metric; + } catch (error) { + toast.error('Error saving metric'); + throw error; + } finally { + this.isSaving = false; + } + } + + fetchList() { + this.isLoading = true; + return metricService + .getMetrics() + .then((metrics: any[]) => { + this.metrics = metrics.map((m) => new Widget().fromJson(m)); + }) + .finally(() => { + this.isLoading = false; + }); + } + + fetch(id: string, period?: any) { + this.isLoading = true; + return metricService + .getMetric(id) + .then((metric: any) => { + return (this.instance = new Widget().fromJson(metric, period)); + }) + .finally(() => { + this.isLoading = false; + }); + } + + delete(metric: Widget) { + this.isSaving = true; + // @ts-ignore + return metricService + .deleteMetric(metric[Widget.ID_KEY]) + .then(() => { + // @ts-ignore + this.removeById(metric[Widget.ID_KEY]); + toast.success('Card deleted successfully'); + }) + .finally(() => { + this.instance.updateKey('hasChanged', false); + this.isSaving = false; + }); + } + + fetchError(errorId: any): Promise<any> { + return new Promise((resolve, reject) => { + errorService + .one(errorId) + .then((error: any) => { + resolve(new Error().fromJSON(error)); + }) + .catch((error: any) => { + toast.error('Failed to fetch error details.'); + reject(error); + }); + }); + } } diff --git a/frontend/app/mstore/notesStore.ts b/frontend/app/mstore/notesStore.ts index 46d7fc652..ea52cb2e7 100644 --- a/frontend/app/mstore/notesStore.ts +++ b/frontend/app/mstore/notesStore.ts @@ -2,13 +2,9 @@ import { makeAutoObservable } from "mobx" import { notesService } from "App/services" import { Note, WriteNote, iTag, NotesFilter } from 'App/services/NotesService' -interface SessionNotes { - [sessionId: string]: Note[] -} - export default class NotesStore { notes: Note[] = [] - sessionNotes: SessionNotes = {} + sessionNotes: Note[] = [] loading: boolean page = 1 pageSize = 10 @@ -48,7 +44,7 @@ export default class NotesStore { this.loading = true try { const notes = await notesService.getNotesBySessionId(sessionId) - this.sessionNotes[sessionId] = notes + this.setNotes(notes) return notes; } catch (e) { console.error(e) @@ -57,6 +53,10 @@ export default class NotesStore { } } + setNotes(notes: Note[]) { + this.sessionNotes = notes + } + async addNote(sessionId: string, note: WriteNote) { this.loading = true try { @@ -134,4 +134,13 @@ export default class NotesStore { console.error(e) } } + + async sendMsTeamsNotification(noteId: string, webhook: string) { + try { + const resp = await notesService.sendMsTeamsNotification(noteId, webhook) + return resp + } catch (e) { + console.error(e) + } + } } diff --git a/frontend/app/mstore/recordingsStore.ts b/frontend/app/mstore/recordingsStore.ts new file mode 100644 index 000000000..783c129e7 --- /dev/null +++ b/frontend/app/mstore/recordingsStore.ts @@ -0,0 +1,92 @@ +import { makeAutoObservable } from 'mobx'; +import { recordingsService } from 'App/services'; +import { IRecord } from 'App/services/RecordingsService'; + +export default class RecordingsStore { + recordings: IRecord[] = []; + loading: boolean; + + page = 1; + pageSize = 15; + order: 'desc' | 'asc' = 'desc'; + search = ''; + // later we will add search by user id + userId = '0'; + + constructor() { + makeAutoObservable(this); + } + + setRecordings(records: IRecord[]) { + this.recordings = records; + } + + setUserId(userId: string) { + this.userId = userId; + this.fetchRecordings(); + } + + updateSearch(val: string) { + this.search = val; + } + updatePage(page: number) { + this.page = page; + } + + async fetchRecordings() { + const filter = { + page: this.page, + limit: this.pageSize, + order: this.order, + search: this.search, + userId: this.userId === '0' ? undefined : this.userId, + }; + + this.loading = true; + try { + const recordings = await recordingsService.fetchRecordings(filter); + this.setRecordings(recordings); + return recordings; + } catch (e) { + console.error(e); + } finally { + this.loading = false; + } + } + + async fetchRecordingUrl(id: number): Promise<string> { + this.loading = true; + try { + const recording = await recordingsService.fetchRecording(id); + return recording.URL; + } catch (e) { + console.error(e); + } finally { + this.loading = false; + } + } + + async deleteRecording(id: number) { + this.loading = true; + try { + const recording = await recordingsService.deleteRecording(id); + return recording; + } catch (e) { + console.error(e); + } finally { + this.loading = false; + } + } + + async updateRecordingName(id: number, name: string) { + this.loading = true; + try { + const recording = await recordingsService.updateRecordingName(id, name); + return recording; + } catch (e) { + console.error(e); + } finally { + this.loading = false; + } + } +} diff --git a/frontend/app/mstore/roleStore.ts b/frontend/app/mstore/roleStore.ts index 6f87b4bcb..45c8b65d0 100644 --- a/frontend/app/mstore/roleStore.ts +++ b/frontend/app/mstore/roleStore.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, observable, action } from "mobx" +import { makeAutoObservable, observable } from "mobx" import { userService } from "App/services"; import Role, { IRole } from "./types/role"; diff --git a/frontend/app/mstore/sessionStore.ts b/frontend/app/mstore/sessionStore.ts index d055a9aa8..1a3bc6426 100644 --- a/frontend/app/mstore/sessionStore.ts +++ b/frontend/app/mstore/sessionStore.ts @@ -1,8 +1,15 @@ import { makeAutoObservable, observable, action } from 'mobx'; import { sessionService } from 'App/services'; import { filterMap } from 'Duck/search'; -import Session from './types/session'; +import Session from 'Types/session'; import Record, { LAST_7_DAYS } from 'Types/app/period'; +import Watchdog from "Types/watchdog"; +import ErrorStack from 'Types/session/errorStack'; +import { Location, InjectedEvent } from 'Types/session/event' +import { getDateRangeFromValue } from "App/dateRange"; +import { getRE, setSessionFilter, getSessionFilter, compareJsonObjects, cleanSessionFilters } from 'App/utils'; +import store from 'App/store' +import { Note } from "App/services/NotesService"; class UserFilter { endDate: number = new Date().getTime(); @@ -76,10 +83,48 @@ class DevTools { } } +const range = getDateRangeFromValue(LAST_7_DAYS); +const defaultDateFilters = { + url: '', + rangeValue: LAST_7_DAYS, + startDate: range.start.unix() * 1000, + endDate: range.end.unix() * 1000, +}; + export default class SessionStore { userFilter: UserFilter = new UserFilter(); devTools: DevTools = new DevTools(); + list: Session[] = [] + sessionIds: string[] = [] + current = new Session() + total = 0 + keyMap = {} + wdTypeCount = {} + favoriteList: Session[] = [] + activeTab = Watchdog({ name: 'All', type: 'all' }) + timezone = 'local' + errorStack: ErrorStack[] = [] + eventsIndex = [] + sourcemapUploaded = true + filteredEvents: InjectedEvent[] | null = null + eventsQuery = '' + showChatWindow = false + liveSessions: Session[] = [] + visitedEvents = [] + insights: any[] = [] + insightFilters = defaultDateFilters + host = '' + funnelPage = {} + /** @Deprecated */ + timelinePointer = {} + sessionPath = {} + lastPlayedSessionId: string + timeLineTooltip = { time: 0, offset: 0, isVisible: false, timeStr: '' } + createNoteTooltip = { time: 0, isVisible: false, isEdit: false, note: null } + previousId = '' + nextId = '' + constructor() { makeAutoObservable(this, { userFilter: observable, @@ -94,10 +139,10 @@ export default class SessionStore { getSessions(filter: any): Promise<any> { return new Promise((resolve, reject) => { sessionService - .getSessions(filter.toJson()) + .getSessions(filter.toJson?.() || filter) .then((response: any) => { resolve({ - sessions: response.sessions.map((session: any) => new Session().fromJson(session)), + sessions: response.sessions.map((session: any) => new Session(session)), total: response.total, }); }) @@ -106,4 +151,256 @@ export default class SessionStore { }); }); } + + async fetchLiveSessions(params = {}) { + try { + const data = await sessionService.getLiveSessions(params); + this.liveSessions = data.map(session => new Session({ ...session, live: true })); + } catch (e) { + console.error(e) + } + } + + async fetchSessions(params = {}, force = false) { + try { + if (!force) { + const oldFilters = getSessionFilter(); + if (compareJsonObjects(oldFilters, cleanSessionFilters(params))) { + return; + } + } + setSessionFilter(cleanSessionFilters(params)); + const data = await sessionService.getSessions(params); + const list = data.sessions.map(s => new Session(s)) + + this.list = list; + this.total = data.total; + this.sessionIds = data.sessions.map(s => s.sessionId); + this.favoriteList = list.filter(s => s.favorite); + } catch (e) { + console.error(e) + } + } + + async fetchSessionInfo(sessionId: string, isLive = false) { + try { + const { events } = store.getState().getIn(['filters', 'appliedFilter']); + const data = await sessionService.getSessionInfo(sessionId, isLive) + const session = new Session(data) + + const matching: number[] = []; + const visitedEvents: Location[] = []; + const tmpMap: Set<string> = new Set(); + + session.events.forEach((event) => { + if (event.type === 'LOCATION' && !tmpMap.has(event.url)) { + tmpMap.add(event.url); + visitedEvents.push(event); + } + }); + + + (events as {}[]).forEach(({ key, operator, value }: any) => { + session.events.forEach((e, index) => { + if (key === e.type) { + const val = e.type === 'LOCATION' ? e.url : e.value; + if (operator === 'is' && value === val) { + matching.push(index); + } + if (operator === 'contains' && val.includes(value)) { + matching.push(index); + } + } + }); + }); + } catch (e) { + console.error(e) + } + } + + async fetchErrorStack(sessionId: string, errorId: string) { + try { + const data = await sessionService.getErrorStack(sessionId, errorId); + this.errorStack = data.trace.map(es => new ErrorStack(es)) + } catch (e) { + console.error(e) + } + } + + async fetchAutoplayList(params = {}) { + try { + setSessionFilter(cleanSessionFilters(params)); + const data = await sessionService.getAutoplayList(params); + const list = [...this.sessionIds, ...data.map(s => s.sessionId)] + this.sessionIds = list.filter((id, ind) => list.indexOf(id) === ind); + } catch (e) { + console.error(e) + } + } + + setAutoplayValues() { + const currentId = this.current.sessionId + const currentIndex = this.sessionIds.indexOf(currentId) + + this.previousId = this.sessionIds[currentIndex - 1] + this.nextId = this.sessionIds[currentIndex + 1] + } + + setEventQuery(filter: { query: string }) { + const events = this.current.events + const query = filter.query; + const searchRe = getRE(query, 'i') + + const filteredEvents = query ? events.filter( + (e) => searchRe.test(e.url) + || searchRe.test(e.value) + || searchRe.test(e.label) + || searchRe.test(e.type) + || (e.type === 'LOCATION' && searchRe.test('visited')) + ) : null; + + this.filteredEvents = filteredEvents + this.eventsQuery = query + } + + async toggleFavorite(id: string) { + try { + const r = await sessionService.toggleFavorite(id) + if (r.success) { + const list = this.list; + const current = this.current; + const sessionIdx = list.findIndex(({ sessionId }) => sessionId === id); + const session = list[sessionIdx] + const wasInFavorite = this.favoriteList.findIndex(({ sessionId }) => sessionId === id) > -1; + + if (session && !wasInFavorite) { + session.favorite = true + this.list[sessionIdx] = session + } + if (current.sessionId === id) { + this.current.favorite = !wasInFavorite + } + + if (session) { + if (wasInFavorite) { + this.favoriteList = this.favoriteList.filter(({ sessionId }) => sessionId !== id) + } else { + this.favoriteList.push(session) + } + } + } else { + console.error(r) + } + } catch (e) { + console.error(e) + } + } + + sortSessions(sortKey: string, sign: number) { + const comparator = (s1: Session, s2: Session) => { + // @ts-ignore + let diff = s1[sortKey] - s2[sortKey]; + diff = diff === 0 ? s1.startedAt - s2.startedAt : diff; + return sign * diff; + }; + + this.list = this.list.sort(comparator) + this.favoriteList = this.favoriteList.sort(comparator) + return; + } + + setActiveTab(tab: { type: string }) { + const list = tab.type === 'all' + ? this.list : this.list.filter(s => s.issueTypes.includes(tab.type)) + + // @ts-ignore + this.activeTab = tab + this.sessionIds = list.map(s => s.sessionId) + } + + setTimezone(tz: string) { + this.timezone = tz; + } + + toggleChatWindow(isActive: boolean) { + this.showChatWindow = isActive + } + + async fetchInsights(filters = {}) { + try { + const data = await sessionService.getClickMap(filters) + + this.insights = data + } catch (e) { + console.error(e) + } + } + + setFunnelPage(page = {}) { + this.funnelPage = page || false + } + + /* @deprecated */ + setTimelinePointer(pointer: {}) { + this.timelinePointer = pointer + } + + setTimelineTooltip(tp: { time: number, offset: number, isVisible: boolean, timeStr: string }) { + this.timeLineTooltip = tp + } + + setEditNoteTooltip(tp: { time: number, isVisible: boolean, isEdit: boolean, note: any }) { + this.createNoteTooltip = tp + } + + filterOutNote(noteId: string) { + const current = this.current + + current.notesWithEvents = current.notesWithEvents.filter(n => { + if ('noteId' in item) { + return item.noteId !== noteId + } + return true + }) + + this.current = current + } + + addNote(note: Note) { + const current = this.current + + current.notesWithEvents.push(note) + current.notesWithEvents.sort((a,b) => { + const aTs = a.time || a.timestamp + const bTs = b.time || b.timestamp + + return aTs - bTs + }) + + this.current = current + } + + updateNote(note: Note) { + const noteIndex = this.current.notesWithEvents.findIndex(item => { + if ('noteId' in item) { + return item.noteId === note.noteId + } + return false + }) + + this.current.notesWithEvents[noteIndex] = note + } + + setSessionPath(path = {}) { + this.sessionPath = path + } + + setLastPlayed(sessionId: string) { + const list = this.list + const sIndex = list.findIndex((s) => s.sessionId === sessionId) + if (sIndex !== -1) { + this.list[sIndex].viewed = true + } + } + } diff --git a/frontend/app/mstore/settingsStore.ts b/frontend/app/mstore/settingsStore.ts index 45cb9610c..c31071694 100644 --- a/frontend/app/mstore/settingsStore.ts +++ b/frontend/app/mstore/settingsStore.ts @@ -1,45 +1,98 @@ -import { makeAutoObservable, observable, action } from "mobx" +import { makeAutoObservable, observable } from "mobx" import SessionSettings from "./types/sessionSettings" import { sessionService } from "App/services" import { toast } from 'react-toastify'; +import Webhook, { IWebhook } from 'Types/webhook'; +import { + webhookService +} from 'App/services'; export default class SettingsStore { - loadingCaptureRate: boolean = false; - sessionSettings: SessionSettings = new SessionSettings() - captureRateFetched: boolean = false; - limits: any = null; + loadingCaptureRate: boolean = false; + sessionSettings: SessionSettings = new SessionSettings() + captureRateFetched: boolean = false; + limits: any = null; - constructor() { - makeAutoObservable(this, { - sessionSettings: observable, + webhooks: Webhook[] = [] + webhookInst = new Webhook() + + hooksLoading = false + + constructor() { + makeAutoObservable(this, { + sessionSettings: observable, + }) + } + + saveCaptureRate(data: any) { + return sessionService.saveCaptureRate(data) + .then(data => data.json()) + .then(({ data }) => { + this.sessionSettings.merge({ + captureRate: data.rate, + captureAll: data.captureAll }) - } + toast.success("Settings updated successfully"); + }).catch(err => { + toast.error("Error saving capture rate"); + }) + } - saveCaptureRate(data: any) { - return sessionService.saveCaptureRate(data) - .then(data => data.json()) - .then(({ data }) => { - this.sessionSettings.merge({ - captureRate: data.rate, - captureAll: data.captureAll - }) - toast.success("Settings updated successfully"); - }).catch(err => { - toast.error("Error saving capture rate"); - }) - } + fetchCaptureRate(): Promise<any> { + this.loadingCaptureRate = true; + return sessionService.fetchCaptureRate() + .then(data => { + this.sessionSettings.merge({ + captureRate: data.rate, + captureAll: data.captureAll + }) + this.captureRateFetched = true; + }).finally(() => { + this.loadingCaptureRate = false; + }) + } - fetchCaptureRate(): Promise<any> { - this.loadingCaptureRate = true; - return sessionService.fetchCaptureRate() - .then(data => { - this.sessionSettings.merge({ - captureRate: data.rate, - captureAll: data.captureAll - }) - this.captureRateFetched = true; - }).finally(() => { - this.loadingCaptureRate = false; - }) - } + fetchWebhooks = () => { + this.hooksLoading = true + return webhookService.fetchList() + .then(data => { + this.webhooks = data.map(hook => new Webhook(hook)) + this.hooksLoading = false + }) + } + + initWebhook = (inst?: Partial<IWebhook> | Webhook) => { + this.webhookInst = inst instanceof Webhook ? inst : new Webhook(inst) + } + + saveWebhook = (inst: Webhook) => { + this.hooksLoading = true + return webhookService.saveWebhook(inst) + .then(data => { + this.webhookInst = new Webhook(data) + if (inst.webhookId === undefined) this.setWebhooks([...this.webhooks, this.webhookInst]) + else this.setWebhooks([...this.webhooks.filter(hook => hook.webhookId !== data.webhookId), this.webhookInst]) + + }) + .finally(() => { + this.hooksLoading = false + }) + } + + setWebhooks = (webhooks: Webhook[]) => { + this.webhooks = webhooks + } + + removeWebhook = (hookId: string) => { + this.hooksLoading = true + return webhookService.removeWebhook(hookId) + .then(() => { + this.webhooks = this.webhooks.filter(hook => hook.webhookId!== hookId) + this.hooksLoading = false + }) + } + + editWebhook = (diff: Partial<IWebhook>) => { + Object.assign(this.webhookInst, diff) + } } diff --git a/frontend/app/mstore/types/dashboard.ts b/frontend/app/mstore/types/dashboard.ts index 0213f9b0d..af499c870 100644 --- a/frontend/app/mstore/types/dashboard.ts +++ b/frontend/app/mstore/types/dashboard.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, observable, action, runInAction } from "mobx" +import { makeAutoObservable, runInAction } from "mobx" import Widget from "./widget" import { dashboardService } from "App/services" import { toast } from 'react-toastify'; @@ -6,7 +6,7 @@ import { DateTime } from 'luxon'; export default class Dashboard { public static get ID_KEY():string { return "dashboardId" } - dashboardId: any = undefined + dashboardId?: string = undefined name: string = "Untitled Dashboard" description: string = "" isPublic: boolean = true @@ -30,12 +30,19 @@ export default class Dashboard { this.validate() } + updateInfo(data: any) { + runInAction(() => { + this.name = data.name || this.name + this.description = data.description || this.description + this.isPublic = data.isPublic + }) + } + toJson() { return { dashboardId: this.dashboardId, name: this.name, isPublic: this.isPublic, - createdAt: this.createdAt, metrics: this.metrics, description: this.description, } diff --git a/frontend/app/mstore/types/filter.ts b/frontend/app/mstore/types/filter.ts index 549a0ad29..9b2874e9f 100644 --- a/frontend/app/mstore/types/filter.ts +++ b/frontend/app/mstore/types/filter.ts @@ -7,6 +7,7 @@ export default class Filter { name: string = '' filters: FilterItem[] = [] eventsOrder: string = 'then' + eventsOrderSupport: string[] = ['then', 'or', 'and'] startTimestamp: number = 0 endTimestamp: number = 0 diff --git a/frontend/app/mstore/types/filterItem.ts b/frontend/app/mstore/types/filterItem.ts index 9214fb9bd..65d79e22d 100644 --- a/frontend/app/mstore/types/filterItem.ts +++ b/frontend/app/mstore/types/filterItem.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, runInAction, observable, action, reaction } from 'mobx'; +import { makeAutoObservable, observable, action } from 'mobx'; import { FilterKey, FilterType, FilterCategory } from 'Types/filter/filterType'; import { filtersMap } from 'Types/filter/newFilter'; diff --git a/frontend/app/mstore/types/filterSeries.ts b/frontend/app/mstore/types/filterSeries.ts index c3051e612..46d6f6472 100644 --- a/frontend/app/mstore/types/filterSeries.ts +++ b/frontend/app/mstore/types/filterSeries.ts @@ -1,6 +1,6 @@ // import Filter from 'Types/filter'; import Filter from './filter' -import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx" +import { makeAutoObservable, observable, action } from "mobx" export default class FilterSeries { public static get ID_KEY():string { return "seriesId" } @@ -24,7 +24,7 @@ export default class FilterSeries { fromJson(json) { this.seriesId = json.seriesId this.name = json.name - this.filter = new Filter().fromJson(json.filter) + this.filter = new Filter().fromJson(json.filter || { filters: [] }) return this } diff --git a/frontend/app/mstore/types/session.ts b/frontend/app/mstore/types/session.ts index 12b031d8a..d0984593e 100644 --- a/frontend/app/mstore/types/session.ts +++ b/frontend/app/mstore/types/session.ts @@ -1,6 +1,6 @@ import { runInAction, makeAutoObservable, observable } from 'mobx' -import { List, Map } from 'immutable'; -import { DateTime, Duration } from 'luxon'; +import { Map } from 'immutable'; +import { Duration } from 'luxon'; const HASH_MOD = 1610612741; const HASH_P = 53; diff --git a/frontend/app/mstore/types/sessionSettings.ts b/frontend/app/mstore/types/sessionSettings.ts index 95005b85d..ece447dbb 100644 --- a/frontend/app/mstore/types/sessionSettings.ts +++ b/frontend/app/mstore/types/sessionSettings.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, runInAction, action } from 'mobx'; +import { makeAutoObservable, runInAction } from 'mobx'; import moment from 'moment'; import { SKIP_TO_ISSUE, TIMEZONE, DURATION_FILTER } from 'App/constants/storageKeys'; @@ -13,27 +13,52 @@ const defaultDurationFilter = { countType: 'sec' } +const negativeExceptions = { + 4: ['-04:30'], + 3: ['-03:30'], + +} +const exceptions = { + 3: ['+03:30'], + 4: ['+04:30'], + 5: ['+05:30', '+05:45'], + 6: ['+06:30'], + 9: ['+09:30'] +} + export const generateGMTZones = (): Timezone[] => { const timezones: Timezone[] = []; - const positiveNumbers = [...Array(12).keys()]; - const negativeNumbers = [...Array(12).keys()].reverse(); + const positiveNumbers = [...Array(13).keys()]; + const negativeNumbers = [...Array(13).keys()].reverse(); negativeNumbers.pop(); // remove trailing zero since we have one in positive numbers array const combinedArray = [...negativeNumbers, ...positiveNumbers]; for (let i = 0; i < combinedArray.length; i++) { - let symbol = i < 11 ? '-' : '+'; - let isUTC = i === 11; - let value = String(combinedArray[i]).padStart(2, '0'); + let symbol = i < 12 ? '-' : '+'; + let isUTC = i === 12; + const item = combinedArray[i] + let value = String(item).padStart(2, '0'); - let tz = `UTC ${symbol}${String(combinedArray[i]).padStart(2, '0')}:00`; + let tz = `UTC ${symbol}${String(item).padStart(2, '0')}:00`; let dropdownValue = `UTC${symbol}${value}`; timezones.push({ label: tz, value: isUTC ? 'UTC' : dropdownValue }); + + // @ts-ignore + const negativeMatch = negativeExceptions[item], positiveMatch = exceptions[item] + if (i < 11 && negativeMatch) { + negativeMatch.forEach((str: string) => { + timezones.push({ label: `UTC ${str}`, value: `UTC${str}`}) + }) + } else if (i > 11 && positiveMatch) { + positiveMatch.forEach((str: string) => { + timezones.push({ label: `UTC ${str}`, value: `UTC${str}`}) + }) + } } - timezones.splice(17, 0, { label: 'GMT +05:30', value: 'UTC+05:30' }); return timezones; }; diff --git a/frontend/app/mstore/types/user.ts b/frontend/app/mstore/types/user.ts index 9b5a06b43..e867be263 100644 --- a/frontend/app/mstore/types/user.ts +++ b/frontend/app/mstore/types/user.ts @@ -41,7 +41,7 @@ export default class User { this.userId = json.userId || json.id; // TODO api returning id this.name = json.name; this.email = json.email; - this.createdAt = json.createdAt && DateTime.fromMillis(json.createdAt || 0) + this.createdAt = json.createdAt && DateTime.fromMillis(new Date(json.createdAt).getTime()) this.isAdmin = json.admin this.isSuperAdmin = json.superAdmin this.isJoined = json.joined diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index 62371fde3..ecf7249ce 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -1,204 +1,317 @@ -import { makeAutoObservable, runInAction, observable, action } from "mobx" -import FilterSeries from "./filterSeries"; +import { makeAutoObservable, runInAction } from 'mobx'; +import FilterSeries from './filterSeries'; import { DateTime } from 'luxon'; -import { metricService, errorService } from "App/services"; -import Session from "App/mstore/types/session"; +import Session from 'App/mstore/types/session'; import Funnelissue from 'App/mstore/types/funnelIssue'; -import { issueOptions } from 'App/constants/filterOptions'; +import { issueOptions, issueCategories, issueCategoriesMap } from 'App/constants/filterOptions'; import { FilterKey } from 'Types/filter/filterType'; import Period, { LAST_24_HOURS } from 'Types/app/period'; +import Funnel from "../types/funnel"; +import { metricService } from 'App/services'; +import { FUNNEL, INSIGHTS, TABLE, WEB_VITALS } from 'App/constants/card'; +import Error from '../types/error'; +import { getChartFormatter } from 'Types/dashboard/helper'; + +export class InishtIssue { + icon: string; + iconColor: string; + change: number; + isNew = false; + category: string; + label: string; + value: number; + oldValue: number; + isIncreased?: boolean; + + constructor( + category: string, + public name: string, + public ratio: number, + oldValue = 0, + value = 0, + change = 0, + isNew = false + ) { + this.category = category; + this.value = Math.round(value); + this.oldValue = Math.round(oldValue); + // @ts-ignore + this.label = issueCategoriesMap[category]; + this.icon = `ic-${category}`; + + this.change = parseInt(change.toFixed(2)); + this.isIncreased = this.change > 0; + this.iconColor = 'gray-dark'; + this.isNew = isNew; + } +} export default class Widget { - public static get ID_KEY():string { return "metricId" } - metricId: any = undefined - widgetId: any = undefined - category?: string = undefined - name: string = "Untitled Metric" - // metricType: string = "timeseries" - metricType: string = "timeseries" - metricOf: string = "sessionCount" - metricValue: string = "" - viewType: string = "lineChart" - metricFormat: string = "sessionCount" - series: FilterSeries[] = [] - sessions: [] = [] - isPublic: boolean = true - owner: string = "" - lastModified: number = new Date().getTime() - dashboards: any[] = [] - dashboardIds: any[] = [] - config: any = {} - page: number = 1 - limit: number = 5 - params: any = { density: 70 } + public static get ID_KEY(): string { + return 'metricId'; + } + metricId: any = undefined; + widgetId: any = undefined; + category?: string = undefined; + name: string = 'Untitled Card'; + metricType: string = 'timeseries'; + metricOf: string = 'sessionCount'; + metricValue: string = ''; + viewType: string = 'lineChart'; + metricFormat: string = 'sessionCount'; + series: FilterSeries[] = []; + sessions: [] = []; + isPublic: boolean = true; + owner: string = ''; + lastModified: number = new Date().getTime(); + dashboards: any[] = []; + dashboardIds: any[] = []; + config: any = {}; + page: number = 1; + limit: number = 5; + thumbnail?: string; + params: any = { density: 70 }; - period: Record<string, any> = Period({ rangeName: LAST_24_HOURS }) // temp value in detail view - hasChanged: boolean = false + period: Record<string, any> = Period({ rangeName: LAST_24_HOURS }); // temp value in detail view + hasChanged: boolean = false; - sessionsLoading: boolean = false + position: number = 0; + data: any = { + sessions: [], + issues: [], + total: 0, + chart: [], + namesMap: {}, + avg: 0, + percentiles: [], + }; + isLoading: boolean = false; + isValid: boolean = false; + dashboardId: any = undefined; + predefinedKey: string = ''; - position: number = 0 - data: any = { - sessions: [], - total: 0, - chart: [], - namesMap: {}, - avg: 0, - percentiles: [], - } - isLoading: boolean = false - isValid: boolean = false - dashboardId: any = undefined - colSpan: number = 2 - predefinedKey: string = '' + constructor() { + makeAutoObservable(this); - constructor() { - makeAutoObservable(this) + const filterSeries = new FilterSeries(); + this.series.push(filterSeries); + } - const filterSeries = new FilterSeries() - this.series.push(filterSeries) - } + updateKey(key: string, value: any) { + this[key] = value; + } - updateKey(key: string, value: any) { - this[key] = value - } + removeSeries(index: number) { + this.series.splice(index, 1); + } - removeSeries(index: number) { - this.series.splice(index, 1) - } + addSeries() { + const series = new FilterSeries(); + series.name = 'Series ' + (this.series.length + 1); + this.series.push(series); + } - addSeries() { - const series = new FilterSeries() - series.name = "Series " + (this.series.length + 1) - this.series.push(series) - } + fromJson(json: any, period?: any) { + json.config = json.config || {}; + runInAction(() => { + this.metricId = json.metricId; + this.widgetId = json.widgetId; + this.metricValue = this.metricValueFromArray(json.metricValue, json.metricType); + this.metricOf = json.metricOf; + this.metricType = json.metricType; + this.metricFormat = json.metricFormat; + this.viewType = json.viewType; + this.name = json.name; + this.series = + json.series && json.series.length > 0 + ? json.series.map((series: any) => new FilterSeries().fromJson(series)) + : [new FilterSeries()]; + this.dashboards = json.dashboards || []; + this.owner = json.ownerEmail; + this.lastModified = + json.editedAt || json.createdAt + ? DateTime.fromMillis(json.editedAt || json.createdAt) + : null; + this.config = json.config; + this.position = json.config.position; + this.predefinedKey = json.predefinedKey; + this.category = json.category; + this.thumbnail = json.thumbnail; + this.isPublic = json.isPublic; - fromJson(json: any, period?: any) { - json.config = json.config || {} - runInAction(() => { - this.metricId = json.metricId - this.widgetId = json.widgetId - this.metricValue = this.metricValueFromArray(json.metricValue) - this.metricOf = json.metricOf - this.metricType = json.metricType - this.metricFormat = json.metricFormat - this.viewType = json.viewType - this.name = json.name - this.series = json.series ? json.series.map((series: any) => new FilterSeries().fromJson(series)) : [], - this.dashboards = json.dashboards || [] - this.owner = json.ownerEmail - this.lastModified = json.editedAt || json.createdAt ? DateTime.fromMillis(json.editedAt || json.createdAt) : null - this.config = json.config - this.position = json.config.position - this.predefinedKey = json.predefinedKey - this.category = json.category + if (this.metricType === FUNNEL) { + this.series[0].filter.eventsOrder = 'then'; + this.series[0].filter.eventsOrderSupport = ['then']; + } - if (period) { - this.period = period + if (period) { + this.period = period; + } + }); + return this; + } + + toWidget(): any { + return { + config: { + position: this.position, + col: this.config.col, + row: this.config.row, + }, + }; + } + + toJson() { + return { + metricId: this.metricId, + widgetId: this.widgetId, + metricOf: this.metricOf, + metricValue: this.metricValueToArray(this.metricValue), + metricType: this.metricType, + metricFormat: this.metricFormat, + viewType: this.viewType, + name: this.name, + series: this.series.map((series: any) => series.toJson()), + thumbnail: this.thumbnail, + config: { + ...this.config, + col: + this.metricType === 'funnel' || + this.metricOf === FilterKey.ERRORS || + this.metricOf === FilterKey.SESSIONS || + this.metricOf === FilterKey.SLOWEST_RESOURCES || + this.metricOf === FilterKey.MISSING_RESOURCES || + this.metricOf === FilterKey.PAGES_RESPONSE_TIME_DISTRIBUTION + ? 4 + : this.metricType === WEB_VITALS + ? 1 + : 2, + }, + }; + } + + validate() { + this.isValid = this.name.length > 0; + } + + update(data: any) { + runInAction(() => { + Object.assign(this, data); + }); + } + + exists() { + return this.metricId !== undefined; + } + + setData(data: any, period: any) { + const _data: any = { ...data }; + + if (this.metricOf === FilterKey.ERRORS) { + _data['errors'] = data.errors.map((s: any) => new Error().fromJSON(s)); + } else if (this.metricType === INSIGHTS) { + _data['issues'] = data + .filter((i: any) => i.change > 0 || i.change < 0) + .map( + (i: any) => + new InishtIssue(i.category, i.name, i.ratio, i.oldValue, i.value, i.change, i.isNew) + ); + } else if (this.metricType === FUNNEL) { + _data.funnel = new Funnel().fromJSON(_data); + } else { + if (data.hasOwnProperty('chart')) { + _data['value'] = data.value; + _data['unit'] = data.unit; + _data['chart'] = getChartFormatter(period)(data.chart); + _data['namesMap'] = data.chart + .map((i: any) => Object.keys(i)) + .flat() + .filter((i: any) => i !== 'time' && i !== 'timestamp') + .reduce((unique: any, item: any) => { + if (!unique.includes(item)) { + unique.push(item); } + return unique; + }, []); + } else { + _data['chart'] = getChartFormatter(period)(Array.isArray(data) ? data : []); + _data['namesMap'] = Array.isArray(data) + ? data + .map((i) => Object.keys(i)) + .flat() + .filter((i) => i !== 'time' && i !== 'timestamp') + .reduce((unique: any, item: any) => { + if (!unique.includes(item)) { + unique.push(item); + } + return unique; + }, []) + : []; + } + } + + Object.assign(this.data, _data) + return _data; + } + + fetchSessions(metricId: any, filter: any): Promise<any> { + return new Promise((resolve) => { + metricService.fetchSessions(metricId, filter).then((response: any[]) => { + resolve( + response.map((cat: { sessions: any[] }) => { + return { + ...cat, + sessions: cat.sessions.map((s: any) => new Session().fromJson(s)), + }; + }) + ); + }); + }); + } + + fetchIssues(filter: any): Promise<any> { + return new Promise((resolve) => { + metricService.fetchIssues(filter).then((response: any) => { + const significantIssues = response.issues.significant + ? response.issues.significant.map((issue: any) => new Funnelissue().fromJSON(issue)) + : []; + const insignificantIssues = response.issues.insignificant + ? response.issues.insignificant.map((issue: any) => new Funnelissue().fromJSON(issue)) + : []; + resolve({ + issues: significantIssues.length > 0 ? significantIssues : insignificantIssues, + }); + }); + }); + } + + fetchIssue(funnelId: any, issueId: any, params: any): Promise<any> { + return new Promise((resolve, reject) => { + metricService + .fetchIssue(funnelId, issueId, params) + .then((response: any) => { + resolve({ + issue: new Funnelissue().fromJSON(response.issue), + sessions: response.sessions.sessions.map((s: any) => new Session().fromJson(s)), + }); }) - return this - } + .catch((error: any) => { + reject(error); + }); + }); + } - setPeriod(period: any) { - this.period = new Period({ start: period.startDate, end: period.endDate, rangeName: period.rangeName }) + private metricValueFromArray(metricValue: any, metricType: string) { + if (!Array.isArray(metricValue)) return metricValue; + if (metricType === TABLE) { + return issueOptions.filter((i: any) => metricValue.includes(i.value)); + } else if (metricType === INSIGHTS) { + return issueCategories.filter((i: any) => metricValue.includes(i.value)); } + } - toWidget(): any { - return { - config: { - position: this.position, - col: this.config.col, - row: this.config.row, - } - } - } - - toJsonDrilldown() { - return this.series.map((series: any) => series.toJson()) - } - - toJson() { - return { - metricId: this.metricId, - widgetId: this.widgetId, - metricOf: this.metricOf, - metricValue: this.metricValueToArray(this.metricValue), - metricType: this.metricType, - metricFormat: this.metricFormat, - viewType: this.viewType, - name: this.name, - series: this.series.map((series: any) => series.toJson()), - config: { - ...this.config, - col: (this.metricType === 'funnel' || this.metricOf === FilterKey.ERRORS || this.metricOf === FilterKey.SESSIONS) ? 4 : 2 - }, - } - } - - validate() { - this.isValid = this.name.length > 0 - } - - update(data: any) { - runInAction(() => { - Object.assign(this, data) - }) - } - - exists() { - return this.metricId !== undefined - } - - setData(data: any) { - this.data = data; - } - - fetchSessions(metricId: any, filter: any): Promise<any> { - return new Promise((resolve, reject) => { - metricService.fetchSessions(metricId, filter).then((response: any[]) => { - resolve(response.map((cat: { sessions: any[]; }) => { - return { - ...cat, - sessions: cat.sessions.map((s: any) => new Session().fromJson(s)) - } - })) - }) - }) - } - - fetchIssues(filter: any): Promise<any> { - return new Promise((resolve, reject) => { - metricService.fetchIssues(filter).then((response: any) => { - const significantIssues = response.issues.significant ? response.issues.significant.map((issue: any) => new Funnelissue().fromJSON(issue)) : [] - const insignificantIssues = response.issues.insignificant ? response.issues.insignificant.map((issue: any) => new Funnelissue().fromJSON(issue)) : [] - resolve({ - issues: significantIssues.length > 0 ? significantIssues : insignificantIssues, - }) - }) - }) - } - - fetchIssue(funnelId: any, issueId: any, params: any): Promise<any> { - return new Promise((resolve, reject) => { - metricService.fetchIssue(funnelId, issueId, params).then((response: any) => { - resolve({ - issue: new Funnelissue().fromJSON(response.issue), - sessions: response.sessions.sessions.map((s: any) => new Session().fromJson(s)), - }) - }).catch((error: any) => { - reject(error) - }) - }) - } - - private metricValueFromArray(metricValue: any) { - if (!Array.isArray(metricValue)) return metricValue; - return issueOptions.filter((i: any) => metricValue.includes(i.value)) - } - - private metricValueToArray(metricValue: any) { - if (!Array.isArray(metricValue)) return metricValue; - return metricValue.map((i: any) => i.value) - } + private metricValueToArray(metricValue: any) { + if (!Array.isArray(metricValue)) return metricValue; + return metricValue.map((i: any) => i.value); + } } diff --git a/frontend/app/mstore/userStore.ts b/frontend/app/mstore/userStore.ts index 523ae04f7..b46f776a3 100644 --- a/frontend/app/mstore/userStore.ts +++ b/frontend/app/mstore/userStore.ts @@ -1,12 +1,12 @@ import { makeAutoObservable, observable, action } from "mobx" -import User, { IUser } from "./types/user"; +import User from "./types/user"; import { userService } from "App/services"; import { toast } from 'react-toastify'; import copy from 'copy-to-clipboard'; export default class UserStore { - list: IUser[] = []; - instance: IUser|null = null; + list: User[] = []; + instance: User|null = null; page: number = 1; pageSize: number = 10; searchQuery: string = ""; @@ -44,7 +44,7 @@ export default class UserStore { } initUser(user?: any ): Promise<void> { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { if (user) { this.instance = new User().fromJson(user.toJson()); } else { @@ -54,7 +54,7 @@ export default class UserStore { }) } - updateKey(key: string, value: any) { + updateKey(key: keyof this, value: any) { this[key] = value if (key === 'searchQuery') { @@ -62,7 +62,7 @@ export default class UserStore { } } - updateUser(user: IUser) { + updateUser(user: User) { const index = this.list.findIndex(u => u.userId === user.userId); if (index > -1) { this.list[index] = user; @@ -101,7 +101,7 @@ export default class UserStore { }); } - saveUser(user: IUser): Promise<any> { + saveUser(user: User): Promise<any> { this.saving = true; const wasCreating = !user.userId; return new Promise((resolve, reject) => { diff --git a/frontend/app/mstore/weeklyReportConfigStore.ts b/frontend/app/mstore/weeklyReportConfigStore.ts new file mode 100644 index 000000000..7b0eec8a6 --- /dev/null +++ b/frontend/app/mstore/weeklyReportConfigStore.ts @@ -0,0 +1,32 @@ +import { makeAutoObservable }from "mobx" +import { configService } from "App/services"; + +export default class weeklyReportConfigStore { + public weeklyReport = false + + constructor() { + makeAutoObservable(this) + } + + setReport(value: boolean) { + this.weeklyReport = value + } + + async fetchReport() { + try { + const { weeklyReport } = await configService.fetchWeeklyReport() + return this.setReport(weeklyReport) + } catch (e) { + console.error(e) + } + } + + async fetchEditReport(value: boolean) { + try { + const { weeklyReport } = await configService.editWeeklyReport({ weeklyReport: value }) + return this.setReport(weeklyReport) + } catch (e) { + console.error(e) + } + } +} \ No newline at end of file diff --git a/frontend/app/player-ui/FullScreenButton.tsx b/frontend/app/player-ui/FullScreenButton.tsx new file mode 100644 index 000000000..1f7fd2822 --- /dev/null +++ b/frontend/app/player-ui/FullScreenButton.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import { Icon } from 'UI' +import cn from 'classnames' + +interface IProps { + size: number; + onClick: () => void; + customClasses: string; +} + +export function FullScreenButton({ size = 18, onClick, customClasses }: IProps) { + + return ( + <div + onClick={onClick} + className={cn('py-1 px-2 hover-main cursor-pointer bg-gray-lightest', customClasses)} + > + <Icon + name="arrows-angle-extend" + size={size} + color="inherit" + /> + </div> + ) +} \ No newline at end of file diff --git a/frontend/app/player-ui/PlayButton.tsx b/frontend/app/player-ui/PlayButton.tsx new file mode 100644 index 000000000..1781f481a --- /dev/null +++ b/frontend/app/player-ui/PlayButton.tsx @@ -0,0 +1,44 @@ +import React from 'react' +import { Icon, Tooltip } from "UI"; + +export enum PlayingState { + Playing, + Paused, + Completed +} + +interface IProps { + togglePlay: () => void; + iconSize: number; + state: PlayingState; +} + +const Values = { + [PlayingState.Playing]: { + icon: 'pause-fill' as const, + label: 'Pause' + }, + [PlayingState.Completed]: { + icon: 'arrow-clockwise' as const, + label: 'Replay this session', + }, + [PlayingState.Paused]: { + icon: 'play-fill-new' as const, + label: 'Play' + } +} + +export function PlayButton({ togglePlay, iconSize, state }: IProps) { + const { icon, label } = Values[state]; + + return ( + <Tooltip title={label} className="mr-4"> + <div + onClick={togglePlay} + className="hover-main color-main cursor-pointer rounded hover:bg-gray-light-shade" + > + <Icon name={icon} size={iconSize} color="inherit" /> + </div> + </Tooltip> + ) +} \ No newline at end of file diff --git a/frontend/app/player-ui/PlayTime.tsx b/frontend/app/player-ui/PlayTime.tsx new file mode 100644 index 000000000..7a6260291 --- /dev/null +++ b/frontend/app/player-ui/PlayTime.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import { Duration } from 'luxon'; + +const styles = { + padding: '0 12px', + width: '70px', + 'text-align': 'center', +} + +interface IProps { + /** current time in ms */ + time: number; + isCustom?: boolean; + format?: string; +} + +/** Play time timer */ +export const PlayTime = ({ time, isCustom, format = 'm:ss', }: IProps) => ( + <div + style={!isCustom ? styles : undefined} + className={!isCustom ? 'color-gray-medium' : undefined} + > + {Duration.fromMillis(time).toFormat(format)} + </div> +) + +PlayTime.displayName = "PlayTime"; \ No newline at end of file diff --git a/frontend/app/player-ui/ProgressBar.tsx b/frontend/app/player-ui/ProgressBar.tsx new file mode 100644 index 000000000..362b5563f --- /dev/null +++ b/frontend/app/player-ui/ProgressBar.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +interface IProps { + scale: number; + live?: boolean; + left: number; + time: number; +} + +const styles = { + display: 'block', + pointerEvents: 'none' as const, + height: '10px', + zIndex: 1, +} +const replayBg = '#d0d4f2' // active blue border +const liveBg = 'rgba(66, 174, 94, 0.3)' // light green shade + +/** Playtime progress bar */ +export function ProgressBar ({ scale, live = false, left, time }: IProps) { + return ( + <div + style={{ + ...styles, + width: `${ time * scale }%`, + backgroundColor: live && left > 99 ? liveBg : replayBg + }} + /> + ) +} + +ProgressBar.displayName = 'ProgressBar'; diff --git a/frontend/app/player-ui/ProgressCircle.tsx b/frontend/app/player-ui/ProgressCircle.tsx new file mode 100644 index 000000000..6b1945080 --- /dev/null +++ b/frontend/app/player-ui/ProgressCircle.tsx @@ -0,0 +1,16 @@ +import React, { memo } from 'react'; +import cn from 'classnames'; +import styles from './styles.module.css'; + +interface IProps { + preview?: boolean; + isGreen?: boolean; +} + +export const ProgressCircle = memo(({ preview, isGreen }: IProps) => ( + <div + className={cn(styles.positionTracker, { [styles.greenTracker]: isGreen })} + role={preview ? 'BoxPreview' : 'Box'} + /> + ) +) \ No newline at end of file diff --git a/frontend/app/player-ui/SkipButton.tsx b/frontend/app/player-ui/SkipButton.tsx new file mode 100644 index 000000000..1206bc421 --- /dev/null +++ b/frontend/app/player-ui/SkipButton.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import { Icon } from 'UI' +import cn from 'classnames' + +interface IProps { + size: number; + onClick: () => void; + isBackwards?: boolean; + customClasses: string; +} + +export function SkipButton({ size = 18, onClick, isBackwards, customClasses }: IProps) { + + return ( + <div + onClick={onClick} + className={cn('py-1 px-2 hover-main cursor-pointer bg-gray-lightest', customClasses)} + style={{ transform: isBackwards ? 'rotate(180deg)' : '' }} + > + <Icon + name="skip-forward-fill" + size={size} + color="inherit" + /> + </div> + ) +} \ No newline at end of file diff --git a/frontend/app/player-ui/index.tsx b/frontend/app/player-ui/index.tsx new file mode 100644 index 000000000..0a79c4aff --- /dev/null +++ b/frontend/app/player-ui/index.tsx @@ -0,0 +1,6 @@ +export { PlayButton, PlayingState } from './PlayButton' +export { SkipButton } from './SkipButton' +export { FullScreenButton } from './FullScreenButton' +export { PlayTime } from './PlayTime' +export { ProgressBar } from './ProgressBar' +export { ProgressCircle } from './ProgressCircle' \ No newline at end of file diff --git a/frontend/app/player-ui/styles.module.css b/frontend/app/player-ui/styles.module.css new file mode 100644 index 000000000..611cbf756 --- /dev/null +++ b/frontend/app/player-ui/styles.module.css @@ -0,0 +1,176 @@ +.positionTracker { + width: 15px; + height: 15px; + box-shadow: 0 0 0 1px #2331A8; + margin-left: -7px; + border-radius: 50%; + background-color: $main; + position: absolute; + left: 0; + z-index: 98; + top: 3px; + transition: all 0.2s ease-out; +&:hover, +&:focus { + transition: all 0.1s ease-in; + width: 20px; + height: 20px; + top: 1px; + left: -2px; + } + +} + +.greenTracker { + background-color: #42AE5E!important; + box-shadow: 0 0 0 1px #42AE5E; +} + +.progress { + height: 10px; + padding: 8px 0; + cursor: pointer; + width: 100%; + max-width: 100%; + position: relative; + display: flex; + align-items: center; + +} + + +.skipInterval { + position: absolute; + top: 3px; + height: 10px; + bottom: 0; + background: repeating-linear-gradient( 125deg, #efefef, #efefef 3px, #ddd 3px, #efefef 5px ); + pointer-events: none; + z-index: 2; +} + + +.event { + position: absolute; + width: 2px; + height: 10px; + background: $main; + z-index: 3; + pointer-events: none; +} + +.redEvent { + position: absolute; + width: 2px; + height: 10px; + background: $red; + z-index: 3; + pointer-events: none; +} + +.markup { + position: absolute; + width: 2px; + height: 8px; + margin-left: -8px; +&:hover { + z-index: 9999; + } +} + +.markup.info { + background: $blue2; +} + +.popup { + max-width: 300px !important; + overflow: hidden; + text-overflow: ellipsis; +& span { + display: block; + max-height: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +.timeline { + overflow: hidden; + position: absolute; + left: 0; + right: 0; + height: 10px; + border: 1px solid $gray-light; + display: flex; + align-items: center; +} + +.clickRage { + position: absolute; + width: 2px; + height: 8px; + margin-left: -1px; +} + +.returningLocation { + position: absolute; + height: 20%; + border-radius: 50%; + width: 12px; +} + +.feedbackIcon { + position: absolute; + margin-top: -20px; + margin-left: -9px; + background-color: $gray-lightest; + padding: 2px; + box-shadow: 0px 0px 0px 1px rgba(0,0,0,0.1); + +& .tooltipArrow { + width: 50px; + height: 25px; + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + overflow: hidden; +&::after { + content: ""; + position: absolute; + width: 6px; + height: 6px; + background: $gray-lightest; + transform: translateX(-50%) translateY(50%) rotate(45deg); + bottom: 100%; + left: 50%; + box-shadow: 0px 0px 0px 1px rgba(0,0,0,0.1); + } +} +} + +.timeTooltip { + position: absolute; + padding: 0.25rem; + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + background: black; + top: -35px; + color: white; + +&:after { + content:''; + position: absolute; + top: 100%; + left: 0; + right: 0; + margin: 0 auto; + width: 0; + height: 0; + border-top: solid 5px black; + border-left: solid 5px transparent; + border-right: solid 5px transparent; + } +} diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/Marker.js b/frontend/app/player/MessageDistributor/StatedScreen/Screen/Marker.js deleted file mode 100644 index ff1476ac3..000000000 --- a/frontend/app/player/MessageDistributor/StatedScreen/Screen/Marker.js +++ /dev/null @@ -1,135 +0,0 @@ -import styles from './marker.module.css'; - -function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') -} - -function escapeHtml(string) { - return string.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); -} - -function safeString(string) { - return (escapeHtml(escapeRegExp(string))) -} - -export default class Marker { - _target = null; - _selector = null; - _tooltip = null; - - constructor(overlay, screen) { - this.screen = screen; - - this._tooltip = document.createElement('div'); - this._tooltip.className = styles.tooltip; - this._tooltip.appendChild(document.createElement('div')); - - const htmlStr = document.createElement('div'); - htmlStr.innerHTML = '<b>Right-click > Inspect</b> for more details.'; - this._tooltip.appendChild(htmlStr); - - const marker = document.createElement('div'); - marker.className = styles.marker; - const markerL = document.createElement('div'); - const markerR = document.createElement('div'); - const markerT = document.createElement('div'); - const markerB = document.createElement('div'); - markerL.className = styles.markerL; - markerR.className = styles.markerR; - markerT.className = styles.markerT; - markerB.className = styles.markerB; - marker.appendChild(markerL); - marker.appendChild(markerR); - marker.appendChild(markerT); - marker.appendChild(markerB); - - marker.appendChild(this._tooltip); - - overlay.appendChild(marker); - this._marker = marker; - } - - get target() { - return this._target; - } - - mark(element) { - if (this._target === element) { - return; - } - this._target = element; - this._selector = null; - this.redraw(); - } - - unmark() { - this.mark(null); - } - - _autodefineTarget() { - // TODO: put to Screen - if (this._selector) { - try { - const fitTargets = this.screen.document.querySelectorAll(this._selector); - if (fitTargets.length === 0) { - this._target = null; - } else { - this._target = fitTargets[0]; - const cursorTarget = this.screen.getCursorTarget(); - fitTargets.forEach((target) => { - if (target.contains(cursorTarget)) { - this._target = target; - } - }); - } - } catch (e) { - console.info(e); - } - } else { - this._target = null; - } - } - - markBySelector(selector) { - this._selector = selector; - this._autodefineTarget(); - this.redraw(); - } - - getTagString(tag) { - const attrs = tag.attributes; - let str = `<span style="color:#9BBBDC">${tag.tagName.toLowerCase()}</span>`; - - for (let i = 0; i < attrs.length; i++) { - let k = attrs[i]; - const attribute = k.name; - if (attribute === 'class') { - str += `<span style="color:#F29766">${'.' + safeString(k.value).split(' ').join('.')}</span>`; - } - - if (attribute === 'id') { - str += `<span style="color:#F29766">${'#' + safeString(k.value).split(' ').join('#')}</span>`; - } - } - - return str; - } - - redraw() { - if (this._selector) { - this._autodefineTarget(); - } - if (!this._target) { - this._marker.style.display = 'none'; - return; - } - const rect = this._target.getBoundingClientRect(); - this._marker.style.display = 'block'; - this._marker.style.left = rect.left + 'px'; - this._marker.style.top = rect.top + 'px'; - this._marker.style.width = rect.width + 'px'; - this._marker.style.height = rect.height + 'px'; - - this._tooltip.firstChild.innerHTML = this.getTagString(this._target); - } -} diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/Screen.ts b/frontend/app/player/MessageDistributor/StatedScreen/Screen/Screen.ts deleted file mode 100644 index a0ae8a800..000000000 --- a/frontend/app/player/MessageDistributor/StatedScreen/Screen/Screen.ts +++ /dev/null @@ -1,84 +0,0 @@ -import Marker from './Marker'; -import Cursor from './Cursor'; -import Inspector from './Inspector'; -// import styles from './screen.module.css'; -// import { getState } from '../../../store'; -import BaseScreen from './BaseScreen'; - -export { INITIAL_STATE } from './BaseScreen'; -export type { State } from './BaseScreen'; - -export default class Screen extends BaseScreen { - public readonly cursor: Cursor; - private substitutor: BaseScreen | null = null; - private inspector: Inspector | null = null; - private marker: Marker | null = null; - constructor() { - super(); - this.cursor = new Cursor(this.overlay); - } - - getCursorTarget() { - return this.getElementFromInternalPoint(this.cursor.getPosition()); - } - - getCursorTargets() { - return this.getElementsFromInternalPoint(this.cursor.getPosition()); - } - - _scale() { - super._scale(); - if (this.substitutor) { - this.substitutor._scale(); - } - } - - enableInspector(clickCallback: ({ target: Element }) => void): Document | null { - if (!this.parentElement) return null; - if (!this.substitutor) { - this.substitutor = new Screen(); - this.marker = new Marker(this.substitutor.overlay, this.substitutor); - this.inspector = new Inspector(this.substitutor, this.marker); - //this.inspector.addClickListener(clickCallback, true); - this.substitutor.attach(this.parentElement); - } - - this.substitutor.display(false); - - const docElement = this.document?.documentElement; // this.substitutor.document?.importNode( - const doc = this.substitutor.document; - if (doc && docElement) { - // doc.documentElement.innerHTML = ""; - // // Better way? - // for (let i = 1; i < docElement.attributes.length; i++) { - // const att = docElement.attributes[i]; - // doc.documentElement.setAttribute(att.name, att.value); - // } - // for (let i = 1; i < docElement.childNodes.length; i++) { - // doc.documentElement.appendChild(docElement.childNodes[i].cloneNode(true)); - // } - doc.open(); - doc.write(docElement.outerHTML); // Context will be iframe, so instanceof Element won't work - doc.close(); - - // TODO! : copy stylesheets, check with styles - } - this.display(false); - this.inspector.toggle(true, clickCallback); - this.substitutor.display(true); - return doc; - } - - disableInspector() { - if (this.substitutor) { - const doc = this.substitutor.document; - if (doc) { - doc.documentElement.innerHTML = ""; - } - this.inspector.toggle(false); - this.substitutor.display(false); - } - this.display(true); - } - -} \ No newline at end of file diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/index.js b/frontend/app/player/MessageDistributor/StatedScreen/Screen/index.js deleted file mode 100644 index 96f315c68..000000000 --- a/frontend/app/player/MessageDistributor/StatedScreen/Screen/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './Screen'; -export * from './Screen'; diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/types.ts b/frontend/app/player/MessageDistributor/StatedScreen/Screen/types.ts deleted file mode 100644 index 0fdac4d8b..000000000 --- a/frontend/app/player/MessageDistributor/StatedScreen/Screen/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface Point { - x: number; - y: number; -} \ No newline at end of file diff --git a/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts b/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts deleted file mode 100644 index 45028f88f..000000000 --- a/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts +++ /dev/null @@ -1,152 +0,0 @@ -import Screen, { INITIAL_STATE as SUPER_INITIAL_STATE, State as SuperState } from './Screen/Screen'; -import { update, getState } from '../../store'; - -import type { Point } from './Screen/types'; - -function getOffset(el: Element, innerWindow: Window) { - const rect = el.getBoundingClientRect(); - return { - fixedLeft: rect.left + innerWindow.scrollX, - fixedTop: rect.top + innerWindow.scrollY, - rect, - }; -} - -//export interface targetPosition - -interface BoundingRect { - top: number, - left: number, - width: number, - height: number, -} - -export interface MarkedTarget { - boundingRect: BoundingRect, - el: Element, - selector: string, - count: number, - index: number, - active?: boolean, - percent: number -} - -export interface State extends SuperState { - messagesLoading: boolean, - cssLoading: boolean, - markedTargets: MarkedTarget[] | null, - activeTargetIndex: number, -} - -export const INITIAL_STATE: State = { - ...SUPER_INITIAL_STATE, - messagesLoading: false, - cssLoading: false, - markedTargets: null, - activeTargetIndex: 0 -}; - -export default class StatedScreen extends Screen { - constructor() { super(); } - - setMessagesLoading(messagesLoading: boolean) { - this.display(!messagesLoading); - update({ messagesLoading }); - } - - setCSSLoading(cssLoading: boolean) { - this.displayFrame(!cssLoading); - update({ cssLoading }); - } - - setSize({ height, width }: { height: number, width: number }) { - update({ width, height }); - this.scale(); - this.updateMarketTargets() - } - - updateMarketTargets() { - const { markedTargets } = getState(); - if (markedTargets) { - update({ - markedTargets: markedTargets.map((mt: any) => ({ - ...mt, - boundingRect: this.calculateRelativeBoundingRect(mt.el), - })), - }); - } - } - - private calculateRelativeBoundingRect(el: Element): BoundingRect { - if (!this.parentElement) return {top:0, left:0, width:0,height:0} //TODO - const { top, left, width, height } = el.getBoundingClientRect(); - const s = this.getScale(); - const scrinRect = this.screen.getBoundingClientRect(); - const parentRect = this.parentElement.getBoundingClientRect(); - - return { - top: top*s + scrinRect.top - parentRect.top, - left: left*s + scrinRect.left - parentRect.left, - width: width*s, - height: height*s, - } - } - - setActiveTarget(index: number) { - const window = this.window - const markedTargets: MarkedTarget[] | null = getState().markedTargets - const target = markedTargets && markedTargets[index] - if (target && window) { - const { fixedTop, rect } = getOffset(target.el, window) - const scrollToY = fixedTop - window.innerHeight / 1.5 - if (rect.top < 0 || rect.top > window.innerHeight) { - // behavior hack TODO: fix it somehow when they will decide to remove it from browser api - // @ts-ignore - window.scrollTo({ top: scrollToY, behavior: 'instant' }) - setTimeout(() => { - if (!markedTargets) { return } - update({ - markedTargets: markedTargets.map(t => t === target ? { - ...target, - boundingRect: this.calculateRelativeBoundingRect(target.el), - } : t) - }) - }, 0) - } - - } - update({ activeTargetIndex: index }); - } - - private actualScroll: Point | null = null - setMarkedTargets(selections: { selector: string, count: number }[] | null) { - if (selections) { - const totalCount = selections.reduce((a, b) => { - return a + b.count - }, 0); - const markedTargets: MarkedTarget[] = []; - let index = 0; - selections.forEach((s) => { - const el = this.getElementBySelector(s.selector); - if (!el) return; - markedTargets.push({ - ...s, - el, - index: index++, - percent: Math.round((s.count * 100) / totalCount), - boundingRect: this.calculateRelativeBoundingRect(el), - count: s.count, - }) - }); - - this.actualScroll = this.getCurrentScroll() - update({ markedTargets }); - } else { - if (this.actualScroll) { - this.window?.scrollTo(this.actualScroll.x, this.actualScroll.y) - this.actualScroll = null - } - update({ markedTargets: null }); - } - } -} diff --git a/frontend/app/player/MessageDistributor/StatedScreen/index.ts b/frontend/app/player/MessageDistributor/StatedScreen/index.ts deleted file mode 100644 index 0955acffb..000000000 --- a/frontend/app/player/MessageDistributor/StatedScreen/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './StatedScreen'; -export * from './StatedScreen'; \ No newline at end of file diff --git a/frontend/app/player/MessageDistributor/index.js b/frontend/app/player/MessageDistributor/index.js deleted file mode 100644 index 8502aee50..000000000 --- a/frontend/app/player/MessageDistributor/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './MessageDistributor'; -export * from './MessageDistributor'; diff --git a/frontend/app/player/MessageDistributor/managers/AssistManager.ts b/frontend/app/player/MessageDistributor/managers/AssistManager.ts deleted file mode 100644 index 897d8dfba..000000000 --- a/frontend/app/player/MessageDistributor/managers/AssistManager.ts +++ /dev/null @@ -1,599 +0,0 @@ -import type { Socket } from 'socket.io-client'; -import type Peer from 'peerjs'; -import type { MediaConnection } from 'peerjs'; -import type MessageDistributor from '../MessageDistributor'; -import store from 'App/store'; -import type { LocalStream } from './LocalStream'; -import { update, getState } from '../../store'; -// import { iceServerConfigFromString } from 'App/utils' -import AnnotationCanvas from './AnnotationCanvas'; -import MStreamReader from '../messages/MStreamReader'; -import JSONRawMessageReader from '../messages/JSONRawMessageReader' - -export enum CallingState { - NoCall, - Connecting, - Requesting, - Reconnecting, - OnCall, -} - -export enum ConnectionStatus { - Connecting, - WaitingMessages, - Connected, - Inactive, - Disconnected, - Error, - Closed, -} - -export enum RemoteControlStatus { - Disabled = 0, - Requesting, - Enabled, -} - - -export function getStatusText(status: ConnectionStatus): string { - switch(status) { - case ConnectionStatus.Closed: - return 'Closed...'; - case ConnectionStatus.Connecting: - return "Connecting..."; - case ConnectionStatus.Connected: - return ""; - case ConnectionStatus.Inactive: - return "Client tab is inactive"; - case ConnectionStatus.Disconnected: - return "Disconnected"; - case ConnectionStatus.Error: - return "Something went wrong. Try to reload the page."; - case ConnectionStatus.WaitingMessages: - return "Connected. Waiting for the data... (The tab might be inactive)" - } -} - -export interface State { - calling: CallingState; - peerConnectionStatus: ConnectionStatus; - remoteControl: RemoteControlStatus; - annotating: boolean; - assistStart: number; -} - -export const INITIAL_STATE: State = { - calling: CallingState.NoCall, - peerConnectionStatus: ConnectionStatus.Connecting, - remoteControl: RemoteControlStatus.Disabled, - annotating: false, - assistStart: 0, -} - -const MAX_RECONNECTION_COUNT = 4; - -export default class AssistManager { - private videoStreams: Record<string, MediaStreamTrack> = {} - - // TODO: Session type - constructor(private session: any, private md: MessageDistributor, private config: any) {} - - private setStatus(status: ConnectionStatus) { - if (getState().peerConnectionStatus === ConnectionStatus.Disconnected && - status !== ConnectionStatus.Connected) { - return - } - - if (status === ConnectionStatus.Connecting) { - this.md.setMessagesLoading(true); - } else { - this.md.setMessagesLoading(false); - } - if (status === ConnectionStatus.Connected) { - this.md.display(true); - } else { - this.md.display(false); - } - update({ peerConnectionStatus: status }); - } - - private get peerID(): string { - return `${this.session.projectKey}-${this.session.sessionId}` - } - - private socketCloseTimeout: ReturnType<typeof setTimeout> | undefined - private onVisChange = () => { - this.socketCloseTimeout && clearTimeout(this.socketCloseTimeout) - if (document.hidden) { - this.socketCloseTimeout = setTimeout(() => { - const state = getState() - if (document.hidden && - (state.calling === CallingState.NoCall && state.remoteControl === RemoteControlStatus.Enabled)) { - this.socket?.close() - } - }, 30000) - } else { - this.socket?.open() - } - } - - private socket: Socket | null = null - connect(agentToken: string) { - const jmr = new JSONRawMessageReader() - const reader = new MStreamReader(jmr, this.session.startedAt) - let waitingForMessages = true - let disconnectTimeout: ReturnType<typeof setTimeout> | undefined - let inactiveTimeout: ReturnType<typeof setTimeout> | undefined - function clearDisconnectTimeout() { - disconnectTimeout && clearTimeout(disconnectTimeout) - disconnectTimeout = undefined - } - function clearInactiveTimeout() { - inactiveTimeout && clearTimeout(inactiveTimeout) - inactiveTimeout = undefined - } - - const now = +new Date() - update({ assistStart: now }) - - import('socket.io-client').then(({ default: io }) => { - if (this.cleaned) { return } - if (this.socket) { this.socket.close() } // TODO: single socket connection - // @ts-ignore - const urlObject = new URL(window.env.API_EDP || window.location.origin) // does it handle ssl automatically? - - const socket: Socket = this.socket = io(urlObject.origin, { - path: '/ws-assist/socket', - auth: { - token: agentToken - }, - query: { - peerId: this.peerID, - identity: "agent", - agentInfo: JSON.stringify({ - ...this.session.agentInfo, - query: document.location.search - }) - } - }) - socket.on("connect", () => { - waitingForMessages = true - this.setStatus(ConnectionStatus.WaitingMessages) // TODO: happens frequently on bad network - }) - socket.on("disconnect", () => { - this.toggleRemoteControl(false) - update({ calling: CallingState.NoCall }) - }) - socket.on('messages', messages => { - jmr.append(messages) // as RawMessage[] - - if (waitingForMessages) { - waitingForMessages = false // TODO: more explicit - this.setStatus(ConnectionStatus.Connected) - - // Call State - if (getState().calling === CallingState.Reconnecting) { - this._callSessionPeer() // reconnecting call (todo improve code separation) - } - } - - for (let msg = reader.readNext();msg !== null;msg = reader.readNext()) { - // @ts-ignore TODO: fix msg types in generator - this.md.appendMessage(msg, msg._index) - } - }) - socket.on("control_granted", id => { - this.toggleRemoteControl(id === socket.id) - }) - socket.on("control_rejected", id => { - id === socket.id && this.toggleRemoteControl(false) - }) - socket.on('SESSION_RECONNECTED', () => { - clearDisconnectTimeout() - clearInactiveTimeout() - this.setStatus(ConnectionStatus.Connected) - }) - - socket.on('UPDATE_SESSION', ({ active }) => { - clearDisconnectTimeout() - !inactiveTimeout && this.setStatus(ConnectionStatus.Connected) - if (typeof active === "boolean") { - clearInactiveTimeout() - if (active) { - this.setStatus(ConnectionStatus.Connected) - } else { - inactiveTimeout = setTimeout(() => this.setStatus(ConnectionStatus.Inactive), 5000) - } - } - }) - socket.on('videofeed', ({ streamId, enabled }) => { - console.log(streamId, enabled) - console.log(this.videoStreams) - if (this.videoStreams[streamId]) { - this.videoStreams[streamId].enabled = enabled - } - console.log(this.videoStreams) - }) - socket.on('SESSION_DISCONNECTED', e => { - waitingForMessages = true - clearDisconnectTimeout() - disconnectTimeout = setTimeout(() => { - if (this.cleaned) { return } - this.setStatus(ConnectionStatus.Disconnected) - }, 30000) - - if (getState().remoteControl === RemoteControlStatus.Requesting) { - this.toggleRemoteControl(false) // else its remaining - } - - // Call State - if (getState().calling === CallingState.OnCall) { - update({ calling: CallingState.Reconnecting }) - } else if (getState().calling === CallingState.Requesting){ - update({ calling: CallingState.NoCall }) - } - }) - socket.on('error', e => { - console.warn("Socket error: ", e ) - this.setStatus(ConnectionStatus.Error); - this.toggleRemoteControl(false) - }) - socket.on('call_end', this.onRemoteCallEnd) - - document.addEventListener('visibilitychange', this.onVisChange) - - }) - } - - /* ==== Remote Control ==== */ - - private onMouseMove = (e: MouseEvent): void => { - if (!this.socket) { return } - const data = this.md.getInternalCoordinates(e) - this.socket.emit("move", [ data.x, data.y ]) - } - - private onWheel = (e: WheelEvent): void => { - e.preventDefault() - if (!this.socket) { return } - //throttling makes movements less smooth, so it is omitted - //this.onMouseMove(e) - this.socket.emit("scroll", [ e.deltaX, e.deltaY ]) - } - - private onMouseClick = (e: MouseEvent): void => { - if (!this.socket) { return; } - if (getState().annotating) { return; } // ignore clicks while annotating - - const data = this.md.getInternalViewportCoordinates(e) - // const el = this.md.getElementFromPoint(e); // requires requestiong node_id from domManager - const el = this.md.getElementFromInternalPoint(data) - if (el instanceof HTMLElement) { - el.focus() - el.oninput = e => { - if (el instanceof HTMLTextAreaElement - || el instanceof HTMLInputElement - ) { - this.socket && this.socket.emit("input", el.value) - } else if (el.isContentEditable) { - this.socket && this.socket.emit("input", el.innerText) - } - } - // TODO: send "focus" event to assist with the nodeID - el.onkeydown = e => { - if (e.key == "Tab") { - e.preventDefault() - } - } - el.onblur = () => { - el.oninput = null - el.onblur = null - } - } - this.socket.emit("click", [ data.x, data.y ]); - } - - private toggleRemoteControl(enable: boolean){ - if (enable) { - this.md.overlay.addEventListener("mousemove", this.onMouseMove) - this.md.overlay.addEventListener("click", this.onMouseClick) - this.md.overlay.addEventListener("wheel", this.onWheel) - this.md.toggleRemoteControlStatus(true) - update({ remoteControl: RemoteControlStatus.Enabled }) - } else { - this.md.overlay.removeEventListener("mousemove", this.onMouseMove) - this.md.overlay.removeEventListener("click", this.onMouseClick) - this.md.overlay.removeEventListener("wheel", this.onWheel) - this.md.toggleRemoteControlStatus(false) - update({ remoteControl: RemoteControlStatus.Disabled }) - this.toggleAnnotation(false) - } - } - - requestReleaseRemoteControl = () => { - if (!this.socket) { return } - const remoteControl = getState().remoteControl - if (remoteControl === RemoteControlStatus.Requesting) { return } - if (remoteControl === RemoteControlStatus.Disabled) { - update({ remoteControl: RemoteControlStatus.Requesting }) - this.socket.emit("request_control", JSON.stringify({ - ...this.session.agentInfo, - query: document.location.search - })) - // setTimeout(() => { - // if (getState().remoteControl !== RemoteControlStatus.Requesting) { return } - // this.socket?.emit("release_control") - // update({ remoteControl: RemoteControlStatus.Disabled }) - // }, 8000) - } else { - this.socket.emit("release_control") - this.toggleRemoteControl(false) - } - } - - releaseRemoteControl = () => { - if (!this.socket) { return } - this.socket.emit("release_control") - this.toggleRemoteControl(false) - } - - - /* ==== PeerJS Call ==== */ - - private _peer: Peer | null = null - private connectionAttempts: number = 0 - private callConnection: MediaConnection[] = [] - private getPeer(): Promise<Peer> { - if (this._peer && !this._peer.disconnected) { return Promise.resolve(this._peer) } - - // @ts-ignore - const urlObject = new URL(window.env.API_EDP || window.location.origin) - return import('peerjs').then(({ default: Peer }) => { - if (this.cleaned) {return Promise.reject("Already cleaned")} - const peerOpts: any = { - host: urlObject.hostname, - path: '/assist', - port: urlObject.port === "" ? (location.protocol === 'https:' ? 443 : 80 ): parseInt(urlObject.port), - } - if (this.config) { - peerOpts['config'] = { - iceServers: this.config, - sdpSemantics: 'unified-plan', - iceTransportPolicy: 'relay', - }; - } - const peer = this._peer = new Peer(peerOpts) - peer.on('call', call => { - console.log('getting call from', call.peer) - call.answer(this.callArgs.localStream.stream) - this.callConnection.push(call) - - this.callArgs.localStream.onVideoTrack(vTrack => { - const sender = call.peerConnection.getSenders().find(s => s.track?.kind === "video") - if (!sender) { - console.warn("No video sender found") - return - } - sender.replaceTrack(vTrack) - }) - - call.on('stream', stream => { - this.videoStreams[call.peer] = stream.getVideoTracks()[0] - this.callArgs && this.callArgs.onStream(stream) - }); - // call.peerConnection.addEventListener("track", e => console.log('newtrack',e.track)) - - call.on("close", this.onRemoteCallEnd) - call.on("error", (e) => { - console.error("PeerJS error (on call):", e) - this.initiateCallEnd(); - this.callArgs && this.callArgs.onError && this.callArgs.onError(); - }); - }) - peer.on('error', e => { - if (e.type === 'disconnected') { - return peer.reconnect() - } else if (e.type !== 'peer-unavailable') { - console.error(`PeerJS error (on peer). Type ${e.type}`, e); - } - - //call-reconnection connected - // if (['peer-unavailable', 'network', 'webrtc'].includes(e.type)) { - // this.setStatus(this.connectionAttempts++ < MAX_RECONNECTION_COUNT - // ? ConnectionStatus.Connecting - // : ConnectionStatus.Disconnected); - // Reconnect... - }) - - return new Promise(resolve => { - peer.on("open", () => resolve(peer)) - }) - }); - - } - - - private handleCallEnd() { - this.callArgs && this.callArgs.onCallEnd() - this.callConnection[0] && this.callConnection[0].close() - update({ calling: CallingState.NoCall }) - this.callArgs = null - this.toggleAnnotation(false) - } - - public initiateCallEnd = async () => { - this.socket?.emit("call_end", store.getState().getIn([ 'user', 'account', 'name'])) - this.handleCallEnd() - const remoteControl = getState().remoteControl - if (remoteControl === RemoteControlStatus.Enabled) { - this.socket.emit("release_control") - this.toggleRemoteControl(false) - } - } - - private onRemoteCallEnd = () => { - if (getState().calling === CallingState.Requesting) { - this.callArgs && this.callArgs.onReject() - this.callConnection[0] && this.callConnection[0].close() - update({ calling: CallingState.NoCall }) - this.callArgs = null - this.toggleAnnotation(false) - } else { - this.handleCallEnd() - } - } - - private callArgs: { - localStream: LocalStream, - onStream: (s: MediaStream)=>void, - onCallEnd: () => void, - onReject: () => void, - onError?: ()=> void, - } | null = null - - public setCallArgs( - localStream: LocalStream, - onStream: (s: MediaStream)=>void, - onCallEnd: () => void, - onReject: () => void, - onError?: ()=> void, - ) { - this.callArgs = { - localStream, - onStream, - onCallEnd, - onReject, - onError, - } - } - - public call(thirdPartyPeers?: string[]): { end: Function } { - if (thirdPartyPeers && thirdPartyPeers.length > 0) { - this.addPeerCall(thirdPartyPeers) - } else { - this._callSessionPeer() - } - return { - end: this.initiateCallEnd, - } - } - - /** Connecting to the other agents that are already - * in the call with the user - */ - public addPeerCall(thirdPartyPeers: string[]) { - thirdPartyPeers.forEach(peer => this._peerConnection(peer)) - } - - /** Connecting to the app user */ - private _callSessionPeer() { - if (![CallingState.NoCall, CallingState.Reconnecting].includes(getState().calling)) { return } - update({ calling: CallingState.Connecting }) - this._peerConnection(this.peerID); - this.socket && this.socket.emit("_agent_name", store.getState().getIn([ 'user', 'account', 'name'])) - } - - private async _peerConnection(remotePeerId: string) { - try { - const peer = await this.getPeer(); - const call = peer.call(remotePeerId, this.callArgs.localStream.stream) - this.callConnection.push(call) - - this.callArgs.localStream.onVideoTrack(vTrack => { - const sender = call.peerConnection.getSenders().find(s => s.track?.kind === "video") - if (!sender) { - console.warn("No video sender found") - return - } - sender.replaceTrack(vTrack) - }) - - call.on('stream', stream => { - getState().calling !== CallingState.OnCall && update({ calling: CallingState.OnCall }) - - this.videoStreams[call.peer] = stream.getVideoTracks()[0] - - this.callArgs && this.callArgs.onStream(stream) - }); - // call.peerConnection.addEventListener("track", e => console.log('newtrack',e.track)) - - call.on("close", this.onRemoteCallEnd) - call.on("error", (e) => { - console.error("PeerJS error (on call):", e) - this.initiateCallEnd(); - this.callArgs && this.callArgs.onError && this.callArgs.onError(); - }); - } catch (e) { - console.error(e) - } - } - - toggleAnnotation(enable?: boolean) { - // if (getState().calling !== CallingState.OnCall) { return } - if (typeof enable !== "boolean") { - enable = !!getState().annotating - } - if (enable && !this.annot) { - const annot = this.annot = new AnnotationCanvas() - annot.mount(this.md.overlay) - annot.canvas.addEventListener("mousedown", e => { - if (!this.socket) { return } - const data = this.md.getInternalViewportCoordinates(e) - annot.start([ data.x, data.y ]) - this.socket.emit("startAnnotation", [ data.x, data.y ]) - }) - annot.canvas.addEventListener("mouseleave", () => { - if (!this.socket) { return } - annot.stop() - this.socket.emit("stopAnnotation") - }) - annot.canvas.addEventListener("mouseup", () => { - if (!this.socket) { return } - annot.stop() - this.socket.emit("stopAnnotation") - }) - annot.canvas.addEventListener("mousemove", e => { - if (!this.socket || !annot.isPainting()) { return } - - const data = this.md.getInternalViewportCoordinates(e) - annot.move([ data.x, data.y ]) - this.socket.emit("moveAnnotation", [ data.x, data.y ]) - }) - update({ annotating: true }) - } else if (!enable && !!this.annot) { - this.annot.remove() - this.annot = null - update({ annotating: false }) - } - } - - toggleVideoLocalStream(enabled: boolean) { - this.getPeer().then((peer) => { - this.socket.emit('videofeed', { streamId: peer.id, enabled }) - }) - } - - private annot: AnnotationCanvas | null = null - - /* ==== Cleaning ==== */ - private cleaned: boolean = false - clear() { - this.cleaned = true // sometimes cleaned before modules loaded - this.initiateCallEnd(); - if (this._peer) { - console.log("destroying peer...") - const peer = this._peer; // otherwise it calls reconnection on data chan close - this._peer = null; - peer.disconnect(); - peer.destroy(); - } - if (this.socket) { - this.socket.close() - document.removeEventListener('visibilitychange', this.onVisChange) - } - if (this.annot) { - this.annot.remove() - this.annot = null - } - } -} diff --git a/frontend/app/player/MessageDistributor/managers/MouseMoveManager.ts b/frontend/app/player/MessageDistributor/managers/MouseMoveManager.ts deleted file mode 100644 index ca9e3b740..000000000 --- a/frontend/app/player/MessageDistributor/managers/MouseMoveManager.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type StatedScreen from '../StatedScreen'; -import type { MouseMove } from '../messages'; - -import ListWalker from './ListWalker'; - -const HOVER_CLASS = "-openreplay-hover"; -const HOVER_CLASS_DEPR = "-asayer-hover"; - -export default class MouseMoveManager extends ListWalker<MouseMove> { - private hoverElements: Array<Element> = []; - - constructor(private screen: StatedScreen) {super()} - - private updateHover(): void { - const curHoverElements = this.screen.getCursorTargets(); - const diffAdd = curHoverElements.filter(elem => !this.hoverElements.includes(elem)); - const diffRemove = this.hoverElements.filter(elem => !curHoverElements.includes(elem)); - this.hoverElements = curHoverElements; - diffAdd.forEach(elem => { - elem.classList.add(HOVER_CLASS) - elem.classList.add(HOVER_CLASS_DEPR) - }); - diffRemove.forEach(elem => { - elem.classList.remove(HOVER_CLASS) - elem.classList.remove(HOVER_CLASS_DEPR) - }); - } - - reset(): void { - this.hoverElements = []; - } - - move(t: number) { - const lastMouseMove = this.moveGetLast(t); - if (!!lastMouseMove){ - this.screen.cursor.move(lastMouseMove); - //window.getComputedStyle(this.screen.getCursorTarget()).cursor === 'pointer' // might nfluence performance though - this.updateHover(); - } - } -} diff --git a/frontend/app/player/MessageDistributor/managers/ReduxStateManager.ts b/frontend/app/player/MessageDistributor/managers/ReduxStateManager.ts deleted file mode 100644 index 81638994e..000000000 --- a/frontend/app/player/MessageDistributor/managers/ReduxStateManager.ts +++ /dev/null @@ -1,53 +0,0 @@ -// import { applyChange, revertChange } from 'deep-diff'; -// import ListWalker from './ListWalker'; -// import type { Redux } from '../messages'; - -// export default class ReduxStateManager extends ListWalker<Redux> { -// private state: Object = {} -// private finalStates: Object[] = [] - -// moveWasUpdated(time, index) { -// super.moveApply( -// time, -// this.onIncrement, -// this.onDecrement, -// ) -// } - -// onIncrement = (item) => { -// this.processRedux(item, true); -// } - -// onDecrement = (item) => { -// this.processRedux(item, false); -// } - -// private processRedux(action, forward) { -// if (forward) { -// if (!!action.state) { -// this.finalStates.push(this.state); -// this.state = JSON.parse(JSON.stringify(action.state)); // Deep clone :( -// } else { -// action.diff.forEach(d => { -// try { -// applyChange(this.state, d); -// } catch (e) { -// //console.warn("Deepdiff error") -// } -// }); -// } -// } else { -// if (!!action.state) { -// this.state = this.finalStates.pop(); -// } else { -// action.diff.forEach(d => { -// try { -// revertChange(this.state, 1, d); // bad lib :( TODO: write our own diff -// } catch (e) { -// //console.warn("Deepdiff error") -// } -// }); -// } -// } -// } -// } diff --git a/frontend/app/player/MessageDistributor/messages/JSONRawMessageReader.ts b/frontend/app/player/MessageDistributor/messages/JSONRawMessageReader.ts deleted file mode 100644 index 33bf9d2c5..000000000 --- a/frontend/app/player/MessageDistributor/messages/JSONRawMessageReader.ts +++ /dev/null @@ -1,100 +0,0 @@ -import type { - RawMessage, - RawSetNodeAttributeURLBased, - RawSetNodeAttribute, - RawSetCssDataURLBased, - RawSetCssData, - RawCssInsertRuleURLBased, - RawCssInsertRule, - RawAdoptedSsInsertRuleURLBased, - RawAdoptedSsInsertRule, - RawAdoptedSsReplaceURLBased, - RawAdoptedSsReplace, -} from './raw' -import type { TrackerMessage } from './tracker' -import translate from './tracker' -import { TP_MAP } from './tracker-legacy' -import { resolveURL, resolveCSS } from './urlResolve' - - -function legacyTranslate(msg: any): RawMessage | null { - const type = TP_MAP[msg._id as keyof typeof TP_MAP] - if (!type) { - return null - } - msg.tp = type - delete msg._id - return msg as RawMessage -} - - -// TODO: commonURLBased logic for feilds -const resolvers = { - "set_node_attribute_url_based": (msg: RawSetNodeAttributeURLBased): RawSetNodeAttribute => - ({ - ...msg, - value: msg.name === 'src' || msg.name === 'href' - ? resolveURL(msg.baseURL, msg.value) - : (msg.name === 'style' - ? resolveCSS(msg.baseURL, msg.value) - : msg.value - ), - tp: "set_node_attribute", - }), - "set_css_data_url_based": (msg: RawSetCssDataURLBased): RawSetCssData => - ({ - ...msg, - data: resolveCSS(msg.baseURL, msg.data), - tp: "set_css_data", - }), - "css_insert_rule_url_based": (msg: RawCssInsertRuleURLBased): RawCssInsertRule => - ({ - ...msg, - rule: resolveCSS(msg.baseURL, msg.rule), - tp: "css_insert_rule", - }), - "adopted_ss_insert_rule_url_based": (msg: RawAdoptedSsInsertRuleURLBased): RawAdoptedSsInsertRule => - ({ - ...msg, - rule: resolveCSS(msg.baseURL, msg.rule), - tp: "adopted_ss_insert_rule", - }), - "adopted_ss_replace_url_based": (msg: RawAdoptedSsReplaceURLBased): RawAdoptedSsReplace => - ({ - ...msg, - text: resolveCSS(msg.baseURL, msg.text), - tp: "adopted_ss_replace" - }), -} as const - -type ResolvableType = keyof typeof resolvers -type ResolvableRawMessage = RawMessage & { tp: ResolvableType } - -function isResolvable(msg: RawMessage): msg is ResolvableRawMessage { - //@ts-ignore - return resolvers[msg.tp] !== undefined -} - - -export default class JSONRawMessageReader { - constructor(private messages: TrackerMessage[] = []){} - append(messages: TrackerMessage[]) { - this.messages = this.messages.concat(messages) - } - readMessage(): RawMessage | null { - let msg = this.messages.shift() - if (!msg) { return null } - const rawMsg = Array.isArray(msg) - ? translate(msg) - : legacyTranslate(msg) - if (!rawMsg) { - return this.readMessage() - } - if (isResolvable(rawMsg)) { - //@ts-ignore ??? too complex typscript... - return resolvers[rawMsg.tp](rawMsg) - } - return rawMsg - } - -} diff --git a/frontend/app/player/MessageDistributor/messages/index.ts b/frontend/app/player/MessageDistributor/messages/index.ts deleted file mode 100644 index 2619b58cd..000000000 --- a/frontend/app/player/MessageDistributor/messages/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './message' \ No newline at end of file diff --git a/frontend/app/player/MessageDistributor/messages/tracker-legacy.ts b/frontend/app/player/MessageDistributor/messages/tracker-legacy.ts deleted file mode 100644 index a6cfec8f0..000000000 --- a/frontend/app/player/MessageDistributor/messages/tracker-legacy.ts +++ /dev/null @@ -1,75 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ -// Auto-generated, do not edit - -export const TP_MAP = { - 81: "batch_metadata", - 82: "partitioned_message", - 0: "timestamp", - 4: "set_page_location", - 5: "set_viewport_size", - 6: "set_viewport_scroll", - 7: "create_document", - 8: "create_element_node", - 9: "create_text_node", - 10: "move_node", - 11: "remove_node", - 12: "set_node_attribute", - 13: "remove_node_attribute", - 14: "set_node_data", - 15: "set_css_data", - 16: "set_node_scroll", - 17: "set_input_target", - 18: "set_input_value", - 19: "set_input_checked", - 20: "mouse_move", - 22: "console_log", - 23: "page_load_timing", - 24: "page_render_timing", - 25: "js_exception_deprecated", - 27: "raw_custom_event", - 28: "user_id", - 29: "user_anonymous_id", - 30: "metadata", - 37: "css_insert_rule", - 38: "css_delete_rule", - 39: "fetch", - 40: "profiler", - 41: "o_table", - 42: "state_action", - 44: "redux", - 45: "vuex", - 46: "mob_x", - 47: "ng_rx", - 48: "graph_ql", - 49: "performance_track", - 53: "resource_timing", - 54: "connection_information", - 55: "set_page_visibility", - 57: "load_font_face", - 58: "set_node_focus", - 59: "long_task", - 60: "set_node_attribute_url_based", - 61: "set_css_data_url_based", - 63: "technical_info", - 64: "custom_issue", - 67: "css_insert_rule_url_based", - 69: "mouse_click", - 70: "create_i_frame_document", - 71: "adopted_ss_replace_url_based", - 72: "adopted_ss_replace", - 73: "adopted_ss_insert_rule_url_based", - 74: "adopted_ss_insert_rule", - 75: "adopted_ss_delete_rule", - 76: "adopted_ss_add_owner", - 77: "adopted_ss_remove_owner", - 79: "zustand", - 78: "js_exception", - 90: "ios_session_start", - 93: "ios_custom_event", - 96: "ios_screen_changes", - 100: "ios_click_event", - 102: "ios_performance_event", - 103: "ios_log", - 105: "ios_network_call", -} as const diff --git a/frontend/app/player/MessageDistributor/network/loadFiles.ts b/frontend/app/player/MessageDistributor/network/loadFiles.ts deleted file mode 100644 index 6b34c3261..000000000 --- a/frontend/app/player/MessageDistributor/network/loadFiles.ts +++ /dev/null @@ -1,71 +0,0 @@ -import APIClient from 'App/api_client'; - -const NO_NTH_FILE = "nnf" -const NO_UNPROCESSED_FILES = "nuf" - -export const loadFiles = ( - urls: string[], - onData: (data: Uint8Array) => void, -): Promise<void> => { - const firstFileURL = urls[0] - urls = urls.slice(1) - if (!firstFileURL) { - return Promise.reject("No urls provided") - } - return window.fetch(firstFileURL) - .then(r => { - return processAPIStreamResponse(r, true) - }) - .then(onData) - .then(() => - urls.reduce((p, url) => - p.then(() => - window.fetch(url) - .then(r => { - return processAPIStreamResponse(r, false) - }) - .then(onData) - ), - Promise.resolve(), - ) - ) - .catch(e => { - if (e === NO_NTH_FILE) { - return - } - throw e - }) -} - - -export async function requestEFSDom(sessionId: string) { - return await requestEFSMobFile(sessionId, "dom.mob") -} - -export async function requestEFSDevtools(sessionId: string) { - return await requestEFSMobFile(sessionId, "devtools.mob") -} - -async function requestEFSMobFile(sessionId: string, filename: string) { - const api = new APIClient() - const res = await api.fetch('/unprocessed/' + sessionId + '/' + filename) - if (res.status >= 400) { - throw NO_UNPROCESSED_FILES - } - return await processAPIStreamResponse(res, false) -} - -const processAPIStreamResponse = (response: Response, isMainFile: boolean) => { - return new Promise<ArrayBuffer>((res, rej) => { - if (response.status === 404 && !isMainFile) { - return rej(NO_NTH_FILE) - } - if (response.status >= 400) { - return rej( - isMainFile ? `no start file. status code ${ response.status }` - : `Bad endfile status code ${response.status}` - ) - } - res(response.arrayBuffer()) - }).then(buffer => new Uint8Array(buffer)) -} diff --git a/frontend/app/player/Player.ts b/frontend/app/player/Player.ts deleted file mode 100644 index 369f854ea..000000000 --- a/frontend/app/player/Player.ts +++ /dev/null @@ -1,279 +0,0 @@ -import { update, getState } from './store'; -import MessageDistributor, { INITIAL_STATE as SUPER_INITIAL_STATE } from './MessageDistributor/MessageDistributor'; -import { Note } from 'App/services/NotesService'; - -const fps = 60; -const performance = window.performance || { now: Date.now.bind(Date) }; -const requestAnimationFrame = - window.requestAnimationFrame || - // @ts-ignore - window.webkitRequestAnimationFrame || - // @ts-ignore - window.mozRequestAnimationFrame || - // @ts-ignore - window.oRequestAnimationFrame || - // @ts-ignore - window.msRequestAnimationFrame || - ((callback: (args: any) => void) => window.setTimeout(() => { callback(performance.now()); }, 1000 / fps)); -const cancelAnimationFrame = - window.cancelAnimationFrame || - // @ts-ignore - window.mozCancelAnimationFrame || - window.clearTimeout; - -const HIGHEST_SPEED = 16; - - -const SPEED_STORAGE_KEY = "__$player-speed$__"; -const SKIP_STORAGE_KEY = "__$player-skip$__"; -const SKIP_TO_ISSUE_STORAGE_KEY = "__$session-skipToIssue$__"; -const AUTOPLAY_STORAGE_KEY = "__$player-autoplay$__"; -const SHOW_EVENTS_STORAGE_KEY = "__$player-show-events$__"; -const storedSpeed: number = parseInt(localStorage.getItem(SPEED_STORAGE_KEY) || "") ; -const initialSpeed = [1,2,4,8,16].includes(storedSpeed) ? storedSpeed : 1; -const initialSkip = localStorage.getItem(SKIP_STORAGE_KEY) === 'true'; -const initialSkipToIssue = localStorage.getItem(SKIP_TO_ISSUE_STORAGE_KEY) === 'true'; -const initialAutoplay = localStorage.getItem(AUTOPLAY_STORAGE_KEY) === 'true'; -const initialShowEvents = localStorage.getItem(SHOW_EVENTS_STORAGE_KEY) === 'true'; - -export const INITIAL_STATE = { - ...SUPER_INITIAL_STATE, - time: 0, - playing: false, - completed: false, - endTime: 0, - inspectorMode: false, - live: false, - livePlay: false, - liveTimeTravel: false, - notes: [], -} as const; - - -export const INITIAL_NON_RESETABLE_STATE = { - skip: initialSkip, - skipToIssue: initialSkipToIssue, - autoplay: initialAutoplay, - speed: initialSpeed, - showEvents: initialShowEvents, -} - -export default class Player extends MessageDistributor { - private _animationFrameRequestId: number = 0; - - private _setTime(time: number, index?: number) { - update({ - time, - completed: false, - }); - super.move(time, index); - } - - private _startAnimation() { - let prevTime = getState().time; - let animationPrevTime = performance.now(); - - const nextFrame = (animationCurrentTime: number) => { - const { - speed, - skip, - autoplay, - skipIntervals, - endTime, - live, - livePlay, - disconnected, - messagesLoading, - cssLoading, - } = getState(); - - const diffTime = messagesLoading || cssLoading || disconnected - ? 0 - : Math.max(animationCurrentTime - animationPrevTime, 0) * (live ? 1 : speed); - - let time = prevTime + diffTime; - - const skipInterval = !live && skip && skipIntervals.find((si: Node) => si.contains(time)); // TODO: good skip by messages - if (skipInterval) time = skipInterval.end; - - const fmt = super.getFirstMessageTime(); - if (time < fmt) time = fmt; // ? - - const lmt = super.getLastMessageTime(); - if (livePlay && time < lmt) time = lmt; - if (endTime < lmt) { - update({ - endTime: lmt, - }); - } - - prevTime = time; - animationPrevTime = animationCurrentTime; - - const completed = !live && time >= endTime; - if (completed) { - this._setTime(endTime); - return update({ - playing: false, - completed: true, - }); - } - - // throttle store updates - // TODO: make it possible to change frame rate - if (live && time - endTime > 100) { - update({ - endTime: time, - livePlay: endTime - time < 900 - }); - } - this._setTime(time); - this._animationFrameRequestId = requestAnimationFrame(nextFrame); - }; - this._animationFrameRequestId = requestAnimationFrame(nextFrame); - } - - play() { - cancelAnimationFrame(this._animationFrameRequestId); - update({ playing: true }); - this._startAnimation(); - } - - pause() { - cancelAnimationFrame(this._animationFrameRequestId); - update({ playing: false }) - } - - togglePlay() { - const { playing, completed } = getState(); - if (playing) { - this.pause(); - } else if (completed) { - this._setTime(0); - this.play(); - } else { - this.play(); - } - } - - jump(setTime: number, index: number) { - const { live, liveTimeTravel, endTime } = getState(); - if (live && !liveTimeTravel) return; - const time = setTime ? setTime : getState().time - if (getState().playing) { - cancelAnimationFrame(this._animationFrameRequestId); - // this._animationFrameRequestId = requestAnimationFrame(() => { - this._setTime(time, index); - this._startAnimation(); - // throttilg the redux state update from each frame to nearly half a second - // which is better for performance and component rerenders - update({ livePlay: Math.abs(time - endTime) < 500 }); - //}); - } else { - //this._animationFrameRequestId = requestAnimationFrame(() => { - this._setTime(time, index); - update({ livePlay: Math.abs(time - endTime) < 500 }); - //}); - } - } - - toggleSkip() { - const skip = !getState().skip; - localStorage.setItem(SKIP_STORAGE_KEY, `${skip}`); - update({ skip }); - } - - toggleInspectorMode(flag: boolean, clickCallback?: (args: any) => void) { - if (typeof flag !== 'boolean') { - const { inspectorMode } = getState(); - flag = !inspectorMode; - } - - if (flag) { - this.pause(); - update({ inspectorMode: true }); - return super.enableInspector(clickCallback); - } else { - super.disableInspector(); - update({ inspectorMode: false }); - } - } - - markTargets(targets: { selector: string, count: number }[] | null) { - this.pause(); - this.setMarkedTargets(targets); - } - - activeTarget(index: number) { - this.setActiveTarget(index); - } - - toggleSkipToIssue() { - const skipToIssue = !getState().skipToIssue; - localStorage.setItem(SKIP_TO_ISSUE_STORAGE_KEY, `${skipToIssue}`); - update({ skipToIssue }); - } - - toggleAutoplay() { - const autoplay = !getState().autoplay; - localStorage.setItem(AUTOPLAY_STORAGE_KEY, `${autoplay}`); - update({ autoplay }); - } - - toggleEvents(shouldShow?: boolean) { - const showEvents = shouldShow || !getState().showEvents; - localStorage.setItem(SHOW_EVENTS_STORAGE_KEY, `${showEvents}`); - update({ showEvents }); - } - - _updateSpeed(speed: number) { - localStorage.setItem(SPEED_STORAGE_KEY, `${speed}`); - update({ speed }); - } - - toggleSpeed() { - const { speed } = getState(); - this._updateSpeed(speed < HIGHEST_SPEED ? speed * 2 : 1); - } - - speedUp() { - const { speed } = getState(); - this._updateSpeed(Math.min(HIGHEST_SPEED, speed * 2)); - } - - speedDown() { - const { speed } = getState(); - this._updateSpeed(Math.max(1, speed/2)); - } - - async toggleTimetravel() { - if (!getState().liveTimeTravel) { - return await this.reloadWithUnprocessedFile() - } - } - - jumpToLive() { - cancelAnimationFrame(this._animationFrameRequestId); - this._setTime(getState().endTime); - this._startAnimation(); - update({ livePlay: true }); - } - - toggleUserName(name?: string) { - this.cursor.toggleUserName(name) - } - - injectNotes(notes: Note[]) { - update({ notes }) - } - - filterOutNote(noteId: number) { - const { notes } = getState() - update({ notes: notes.filter((note: Note) => note.noteId !== noteId) }) - } - - clean() { - this.pause(); - super.clean(); - } -} diff --git a/frontend/app/player/MessageDistributor/managers/ListWalker.ts b/frontend/app/player/common/ListWalker.ts similarity index 88% rename from frontend/app/player/MessageDistributor/managers/ListWalker.ts rename to frontend/app/player/common/ListWalker.ts index c0d59c354..fbd44007b 100644 --- a/frontend/app/player/MessageDistributor/managers/ListWalker.ts +++ b/frontend/app/player/common/ListWalker.ts @@ -1,4 +1,4 @@ -import type { Timed } from '../messages/timed'; +import type { Timed } from './types'; export default class ListWalker<T extends Timed> { private p = 0 @@ -6,12 +6,21 @@ export default class ListWalker<T extends Timed> { append(m: T): void { if (this.length > 0 && this.last && m.time < this.last.time) { - console.error("Trying to append message with the less time then the list tail: ", m) + console.error("Trying to append message with the less time then the list tail:", m.time, 'vs', this.last.time, m, this) return } this.list.push(m); } + insert(m: T): void { + let index = this.list.findIndex(om => om.time > m.time) + if (index === -1) { + index = this.length + } + const oldList = this.list + this._list = [...oldList.slice(0, index), m, ...oldList.slice(index)] + } + reset(): void { this.p = 0 } @@ -98,7 +107,7 @@ export default class ListWalker<T extends Timed> { /* Returns last message with the time <= t. - Assumed that the current message is already handled so + Assumed that the current message is already handled so if pointer doesn't cahnge <null> is returned. */ moveGetLast(t: number, index?: number): T | null { diff --git a/frontend/app/player/MessageDistributor/managers/ListWalkerWithMarks.ts b/frontend/app/player/common/ListWalkerWithMarks.ts similarity index 94% rename from frontend/app/player/MessageDistributor/managers/ListWalkerWithMarks.ts rename to frontend/app/player/common/ListWalkerWithMarks.ts index d2f2ccee3..a97378d03 100644 --- a/frontend/app/player/MessageDistributor/managers/ListWalkerWithMarks.ts +++ b/frontend/app/player/common/ListWalkerWithMarks.ts @@ -1,4 +1,4 @@ -import type { Timed } from '../messages/timed'; +import type { Timed } from './types'; import ListWalker from './ListWalker' diff --git a/frontend/app/player/common/SimpleStore.ts b/frontend/app/player/common/SimpleStore.ts new file mode 100644 index 000000000..dd7e1ee7b --- /dev/null +++ b/frontend/app/player/common/SimpleStore.ts @@ -0,0 +1,14 @@ +import { Store } from './types' + +// (not a type) +export default class SimpleSore<G, S=G> implements Store<G, S> { + constructor(private state: G){} + get(): G { + return this.state + } + update(newState: Partial<S>) { + Object.assign(this.state, newState) + } +} + + diff --git a/frontend/app/player/common/types.ts b/frontend/app/player/common/types.ts new file mode 100644 index 000000000..308ec0659 --- /dev/null +++ b/frontend/app/player/common/types.ts @@ -0,0 +1,27 @@ +export interface Timed { + time: number +} + +export interface Indexed { + index: number +} + +export interface Moveable { + move(time: number): void +} + +export interface Cleanable { + clean(): void +} + +export interface Interval { + contains(t: number): boolean + start: number + end: number +} + +export interface Store<G extends Object, S extends Object = G> { + get(): G + update(state: Partial<S>): void +} + diff --git a/frontend/app/player/create.ts b/frontend/app/player/create.ts new file mode 100644 index 000000000..016b8eb52 --- /dev/null +++ b/frontend/app/player/create.ts @@ -0,0 +1,52 @@ +import SimpleStore from './common/SimpleStore' +import type { Store } from './common/types' + +import WebPlayer from './web/WebPlayer' +import WebLivePlayer from './web/WebLivePlayer' + +type WebState = typeof WebPlayer.INITIAL_STATE +type WebPlayerStore = Store<WebState> +export type IWebPlayer = WebPlayer +export type IWebPlayerStore = WebPlayerStore + +type WebLiveState = typeof WebLivePlayer.INITIAL_STATE +type WebLivePlayerStore = Store<WebLiveState> +export type IWebLivePlayer = WebLivePlayer +export type IWebLivePlayerStore = WebLivePlayerStore + +export function createWebPlayer(session: Record<string, any>, wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] { + let store: WebPlayerStore = new SimpleStore<WebState>({ + ...WebPlayer.INITIAL_STATE, + }) + if (wrapStore) { + store = wrapStore(store) + } + + const player = new WebPlayer(store, session, false) + return [player, store] +} + + +export function createClickMapPlayer(session: Record<string, any>, wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] { + let store: WebPlayerStore = new SimpleStore<WebState>({ + ...WebPlayer.INITIAL_STATE, + }) + if (wrapStore) { + store = wrapStore(store) + } + + const player = new WebPlayer(store, session, false, true) + return [player, store] +} + +export function createLiveWebPlayer(session: Record<string, any>, config: RTCIceServer[], wrapStore?: (s:IWebLivePlayerStore) => IWebLivePlayerStore): [IWebLivePlayer, IWebLivePlayerStore] { + let store: WebLivePlayerStore = new SimpleStore<WebLiveState>({ + ...WebLivePlayer.INITIAL_STATE, + }) + if (wrapStore) { + store = wrapStore(store) + } + + const player = new WebLivePlayer(store, session, config) + return [player, store] +} diff --git a/frontend/app/player/index.js b/frontend/app/player/index.js deleted file mode 100644 index c49d75294..000000000 --- a/frontend/app/player/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export * from './store'; -export * from './singletone'; -export * from './MessageDistributor/managers/AssistManager'; -export * from './MessageDistributor/managers/LocalStream'; \ No newline at end of file diff --git a/frontend/app/player/index.ts b/frontend/app/player/index.ts new file mode 100644 index 000000000..26a2270d5 --- /dev/null +++ b/frontend/app/player/index.ts @@ -0,0 +1,10 @@ +export * from './common/types'; +export * from './create'; + + +export * from './web/assist/AssistManager'; +export * from './web/assist/LocalStream'; +export * from './web/WebPlayer'; +export * from './web/storageSelectors'; +export * from './web/types'; +export type { MarkedTarget } from './web/addons/TargetMarker' diff --git a/frontend/app/player/ios/ImagePlayer.js b/frontend/app/player/ios/ImagePlayer.js deleted file mode 100644 index 1dcf2b9cc..000000000 --- a/frontend/app/player/ios/ImagePlayer.js +++ /dev/null @@ -1,454 +0,0 @@ -import { io } from 'socket.io-client'; -import { makeAutoObservable, autorun } from 'mobx'; -import logger from 'App/logger'; -import { - createPlayerState, - createToolPanelState, - createToggleState, - PLAYING, - PAUSED, - COMPLETED, - SOCKET_ERROR, - - CRASHES, - LOGS, - NETWORK, - PERFORMANCE, - CUSTOM, - EVENTS, // last evemt +clicks -} from "./state"; -import { - createListState, - createScreenListState, -} from './lists'; -import Parser from './Parser'; -import PerformanceList from './PerformanceList'; - -const HIGHEST_SPEED = 3; - - -export default class ImagePlayer { - _screen = null - _wrapper = null - _socket = null - toolPanel = createToolPanelState() - fullscreen = createToggleState() - lists = { - [LOGS]: createListState(), - [NETWORK]: createListState(), - [CRASHES]: createListState(), - [EVENTS]: createListState(), - [CUSTOM]: createListState(), - [PERFORMANCE]: new PerformanceList(), - } - _clicks = createListState() - _screens = createScreenListState() - - constructor(session) { - this.state = createPlayerState({ - endTime: session.duration.valueOf(), - }); - //const canvas = document.createElement("canvas"); - // this._context = canvas.getContext('2d'); - // this._img = new Image(); - // this._img..onerror = function(e){ - // logger.log('Error during loading image:', e); - // }; - // wrapper.appendChild(this._img); - session.crashes.forEach(c => this.lists[CRASHES].append(c)); - session.events.forEach(e => this.lists[EVENTS].append(e)); - session.stackEvents.forEach(e => this.lists[CUSTOM].append(e)); - window.fetch(session.mobsUrl) - .then(r => r.arrayBuffer()) - .then(b => { - new Parser(new Uint8Array(b)).parseEach(m => { - m.time = m.timestamp - session.startedAt; - try { - if (m.tp === "ios_log") { - this.lists[LOGS].append(m); - } else if (m.tp === "ios_network_call") { - this.lists[NETWORK].append(m); - // } else if (m.tp === "ios_custom_event") { - // this.lists[CUSTOM].append(m); - } else if (m.tp === "ios_click_event") { - m.time -= 600; //for graphic initiation - this._clicks.append(m); - } else if (m.tp === "ios_performance_event") { - this.lists[PERFORMANCE].append(m); - } - } catch (e) { - logger.error(e); - } - }); - Object.values(this.lists).forEach(list => list.moveGetLast(0)); // In case of negative values - }) - - if (session.socket == null || typeof session.socket.jwt !== "string" || typeof session.socket.url !== "string") { - logger.error("No socket info found fpr session", session); - return - } - - const options = { - extraHeaders: {Authorization: `Bearer ${session.socket.jwt}`}, - reconnectionAttempts: 5, - //transports: ['websocket'], - } - - const socket = this._socket = io(session.socket.url, options); - socket.on("connect", () => { - logger.log("Socket Connected"); - }); - - socket.on('disconnect', (reason) => { - if (reason === 'io client disconnect') { - return; - } - logger.error("Disconnected. Reason: ", reason) - // if (reason === 'io server disconnect') { - // socket.connect(); - // } - }); - socket.on('connect_error', (e) => { - this.state.setState(SOCKET_ERROR); - logger.error(e) - }); - - socket.on('screen', (time, width, height, binary) => { - //logger.log("New Screen!", time, width, height, binary); - this._screens.insertScreen(time, width, height, binary); - }); - socket.on('buffered', (playTime) => { - if (playTime === this.state.time) { - this.state.setBufferingState(false); - } - logger.log("Play ack!", playTime); - }); - - let startPingInterval; - socket.on('start', () => { - logger.log("Started!"); - clearInterval(startPingInterval) - this.state.setBufferingState(true); - socket.emit("speed", this.state.speed); - this.play(); - }); - startPingInterval = setInterval(() => socket.emit("start"), 1000); - socket.emit("start"); - - window.addEventListener("resize", this.scale); - autorun(this.scale); - } - - _click - _getClickElement() { - if (this._click != null) { - return this._click; - } - const click = document.createElement('div'); - click.style.position = "absolute"; - click.style.background = "#ddd"; - click.style.border = "solid 4px #bbb"; - click.style.borderRadius = "50%"; - click.style.width = "32px"; - click.style.height = "32px"; - click.style.transformOrigin = "center"; - return this._click = click; - } - // More sufficient ways? - _animateClick({ x, y }) { - if (this._screen == null) { - return; - } - const click = this._getClickElement(); - if (click.parentElement == null) { - this._screen.appendChild(click); - } - click.style.transition = "none"; - click.style.left = `${x-18}px`; - click.style.top = `${y-18}px`; - click.style.transform = "scale(1)"; - click.style.opacity = "1"; - setTimeout(() => { - click.style.transition = "all ease-in .5s"; - click.style.transform = "scale(0)"; - click.style.opacity = "0"; - }, 0) - } - - _updateFrame({ image, width, height }) { - // const img = new Image(); - // img.onload = () => { - // this._context.drawImage(img); - // }; - // img.onerror = function(e){ - // logger.log('Error during loading image:', e); - // }; - // this._screen.style.backgroundImage = `url(${binaryToDataURL(binaryArray)})`; - this._canvas.getContext('2d').drawImage(image, 0, 0, this._canvas.width, this._canvas.height); - } - - _setTime(ts) { - ts = Math.max(Math.min(ts, this.state.endTime), 0); - this.state.setTime(ts); - Object.values(this.lists).forEach(list => list.moveGetLast(ts)); - const screen = this._screens.moveGetLast(ts); - if (screen != null) { - const { dataURL, width, height } = screen; - this.state.setSize(width, height); - //imagePromise.then(() => this._updateFrame({ image, width, height })); - //this._screen.style.backgroundImage = `url(${screen.dataURL})`; - screen.loadImage.then(() => this._screen.style.backgroundImage = `url(${screen.dataURL})`); - } - const lastClick = this._clicks.moveGetLast(ts); - if (lastClick != null && lastClick.time > ts - 600) { - this._animateClick(lastClick); - } - } - - attach({ wrapperId, screenId }) { - const screen = document.getElementById(screenId); - if (!screen) { - throw new Error(`ImagePlayer: No screen element found with ID "${screenId}" `); - } - const wrapper = document.getElementById(wrapperId); - if (!wrapper) { - throw new Error(`ImagePlayer: No wrapper element found with ID "${wrapperId}" `); - } - screen.style.backgroundSize = "contain"; - screen.style.backgroundPosition = "center"; - wrapper.style.position = "absolute"; - wrapper.style.transformOrigin = "left top"; - wrapper.style.top = "50%"; - wrapper.style.left = "50%"; - // const canvas = document.createElement('canvas'); - // canvas.style.width = "300px"; - // canvas.style.height = "600px"; - // screen.appendChild(canvas); - // this._canvas = canvas; - this._screen = screen; - this._wrapper = wrapper; - this.scale(); - } - - - get loading() { - return this.state.initializing; - } - - get buffering() { - return this.state.buffering; - } - - // get timeTravelDisabled() { - // return this.state.initializing; - // } - - get controlsDisabled() { - return this.state.initializing; //|| this.state.buffering; - } - - _animationFrameRequestId = null - _stopAnimation() { - cancelAnimationFrame(this._animationFrameRequestId); - } - _startAnimation() { - let prevTime = this.state.time; - let animationPrevTime = performance.now(); - const nextFrame = (animationCurrentTime) => { - const { - speed, - //skip, - //skipIntervals, - endTime, - playing, - buffering, - //live, - //livePlay, - //disconnected, - //messagesLoading, - //cssLoading, - } = this.state; - - const diffTime = !playing || buffering - ? 0 - : Math.max(animationCurrentTime - animationPrevTime, 0) * speed; - - let time = prevTime + diffTime; - - //const skipInterval = skip && skipIntervals.find(si => si.contains(time)); // TODO: good skip by messages - //if (skipInterval) time = skipInterval.end; - - //const fmt = this.getFirstMessageTime(); - //if (time < fmt) time = fmt; // ? - - //const lmt = this.getLastMessageTime(); - //if (livePlay && time < lmt) time = lmt; - // if (endTime < lmt) { - // update({ - // endTime: lmt, - // }); - // } - - prevTime = time; - animationPrevTime = animationCurrentTime; - - const completed = time >= endTime; - if (completed) { - this._setComplete(); - } else { - - // if (live && time > endTime) { - // update({ - // endTime: time, - // }); - // } - this._setTime(time); - this._animationFrameRequestId = requestAnimationFrame(nextFrame); - } - }; - this._animationFrameRequestId = requestAnimationFrame(nextFrame); - } - - - scale = () => { - const { height, width } = this.state; // should be before any return for mobx observing - if (this._wrapper === null) return; - const parent = this._wrapper.parentElement; - if (parent === null) return; - let s = 1; - const { offsetWidth, offsetHeight } = parent; - - s = Math.min(offsetWidth / width, (offsetHeight - 20) / height); - if (s > 1) { - s = 1; - } else { - s = Math.round(s * 1e3) / 1e3; - } - - this._wrapper.style.transform = `scale(${ s }) translate(-50%, -50%)`; - this._wrapper.style.width = width + 'px'; - this._wrapper.style.height = height + 'px'; - // this._canvas.style.width = width + 'px'; - // this._canvas.style.height = height + 'px'; - } - - _setComplete() { - this.state.setStatus(COMPLETED); - this._setTime(this.state.endTime); - if (this._socket != null) { - this._socket.emit("pause"); - } - } - - - _pause() { - this._stopAnimation(); - this.state.setStatus(PAUSED); - } - - pause = () => { - this._pause(); - if (this._socket != null) { - this._socket.emit("pause"); - } - } - - _play() { - if (!this.state.playing) { - this._startAnimation(); - } - this.state.setStatus(PLAYING); - } - play = () => { - this._play() - if (this._socket != null) { - this._socket.emit("resume"); - } - } - - _jump(ts) { - if (this.state.playing) { - this._stopAnimation(); - this._setTime(ts); - this._startAnimation(); - } else { - this._setTime(ts); - this.state.setStatus(PAUSED); // for the case when completed - } - } - jump = (ts) => { - ts = Math.round(ts); // Should be integer - this._jump(ts); - if (this._socket != null) { - this.state.setBufferingState(true); - console.log("Send play on jump!", ts) - this._socket.emit("jump", ts); - } - } - - togglePlay = () => { - if (this.state.playing) { - this.pause() - } else { - if (this.state.completed) { - //this.state.time = 0; - this.jump(0) - } - this.play() - } - } - backTenSeconds = () => { - this.jump(Math.max(this.state.time - 10000, 0)); - } - forthTenSeconds = () => { - this.jump(Math.min(this.state.time + 10000, this.state.endTime)); - } - - _setSpeed(speed) { - if (this._socket != null) { - this._socket.emit("speed", speed); - } - this.state.setSpeed(speed) - } - - toggleSpeed = () => { - const speed = this.state.speed; - this._setSpeed(speed < HIGHEST_SPEED ? speed + 1 : 1); - } - - speedUp = () => { - const speed = this.state.speed; - this._setSpeed(Math.min(HIGHEST_SPEED, speed + 1)); - } - - speedDown = () => { - const speed = this.state.speed; - this._setSpeed(Math.max(1, speed - 1)); - } - - togglePanel = (key) => { - this.toolPanel.toggle(key); - setTimeout(() => this.scale(), 0); - } - - closePanel = () => { - this.toolPanel.close(); - setTimeout(() => this.scale(), 0); - } - - toggleFullscreen = (flag = true) => { - this.fullscreen.toggle(flag); - setTimeout(() => this.scale(), 0); - } - - - clean() { - this._stopAnimation(); - if (this._socket != null) { - //this._socket.emit("close"); - this._socket.close(); - } - this._screens.clean(); - } -} - diff --git a/frontend/app/player/ios/Parser.ts b/frontend/app/player/ios/Parser.ts deleted file mode 100644 index 15b750df6..000000000 --- a/frontend/app/player/ios/Parser.ts +++ /dev/null @@ -1,34 +0,0 @@ -import RawMessageReader from '../MessageDistributor/messages/RawMessageReader'; - - -export default class Parser { - private reader: RawMessageReader - private error: boolean = false - constructor(byteArray) { - this.reader = new RawMessageReader(byteArray) - } - - parseEach(cb) { - while (this.hasNext()) { - const msg = this.next(); - if (msg !== null) { - cb(msg); - } - } - } - - hasNext() { - return !this.error && this.reader.hasNextByte(); - } - - next() { - try { - return this.reader.readMessage() - } catch(e) { - console.warn(e) - this.error = true - return null - } - } - -} \ No newline at end of file diff --git a/frontend/app/player/ios/PerformanceList.js b/frontend/app/player/ios/PerformanceList.js deleted file mode 100644 index 47d7918f3..000000000 --- a/frontend/app/player/ios/PerformanceList.js +++ /dev/null @@ -1,73 +0,0 @@ -import { - createListState, -} from './lists'; - -const MIN_INTERVAL = 500; - - -const NAME_MAP = { - "mainThreadCPU": "cpu", - "batteryLevel": "battery", - "memoryUsage": "memory", -} - -export default class PerformanceList { - _list = createListState() - availability = { - cpu: false, - memory: false, - battery: false, - } - - get list() { - return this._list.list; - } - - get count() { - return this._list.count; - } - - moveGetLast(t) { - this._list.moveGetLast(t); - } - - append(m) { - if (!["mainThreadCPU", "memoryUsage", "batteryLevel", "thermalState", "activeProcessorCount", "isLowPowerModeEnabled"].includes(m.name)) { - return; - } - - let lastPoint = Object.assign({ time: 0, cpu: null, battery: null, memory: null }, this._list.last); - if (this._list.length === 0) { - this._list.append(lastPoint); - } - - if (NAME_MAP[m.name] != null) { - this.availability[ NAME_MAP[m.name] ] = true; - if (lastPoint[NAME_MAP[m.name]] === null) { - this._list.forEach(p => p[NAME_MAP[m.name]] = m.value); - lastPoint[NAME_MAP[m.name]] = m.value; - } - } - - - const newPoint = Object.assign({}, lastPoint, { - time: m.time, - [ NAME_MAP[m.name] || m.name ]: m.value, - }); - - const dif = m.time - lastPoint.time; - const insertCount = Math.floor(dif/MIN_INTERVAL); - for (let i = 0; i < insertCount; i++){ - const evalValue = (key) => lastPoint[key] + Math.floor((newPoint[key]-lastPoint[key])/insertCount*(i + 1)) - this._list.append({ - ...lastPoint, - time: evalValue("time"), - cpu: evalValue("cpu") + (Math.floor(5*Math.random())-2), - battery: evalValue("battery"), - memory: evalValue("memory")*(1 + (0.1*Math.random() - 0.05)), - }); - } - - this._list.append(newPoint); - } -} \ No newline at end of file diff --git a/frontend/app/player/ios/ScreenList.ts b/frontend/app/player/ios/ScreenList.ts deleted file mode 100644 index ef6d1e73f..000000000 --- a/frontend/app/player/ios/ScreenList.ts +++ /dev/null @@ -1,57 +0,0 @@ -import ListWalker from '../MessageDistributor/managers/ListWalker'; - -//URL.revokeObjectURL() !! -function binaryToDataURL(arrayBuffer){ - var blob = new Blob([new Uint8Array(arrayBuffer)], {'type' : 'image/jpeg'}); - return URL.createObjectURL(blob); -} - -function prepareImage(width, height, arrayBuffer) { - const dataURL = binaryToDataURL(arrayBuffer); - return { - loadImage: new Promise(resolve => { - const img = new Image(); - img.onload = function() { - //URL.revokeObjectURL(this.src); - resolve(img); - }; - img.src = dataURL; - }).then(), - dataURL, - }; -} - -export default class ScreenList { - _walker = new ListWalker(); - _insertUnique(m) { - let p = this._walker._list.length; - while (p > 0 && this._walker._list[ p - 1 ].time > m.time) { - p--; - } - if (p > 0 && this._walker._list[ p - 1 ].time === m.time) { - return; - } - this._walker._list.splice(p, 0, m); - } - - moveGetLast(time) { - return this._walker.moveGetLast(time); - } - - insertScreen(time, width, height, arrayBuffer): void { - this._insertUnique({ - time, - width, - height, - ...prepareImage(width, height, arrayBuffer), - //image: new ImageData(new Uint8ClampedArray(arrayBuffer), width, height), - // dataURL: binaryToDataURL(arrayBuffer) - }); - } - - clean() { - this._walker.forEach(m => { - URL.revokeObjectURL(m.dataURL); - }); - } -} \ No newline at end of file diff --git a/frontend/app/player/ios/lists.js b/frontend/app/player/ios/lists.js deleted file mode 100644 index b0ddb9d0d..000000000 --- a/frontend/app/player/ios/lists.js +++ /dev/null @@ -1,12 +0,0 @@ -import { makeAutoObservable } from "mobx" -import ListWalker from '../MessageDistributor/managers/ListWalker'; - -import ScreenList from './ScreenList'; - -export function createListState(list) { - return makeAutoObservable(new ListWalker(list)); -} - -export function createScreenListState() { - return makeAutoObservable(new ScreenList()); -} \ No newline at end of file diff --git a/frontend/app/player/ios/state.js b/frontend/app/player/ios/state.js deleted file mode 100644 index 766ba08fe..000000000 --- a/frontend/app/player/ios/state.js +++ /dev/null @@ -1,112 +0,0 @@ -import { makeAutoObservable } from "mobx" - -//configure ({empceActions: true}) - -export const - NONE = 0, - CRASHES = 1, - NETWORK = 2, - LOGS = 3, - EVENTS = 4, - CUSTOM = 5, - PERFORMANCE = 6; - -export function createToolPanelState() { - return makeAutoObservable({ - key: NONE, - toggle(key) { // auto-bind?? - this.key = this.key === key ? NONE : key; - }, - close() { - this.key = NONE; - }, - }); -} - - -export function createToggleState() { - return makeAutoObservable({ - enabled: false, - toggle(flag) { - this.enabled = typeof flag === 'boolean' - ? flag - : !this.enabled; - }, - enable() { - this.enabled = true; - }, - disable() { - this.enabled = false; - }, - }); -} - -const SPEED_STORAGE_KEY = "__$player-speed$__"; -//const SKIP_STORAGE_KEY = "__$player-skip$__"; -//const initialSkip = !!localStorage.getItem(SKIP_STORAGE_KEY); - -export const - INITIALIZING = 0, - PLAYING = 1, - PAUSED = 2, - COMPLETED = 3, - SOCKET_ERROR = 5; - -export const - PORTRAIT = 1, - LANDSCAPE = 2; - -export function createPlayerState(state) { - const storedSpeed = +localStorage.getItem(SPEED_STORAGE_KEY); - const initialSpeed = [1,2,3].includes(storedSpeed) ? storedSpeed : 1; - - return makeAutoObservable({ - status: INITIALIZING, - _statusSaved: null, - setTime(t) { - this.time = t - }, - time: 0, - endTime: 0, - setStatus(status) { - this.status = status; - }, - get initializing() { - return this.status === INITIALIZING; - }, - get playing() { - return this.status === PLAYING; - }, - get completed() { - return this.status === COMPLETED; - }, - _buffering: false, - get buffering() { - return this._buffering; - }, - setBufferingState(flag = true) { - this._buffering = flag; - }, - speed: initialSpeed, - setSpeed(speed) { - localStorage.setItem(SPEED_STORAGE_KEY, speed); - this.speed = speed; - }, - width: 360, - height: 780, - orientation: PORTRAIT, - get orientationLandscape() { - return this.orientation === LANDSCAPE; - }, - setSize(width, height) { - if (height < 0 || width < 0) { - console.log("Player: wrong non-positive size") - return; - } - this.width = width; - this.height = height; - this.orientation = width > height ? LANDSCAPE : PORTRAIT; - }, - ...state, - }); -} diff --git a/frontend/app/player/player/Animator.ts b/frontend/app/player/player/Animator.ts new file mode 100644 index 000000000..e60b28d78 --- /dev/null +++ b/frontend/app/player/player/Animator.ts @@ -0,0 +1,214 @@ +import type { Store, Moveable, Interval } from '../common/types'; + +const fps = 60 +const performance: { now: () => number } = window.performance || { now: Date.now.bind(Date) } +const requestAnimationFrame: typeof window.requestAnimationFrame = + window.requestAnimationFrame || + // @ts-ignore + window.webkitRequestAnimationFrame || + // @ts-ignore + window.mozRequestAnimationFrame || + // @ts-ignore + window.oRequestAnimationFrame || + // @ts-ignore + window.msRequestAnimationFrame || + (callback => window.setTimeout(() => { callback(performance.now()) }, 1000 / fps)) +const cancelAnimationFrame = + window.cancelAnimationFrame || + // @ts-ignore + window.mozCancelAnimationFrame || + window.clearTimeout + + +export interface SetState { + time: number + playing: boolean + completed: boolean + live: boolean + livePlay: boolean + freeze: boolean + + endTime: number +} + +export interface GetState extends SetState { + skip: boolean + speed: number + skipIntervals: Interval[] + ready: boolean + + lastMessageTime: number +} + +export default class Animator { + static INITIAL_STATE: SetState = { + time: 0, + playing: false, + completed: false, + live: false, + livePlay: false, + freeze: false, + + endTime: 0, + } as const + + private animationFrameRequestId: number = 0 + + constructor(private store: Store<GetState>, private mm: Moveable) { + + // @ts-ignore + window.playerJump = this.jump.bind(this) + } + + private setTime(time: number) { + this.store.update({ + time, + completed: false, + }) + this.mm.move(time) + } + + private startAnimation() { + let prevTime = this.store.get().time + let animationPrevTime = performance.now() + + const frameHandler = (animationCurrentTime: number) => { + const { + speed, + skip, + skipIntervals, + endTime, + live, + livePlay, + ready, // = messagesLoading || cssLoading || disconnected + + lastMessageTime, + } = this.store.get() + + const diffTime = !ready + ? 0 + : Math.max(animationCurrentTime - animationPrevTime, 0) * (live ? 1 : speed) + + let time = prevTime + diffTime + + const skipInterval = skip && skipIntervals.find(si => si.contains(time)) + if (skipInterval) time = skipInterval.end + + if (time < 0) { time = 0 } // ? + //const fmt = getFirstMessageTime(); + //if (time < fmt) time = fmt; // ? + + // if (livePlay && time < endTime) { time = endTime } + // === live only + if (livePlay && time < lastMessageTime) { time = lastMessageTime } + if (endTime < lastMessageTime) { + this.store.update({ + endTime: lastMessageTime, + }) + } + // === + + prevTime = time + animationPrevTime = animationCurrentTime + + const completed = !live && time >= endTime + if (completed) { + this.setTime(endTime) + return this.store.update({ + playing: false, + completed: true, + }) + } + + // === live only + if (live && time > endTime) { + this.store.update({ + endTime: time, + }) + } + // === + + this.setTime(time) + this.animationFrameRequestId = requestAnimationFrame(frameHandler) + } + this.animationFrameRequestId = requestAnimationFrame(frameHandler) + } + + play() { + if (this.store.get().freeze) return this.pause() + if (this.store.get().ready) { + cancelAnimationFrame(this.animationFrameRequestId) + this.store.update({ playing: true }) + this.startAnimation() + } else { + setTimeout(() => { + this.play() + }, 250) + } + } + + pause() { + cancelAnimationFrame(this.animationFrameRequestId) + this.store.update({ playing: false }) + } + + freeze() { + return new Promise<void>(res => { + if (this.store.get().ready) { + // making sure that replay is displayed completely + setTimeout(() => { + this.store.update({ freeze: true }) + this.pause() + res() + }, 250) + } else { + setTimeout(() => res(this.freeze()), 500) + } + }) + } + + togglePlay = () => { + const { playing, completed } = this.store.get() + if (playing) { + this.pause() + } else if (completed) { + this.setTime(0) + this.play() + } else { + this.play() + } + } + + // jump by index? + jump = (time: number) => { + if (this.store.get().playing) { + cancelAnimationFrame(this.animationFrameRequestId) + this.setTime(time) + this.startAnimation() + this.store.update({ livePlay: time === this.store.get().endTime }) + } else { + this.setTime(time) + this.store.update({ livePlay: time === this.store.get().endTime }) + } + } + + jumpInterval(interval: number) { + const { endTime, time } = this.store.get() + + if (interval > 0) { + return this.jump( + Math.min( + endTime, + time + interval + ) + ); + } else { + return this.jump( + Math.max( + 0, + time + interval + ) + ); + } + } +} diff --git a/frontend/app/player/player/Player.ts b/frontend/app/player/player/Player.ts new file mode 100644 index 000000000..9ebd04259 --- /dev/null +++ b/frontend/app/player/player/Player.ts @@ -0,0 +1,114 @@ +import * as typedLocalStorage from './localStorage'; + +import type { Moveable, Cleanable, Store } from '../common/types'; +import Animator from './Animator'; +import type { GetState as AnimatorGetState } from './Animator'; + + +/* == separate this == */ +const HIGHEST_SPEED = 16 +const SPEED_STORAGE_KEY = "__$player-speed$__" +const SKIP_STORAGE_KEY = "__$player-skip$__" +const SKIP_TO_ISSUE_STORAGE_KEY = "__$session-skipToIssue$__" +const AUTOPLAY_STORAGE_KEY = "__$player-autoplay$__" +const SHOW_EVENTS_STORAGE_KEY = "__$player-show-events$__" +const storedSpeed: number = typedLocalStorage.number(SPEED_STORAGE_KEY) +const initialSpeed = [0.5, 1, 2, 4, 8, 16].includes(storedSpeed) ? storedSpeed : 0.5 +const initialSkip = typedLocalStorage.boolean(SKIP_STORAGE_KEY) +const initialSkipToIssue = typedLocalStorage.boolean(SKIP_TO_ISSUE_STORAGE_KEY) +const initialAutoplay = typedLocalStorage.boolean(AUTOPLAY_STORAGE_KEY) +const initialShowEvents = typedLocalStorage.boolean(SHOW_EVENTS_STORAGE_KEY) + +export type State = typeof Player.INITIAL_STATE +/* == */ + +export default class Player extends Animator { + static INITIAL_STATE = { + ...Animator.INITIAL_STATE, + skipToIssue: initialSkipToIssue, + showEvents: initialShowEvents, + + autoplay: initialAutoplay, + skip: initialSkip, + speed: initialSpeed, + } as const + + constructor(private pState: Store<State & AnimatorGetState>, private manager: Moveable & Cleanable) { + super(pState, manager) + + // Autoplay + if (pState.get().autoplay) { + let autoPlay = true; + document.addEventListener("visibilitychange", () => { + if (document.hidden) { + const { playing } = pState.get(); + autoPlay = playing + if (playing) { + this.pause(); + } + } else if (autoPlay) { + this.play(); + } + }) + + if (!document.hidden) { + this.play(); + } + } + } + + /* === TODO: incapsulate in LSCache === */ + + toggleAutoplay() { + const autoplay = !this.pState.get().autoplay + localStorage.setItem(AUTOPLAY_STORAGE_KEY, `${autoplay}`); + this.pState.update({ autoplay }) + } + + //TODO: move to react part (with localStorage-cache react hook)? + toggleEvents() { + const showEvents = !this.pState.get().showEvents + localStorage.setItem(SHOW_EVENTS_STORAGE_KEY, `${showEvents}`); + this.pState.update({ showEvents }) + } + + // TODO: move to React part + toggleSkipToIssue() { + const skipToIssue = !this.pState.get().skipToIssue + localStorage.setItem(SKIP_TO_ISSUE_STORAGE_KEY, `${skipToIssue}`); + this.pState.update({ skipToIssue }) + } + + toggleSkip() { + const skip = !this.pState.get().skip + localStorage.setItem(SKIP_STORAGE_KEY, `${skip}`); + this.pState.update({ skip }) + } + private updateSpeed(speed: number) { + localStorage.setItem(SPEED_STORAGE_KEY, `${speed}`); + this.pState.update({ speed }) + } + + toggleSpeed() { + const { speed } = this.pState.get() + this.updateSpeed(speed < HIGHEST_SPEED ? speed * 2 : 0.5) + } + + speedUp() { + const { speed } = this.pState.get() + this.updateSpeed(Math.min(HIGHEST_SPEED, speed * 2)) + } + + speedDown() { + const { speed } = this.pState.get() + this.updateSpeed(Math.max(1, speed / 2)) + } + /* === === */ + + + clean() { + this.pause() + this.manager.clean() + } + +} \ No newline at end of file diff --git a/frontend/app/player/player/_LSCache.ts b/frontend/app/player/player/_LSCache.ts new file mode 100644 index 000000000..beb48af46 --- /dev/null +++ b/frontend/app/player/player/_LSCache.ts @@ -0,0 +1,63 @@ +import * as lstore from './localStorage' + +import type { SimpleState } from './PlayerState' + +const SPEED_STORAGE_KEY = "__$player-speed$__"; +const SKIP_STORAGE_KEY = "__$player-skip$__"; +const SKIP_TO_ISSUE_STORAGE_KEY = "__$session-skipToIssue$__"; +const AUTOPLAY_STORAGE_KEY = "__$player-autoplay$__"; +const SHOW_EVENTS_STORAGE_KEY = "__$player-show-events$__"; + + +const storedSpeed = lstore.number(SPEED_STORAGE_KEY, 1) +const initialSpeed = [1,2,4,8,16].includes(storedSpeed) ? storedSpeed : 1; +const initialSkip = lstore.boolean(SKIP_STORAGE_KEY) +const initialSkipToIssue = lstore.boolean(SKIP_TO_ISSUE_STORAGE_KEY) +const initialAutoplay = lstore.boolean(AUTOPLAY_STORAGE_KEY) +const initialShowEvents = lstore.boolean(SHOW_EVENTS_STORAGE_KEY) + +export const INITIAL_STATE = { + skipToIssue: initialSkipToIssue, + autoplay: initialAutoplay, + showEvents: initialShowEvents, + skip: initialSkip, + speed: initialSpeed, +} + +const KEY_MAP = { + speed: SPEED_STORAGE_KEY, + skip: SKIP_STORAGE_KEY, + skipToIssue: SKIP_TO_ISSUE_STORAGE_KEY, + autoplay: AUTOPLAY_STORAGE_KEY, + showEvents: SHOW_EVENTS_STORAGE_KEY, +} + +type KeysOfBoolean<T> = keyof T & keyof { [ K in keyof T as T[K] extends boolean ? K : never ] : K }; + +type Entries<T> = { + [K in keyof T]: [K, T[K]]; +}[keyof T][]; + +export default class LSCache<G extends Record<string, boolean | number | string> { + constructor(private state: SimpleState<G>, private keyMap: Record<keyof Partial<G>, string>) { + } + update(newState: Partial<G>) { + for (let [k, v] of Object.entries(newState) as Entries<Partial<G>>) { + if (k in this.keyMap) { + // @ts-ignore TODO: nice typing + //lstore[typeof v](this.keyMap[k], v) + localStorage.setItem(this.keyMap[k], String(v)) + } + } + this.state.update(newState) + } + toggle(key: KeysOfBoolean<G>) { + // @ts-ignore TODO: nice typing + this.update({ + [key]: !this.get()[key] + }) + } + get() { + return this.state.get() + } +} \ No newline at end of file diff --git a/frontend/app/player/player/localStorage.ts b/frontend/app/player/player/localStorage.ts new file mode 100644 index 000000000..03bba1d77 --- /dev/null +++ b/frontend/app/player/player/localStorage.ts @@ -0,0 +1,19 @@ + +export function number(key: string, dflt = 0): number { + const stVal = localStorage.getItem(key) + if (stVal === null) { + return dflt + } + const val = parseInt(stVal) + if (isNaN(val)) { + return dflt + } + return val +} + +export function boolean(key: string, dflt = false): boolean { + return localStorage.getItem(key) === "true" +} +export function string(key: string, dflt = ''): string { + return localStorage.getItem(key) || '' +} \ No newline at end of file diff --git a/frontend/app/player/singletone.js b/frontend/app/player/singletone.js deleted file mode 100644 index 357c76a90..000000000 --- a/frontend/app/player/singletone.js +++ /dev/null @@ -1,100 +0,0 @@ -import Player from './Player'; -import { update, cleanStore, getState } from './store'; - -/** @type {Player} */ -let instance = null; - -const initCheck = method => (...args) => { - if (instance === null) { - console.error("Player method called before Player have been initialized."); - return; - } - return method(...args); -} - - -let autoPlay = true; -document.addEventListener("visibilitychange", function() { - if (instance === null) return; - if (document.hidden) { - const { playing } = getState(); - autoPlay = playing - if (playing) { - instance.pause(); - } - } else if (autoPlay) { - instance.play(); - } -}); - -export function init(session, config, live = false) { - const endTime = !live && session.duration.valueOf(); - - instance = new Player(session, config, live); - update({ - initialized: true, - live, - livePlay: live, - endTime, // : 0, //TODO: through initialState - session, - }); - - if (!document.hidden) { - instance.play(); - } -} - -export function clean() { - if (instance === null) return; - instance.clean(); - cleanStore(); - instance = null; -} -export const jump = initCheck((...args) => instance.jump(...args)); - -export const togglePlay = initCheck((...args) => instance.togglePlay(...args)); -export const pause = initCheck((...args) => instance.pause(...args)); -export const toggleSkip = initCheck((...args) => instance.toggleSkip(...args)); -export const toggleSkipToIssue = initCheck((...args) => instance.toggleSkipToIssue(...args)); -export const toggleAutoplay = initCheck((...args) => instance.toggleAutoplay(...args)); -export const toggleSpeed = initCheck((...args) => instance.toggleSpeed(...args)); -export const toggleEvents = initCheck((...args) => instance.toggleEvents(...args)); -export const speedUp = initCheck((...args) => instance.speedUp(...args)); -export const speedDown = initCheck((...args) => instance.speedDown(...args)); -export const attach = initCheck((...args) => instance.attach(...args)); -export const markElement = initCheck((...args) => instance.marker && instance.marker.mark(...args)); -export const scale = initCheck(() => instance.scale()); -export const toggleInspectorMode = initCheck((...args) => instance.toggleInspectorMode(...args)); -/** @type {Player.assistManager.call} */ -export const callPeer = initCheck((...args) => instance.assistManager.call(...args)) -/** @type {Player.assistManager.setCallArgs} */ -export const setCallArgs = initCheck((...args) => instance.assistManager.setCallArgs(...args)) -/** @type {Player.assistManager.initiateCallEnd} */ -export const initiateCallEnd = initCheck((...args) => instance.assistManager.initiateCallEnd(...args)) -export const requestReleaseRemoteControl = initCheck((...args) => instance.assistManager.requestReleaseRemoteControl(...args)) -export const releaseRemoteControl = initCheck((...args) => instance.assistManager.releaseRemoteControl(...args)) -export const markTargets = initCheck((...args) => instance.markTargets(...args)) -export const activeTarget = initCheck((...args) => instance.activeTarget(...args)) -export const toggleAnnotation = initCheck((...args) => instance.assistManager.toggleAnnotation(...args)) -/** @type {Player.toggleTimetravel} */ -export const toggleTimetravel = initCheck((...args) => instance.toggleTimetravel(...args)) -export const jumpToLive = initCheck((...args) => instance.jumpToLive(...args)) -export const toggleUserName = initCheck((...args) => instance.toggleUserName(...args)) -export const injectNotes = initCheck((...args) => instance.injectNotes(...args)) -export const filterOutNote = initCheck((...args) => instance.filterOutNote(...args)) -/** @type {Player.assistManager.toggleVideoLocalStream} */ -export const toggleVideoLocalStream = initCheck((...args) => instance.assistManager.toggleVideoLocalStream(...args)) - -export const Controls = { - jump, - togglePlay, - pause, - toggleSkip, - toggleSkipToIssue, - toggleAutoplay, - toggleEvents, - toggleSpeed, - speedUp, - speedDown, - callPeer -} diff --git a/frontend/app/player/store/connector.js b/frontend/app/player/store/connector.js deleted file mode 100644 index 9e74f56a6..000000000 --- a/frontend/app/player/store/connector.js +++ /dev/null @@ -1,24 +0,0 @@ -import { connect, createProvider } from 'react-redux' -import store from './store'; - -const STORE_KEY = 'playerStore'; - -const PlayerProvider = createProvider(STORE_KEY); -PlayerProvider.defaultProps = { store }; - -function connectPlayer( - mapStateToProps, - mapDispatchToProps, - mergeProps, - options = {} -) { - options.storeKey = STORE_KEY - return connect( - mapStateToProps, - mapDispatchToProps, - mergeProps, - options - ) -} - -export { PlayerProvider, connectPlayer }; \ No newline at end of file diff --git a/frontend/app/player/store/duck.js b/frontend/app/player/store/duck.js deleted file mode 100644 index f57a0ed54..000000000 --- a/frontend/app/player/store/duck.js +++ /dev/null @@ -1,40 +0,0 @@ -import { applyChange, revertChange } from 'deep-diff'; -import { INITIAL_STATE as playerInitialState, INITIAL_NON_RESETABLE_STATE as playerInitialNonResetableState } from '../Player'; - -const UPDATE = 'player/UPDATE'; -const CLEAN = 'player/CLEAN'; -const REDUX = 'player/REDUX'; - -const resetState = { - ...playerInitialState, - initialized: false, -}; - -const initialState = { - ...resetState, - ...playerInitialNonResetableState, -} - -export default (state = initialState, action = {}) => { - switch (action.type) { - case UPDATE: - return { ...state, ...action.state }; - case CLEAN: - return { ...state, ...resetState }; - default: - return state; - } -} - -export function update(state = {}) { - return { - type: UPDATE, - state, - }; -} - -export function clean() { - return { - type: CLEAN - }; -} diff --git a/frontend/app/player/store/index.js b/frontend/app/player/store/index.js deleted file mode 100644 index c2a987661..000000000 --- a/frontend/app/player/store/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export * from './connector'; -export * from './store'; -export * from './selectors'; -export { default } from './store'; \ No newline at end of file diff --git a/frontend/app/player/store/selectors.js b/frontend/app/player/store/selectors.js deleted file mode 100644 index 36750b56d..000000000 --- a/frontend/app/player/store/selectors.js +++ /dev/null @@ -1,48 +0,0 @@ -const REDUX = "redux"; -const MOBX = "mobx"; -const VUEX = "vuex"; -const NGRX = "ngrx"; -const ZUSTAND = 'zustand'; -const NONE = 0; - - -export const STORAGE_TYPES = { - REDUX, - MOBX, - VUEX, - NGRX, - ZUSTAND, - NONE, -}; - - -export function selectStorageType(state) { - if (!state.reduxList) return NONE; - if (state.reduxList.length > 0) { - return REDUX; - } else if (state.vuexList.length > 0) { - return VUEX; - } else if (state.mobxList.length > 0) { - return MOBX; - } else if (state.ngrxList.length > 0) { - return NGRX; - } else if (state.zustandList.length > 0) { - return ZUSTAND; - } - return NONE; -} - -export function selectStorageList(state) { - const key = selectStorageType(state); - if (key !== NONE) { - return state[`${key}List`] || []; - } - return []; -} -export function selectStorageListNow(state) { - const key = selectStorageType(state); - if (key !== NONE) { - return state[`${key}ListNow`] || []; - } - return []; -} diff --git a/frontend/app/player/store/store.js b/frontend/app/player/store/store.js deleted file mode 100644 index fd4fed5c0..000000000 --- a/frontend/app/player/store/store.js +++ /dev/null @@ -1,20 +0,0 @@ -import { createStore } from 'redux'; -import reducer, { - update as updateAction, - clean as cleanAction, -} from './duck'; - -const store = createStore(reducer); - -export const getState = store.getState.bind(store); - -export function update(...args) { - const action = updateAction(...args) - return store.dispatch(action); -} - -export function cleanStore() { - return store.dispatch(cleanAction()); -} - -export default store; diff --git a/frontend/app/player/MessageDistributor/Lists.ts b/frontend/app/player/web/Lists.ts similarity index 53% rename from frontend/app/player/MessageDistributor/Lists.ts rename to frontend/app/player/web/Lists.ts index bae8d46de..cb12f82bf 100644 --- a/frontend/app/player/MessageDistributor/Lists.ts +++ b/frontend/app/player/web/Lists.ts @@ -1,37 +1,52 @@ -import ListWalker from './managers/ListWalker'; -import ListWalkerWithMarks from './managers/ListWalkerWithMarks'; +import ListWalker from '../common/ListWalker'; +import ListWalkerWithMarks from '../common/ListWalkerWithMarks'; +import type { Timed } from '../common/types'; -import type { Message } from './messages' -const SIMPLE_LIST_NAMES = [ "event", "redux", "mobx", "vuex", "zustand", "ngrx", "graphql", "exceptions", "profiles"] as const; -const MARKED_LIST_NAMES = [ "log", "resource", "fetch", "stack" ] as const; +const SIMPLE_LIST_NAMES = [ "event", "redux", "mobx", "vuex", "zustand", "ngrx", "graphql", "exceptions", "profiles"] as const +const MARKED_LIST_NAMES = [ "log", "resource", "fetch", "stack" ] as const //const entityNamesSimple = [ "event", "profile" ]; -const LIST_NAMES = [...SIMPLE_LIST_NAMES, ...MARKED_LIST_NAMES ]; +const LIST_NAMES = [...SIMPLE_LIST_NAMES, ...MARKED_LIST_NAMES ] as const -// TODO: provide correct types +type KeysList = `${typeof LIST_NAMES[number]}List` +type KeysListNow = `${typeof LIST_NAMES[number]}ListNow` +type KeysMarkedCountNow = `${typeof MARKED_LIST_NAMES[number]}MarkedCountNow` +type StateList = { + [key in KeysList]: Timed[] +} +type StateListNow = { + [key in KeysListNow]: Timed[] +} +type StateMarkedCountNow = { + [key in KeysMarkedCountNow]: number +} +type StateNow = StateListNow & StateMarkedCountNow +export type State = StateList & StateNow + +// maybe use list object itself inside the store export const INITIAL_STATE = LIST_NAMES.reduce((state, name) => { state[`${name}List`] = [] state[`${name}ListNow`] = [] - if (MARKED_LIST_NAMES.includes(name)) { - state[`${name}MarkedCountNow`] = 0 - state[`${name}MarkedCount`] = 0 - } return state -}, {}) +}, MARKED_LIST_NAMES.reduce((state, name) => { + state[`${name}MarkedCountNow`] = 0 + return state + }, {} as Partial<StateMarkedCountNow>) as Partial<State> +) as State type SimpleListsObject = { - [key in typeof SIMPLE_LIST_NAMES[number]]: ListWalker<any> + [key in typeof SIMPLE_LIST_NAMES[number]]: ListWalker<Timed> } type MarkedListsObject = { - [key in typeof MARKED_LIST_NAMES[number]]: ListWalkerWithMarks<any> + [key in typeof MARKED_LIST_NAMES[number]]: ListWalkerWithMarks<Timed> } type ListsObject = SimpleListsObject & MarkedListsObject -type InitialLists = { - [key in typeof LIST_NAMES[number]]: any[] +export type InitialLists = { + [key in typeof LIST_NAMES[number]]: any[] // .isRed()? } export default class Lists { @@ -43,23 +58,19 @@ export default class Lists { } for (const name of MARKED_LIST_NAMES) { // TODO: provide types - lists[name] = new ListWalkerWithMarks((el) => el.isRed(), initialLists[name]) + lists[name] = new ListWalkerWithMarks((el) => el.isRed, initialLists[name]) } this.lists = lists as ListsObject } - getFullListsState() { + getFullListsState(): StateList { return LIST_NAMES.reduce((state, name) => { state[`${name}List`] = this.lists[name].list return state - }, MARKED_LIST_NAMES.reduce((state, name) => { - state[`${name}MarkedCount`] = this.lists[name].markedCount - return state - }, {}) - ) + }, {} as Partial<StateList>) as StateList } - moveGetState(t: number)/* : Partial<State> */ { + moveGetState(t: number): StateNow { return LIST_NAMES.reduce((state, name) => { const lastMsg = this.lists[name].moveGetLast(t) // index: name === 'exceptions' ? undefined : index); if (lastMsg != null) { @@ -67,10 +78,10 @@ export default class Lists { } return state }, MARKED_LIST_NAMES.reduce((state, name) => { - state[`${name}MarkedCountNow`] = this.lists[name].markedCountNow + state[`${name}MarkedCountNow`] = this.lists[name].markedCountNow // Red --> Marked return state - }, {}) - ); + }, {} as Partial<StateMarkedCountNow>) as Partial<State> + ) as State } } \ No newline at end of file diff --git a/frontend/app/player/MessageDistributor/MessageDistributor.ts b/frontend/app/player/web/MessageManager.ts similarity index 59% rename from frontend/app/player/MessageDistributor/MessageDistributor.ts rename to frontend/app/player/web/MessageManager.ts index e1b59940a..72c07c445 100644 --- a/frontend/app/player/MessageDistributor/MessageDistributor.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -2,61 +2,25 @@ import { Decoder } from "syncod"; import logger from 'App/logger'; -import Resource, { TYPES } from 'Types/session/resource'; // MBTODO: player types? import { TYPES as EVENT_TYPES } from 'Types/session/event'; -import Log from 'Types/session/log'; +import { Log } from './types/log'; +import { Resource, ResourceType, getResourceFromResourceTiming, getResourceFromNetworkRequest } from './types/resource' -import { update } from '../store'; import { toast } from 'react-toastify'; -import StatedScreen from './StatedScreen/StatedScreen'; +import type { Store, Timed } from '../common/types'; +import ListWalker from '../common/ListWalker'; -import ListWalker from './managers/ListWalker'; import PagesManager from './managers/PagesManager'; import MouseMoveManager from './managers/MouseMoveManager'; import PerformanceTrackManager from './managers/PerformanceTrackManager'; import WindowNodeCounter from './managers/WindowNodeCounter'; import ActivityManager from './managers/ActivityManager'; -import AssistManager from './managers/AssistManager'; import MFileReader from './messages/MFileReader'; -import { loadFiles, requestEFSDom, requestEFSDevtools } from './network/loadFiles'; -import { decryptSessionBytes } from './network/crypto'; - -import { INITIAL_STATE as SUPER_INITIAL_STATE, State as SuperState } from './StatedScreen/StatedScreen'; -import { INITIAL_STATE as ASSIST_INITIAL_STATE, State as AssistState } from './managers/AssistManager'; -import Lists, { INITIAL_STATE as LISTS_INITIAL_STATE } from './Lists'; - -import type { PerformanceChartPoint } from './managers/PerformanceTrackManager'; -import type { SkipInterval } from './managers/ActivityManager'; - - -export interface State extends SuperState, AssistState { - performanceChartData: PerformanceChartPoint[], - skipIntervals: SkipInterval[], - connType?: string, - connBandwidth?: number, - location?: string, - performanceChartTime?: number, - - domContentLoadedTime?: any, - domBuildingTime?: any, - loadTime?: any, - error: boolean, - devtoolsLoading: boolean -} -export const INITIAL_STATE: State = { - ...SUPER_INITIAL_STATE, - ...LISTS_INITIAL_STATE, - ...ASSIST_INITIAL_STATE, - performanceChartData: [], - skipIntervals: [], - error: false, - devtoolsLoading: false, -}; - - +import { MType } from './messages'; +import { isDOMType } from './messages/filters.gen'; import type { Message, SetPageLocation, @@ -66,18 +30,69 @@ import type { MouseClick, } from './messages'; +import { loadFiles, requestEFSDom, requestEFSDevtools } from './network/loadFiles'; +import { decryptSessionBytes } from './network/crypto'; + +import Lists, { INITIAL_STATE as LISTS_INITIAL_STATE, State as ListsState } from './Lists'; + +import Screen, { + INITIAL_STATE as SCREEN_INITIAL_STATE, + State as ScreenState, +} from './Screen/Screen'; + +import type { InitialLists } from './Lists' +import type { PerformanceChartPoint } from './managers/PerformanceTrackManager'; +import type { SkipInterval } from './managers/ActivityManager'; + + +export interface State extends ScreenState, ListsState { + performanceChartData: PerformanceChartPoint[], + skipIntervals: SkipInterval[], + connType?: string, + connBandwidth?: number, + location?: string, + performanceChartTime?: number, + performanceAvaliability?: PerformanceTrackManager['avaliability'] + + domContentLoadedTime?: { time: number, value: number }, + domBuildingTime?: number, + loadTime?: { time: number, value: number }, + error: boolean, + devtoolsLoading: boolean, + + messagesLoading: boolean, + cssLoading: boolean, + + ready: boolean, + lastMessageTime: number, +} + + const visualChanges = [ - "mouse_move", - "mouse_click", - "create_element_node", - "set_input_value", - "set_input_checked", - "set_viewport_size", - "set_viewport_scroll", + MType.MouseMove, + MType.MouseClick, + MType.CreateElementNode, + MType.SetInputValue, + MType.SetInputChecked, + MType.SetViewportSize, + MType.SetViewportScroll, ] -export default class MessageDistributor extends StatedScreen { - // TODO: consistent with the other data-lists +export default class MessageManager { + static INITIAL_STATE: State = { + ...SCREEN_INITIAL_STATE, + ...LISTS_INITIAL_STATE, + performanceChartData: [], + skipIntervals: [], + error: false, + devtoolsLoading: false, + + messagesLoading: false, + cssLoading: false, + ready: false, + lastMessageTime: 0, + } + private locationEventManager: ListWalker<any>/*<LocationEvent>*/ = new ListWalker(); private locationManager: ListWalker<SetPageLocation> = new ListWalker(); private loadedLocationManager: ListWalker<SetPageLocation> = new ListWalker(); @@ -89,7 +104,6 @@ export default class MessageDistributor extends StatedScreen { private resizeManager: ListWalker<SetViewportSize> = new ListWalker([]); private pagesManager: PagesManager; private mouseMoveManager: MouseMoveManager; - private assistManager: AssistManager; private scrollManager: ListWalker<SetViewportScroll> = new ListWalker(); @@ -101,68 +115,47 @@ export default class MessageDistributor extends StatedScreen { private sessionStart: number; private navigationStartOffset: number = 0; private lastMessageTime: number = 0; - private lastMessageInFileTime: number = 0; - constructor(private readonly session: any /*Session*/, config: any, live: boolean) { - super(); - this.pagesManager = new PagesManager(this, this.session.isMobile) - this.mouseMoveManager = new MouseMoveManager(this); - this.assistManager = new AssistManager(session, this, config); + constructor( + private readonly session: any /*Session*/, + private readonly state: Store<State>, + private readonly screen: Screen, + initialLists?: Partial<InitialLists> + ) { + this.pagesManager = new PagesManager(screen, this.session.isMobile, this.setCSSLoading) + this.mouseMoveManager = new MouseMoveManager(screen) - this.sessionStart = this.session.startedAt; + this.sessionStart = this.session.startedAt - if (live) { - this.lists = new Lists() - this.assistManager.connect(this.session.agentToken); - } else { - this.activityManager = new ActivityManager(this.session.duration.milliseconds); - /* == REFACTOR_ME == */ - const eventList = session.events.toJSON(); - // TODO: fix types for events, remove immutable js - eventList.forEach((e: Record<string, string>) => { - if (e.type === EVENT_TYPES.LOCATION) { //TODO type system - this.locationEventManager.append(e); - } - }) + this.lists = new Lists(initialLists) + initialLists?.event?.forEach((e: Record<string, string>) => { // TODO: to one of "Moveable" module + if (e.type === EVENT_TYPES.LOCATION) { + this.locationEventManager.append(e); + } + }) - this.lists = new Lists({ - event: eventList, - stack: session.stackEvents.toJSON(), - resource: session.resources.toJSON(), - exceptions: session.errors.toJSON(), - }) - - /* === */ - this.loadMessages(); - } + this.activityManager = new ActivityManager(this.session.duration.milliseconds) // only if not-live } - private parseAndDistributeMessages(fileReader: MFileReader, onMessage?: (msg: Message) => void) { - const msgs: Array<Message> = [] - let next: ReturnType<MFileReader['next']> - while (next = fileReader.next()) { - const [msg, index] = next - this.distributeMessage(msg, index) - msgs.push(msg) - onMessage?.(msg) - } - - logger.info("Messages count: ", msgs.length, msgs) - + private setCSSLoading = (cssLoading: boolean) => { + this.screen.displayFrame(!cssLoading) + this.state.update({ cssLoading, ready: !this.state.get().messagesLoading && !cssLoading }) + } + private _sortMessagesHack(msgs: Message[]) { // @ts-ignore Hack for upet (TODO: fix ordering in one mutation in tracker(removes first)) const headChildrenIds = msgs.filter(m => m.parentID === 1).map(m => m.id); this.pagesManager.sortPages((m1, m2) => { if (m1.time === m2.time) { - if (m1.tp === "remove_node" && m2.tp !== "remove_node") { + if (m1.tp === MType.RemoveNode && m2.tp !== MType.RemoveNode) { if (headChildrenIds.includes(m1.id)) { return -1; } - } else if (m2.tp === "remove_node" && m1.tp !== "remove_node") { + } else if (m2.tp === MType.RemoveNode && m1.tp !== MType.RemoveNode) { if (headChildrenIds.includes(m2.id)) { return 1; } - } else if (m2.tp === "remove_node" && m1.tp === "remove_node") { + } else if (m2.tp === MType.RemoveNode && m1.tp === MType.RemoveNode) { const m1FromHead = headChildrenIds.includes(m1.id); const m2FromHead = headChildrenIds.includes(m2.id); if (m1FromHead && !m2FromHead) { @@ -176,38 +169,34 @@ export default class MessageDistributor extends StatedScreen { }) } - private waitingForFiles: boolean = false private onFileReadSuccess = () => { - const stateToUpdate = { + const stateToUpdate : Partial<State>= { performanceChartData: this.performanceTrackManager.chartData, performanceAvaliability: this.performanceTrackManager.avaliability, - ...this.lists.getFullListsState() + ...this.lists.getFullListsState(), } if (this.activityManager) { this.activityManager.end() stateToUpdate.skipIntervals = this.activityManager.list } - - update(stateToUpdate) + this.state.update(stateToUpdate) } private onFileReadFailed = (e: any) => { logger.error(e) - update({ error: true }) + this.state.update({ error: true }) toast.error('Error requesting a session file') } private onFileReadFinally = () => { - this.incomingMessages - .filter(msg => msg.time >= this.lastMessageInFileTime) - .forEach(msg => this.distributeMessage(msg, 0)) - this.waitingForFiles = false - this.setMessagesLoading(false) + // this.setMessagesLoading(false) + // this.state.update({ filesLoaded: true }) } - private loadMessages() { - // TODO: reuseable decryptor instance - const createNewParser = (shouldDecrypt=true) => { + async loadMessages(isClickmap: boolean = false) { + this.setMessagesLoading(true) + // TODO: reusable decryptor instance + const createNewParser = (shouldDecrypt = true) => { const decrypt = shouldDecrypt && this.session.fileKey ? (b: Uint8Array) => decryptSessionBytes(b, this.session.fileKey) : (b: Uint8Array) => Promise.resolve(b) @@ -215,77 +204,63 @@ export default class MessageDistributor extends StatedScreen { const fileReader = new MFileReader(new Uint8Array(), this.sessionStart) return (b: Uint8Array) => decrypt(b).then(b => { fileReader.append(b) - this.parseAndDistributeMessages(fileReader) + const msgs: Array<Message> = [] + for (let msg = fileReader.readNext();msg !== null;msg = fileReader.readNext()) { + this.distributeMessage(msg, msg._index) + msgs.push(msg) + } + + logger.info("Messages count: ", msgs.length, msgs) + this._sortMessagesHack(msgs) this.setMessagesLoading(false) }) } - this.setMessagesLoading(true) + this.waitingForFiles = true - loadFiles(this.session.domURL, createNewParser()) - .catch(() => // do if only the first file missing (404) (?) - requestEFSDom(this.session.sessionId) - .then(createNewParser(false)) - // Fallback to back Compatability with mobsUrl - .catch(e => - loadFiles(this.session.mobsUrl, createNewParser(false)) - ) - ) - .then(this.onFileReadSuccess) - .catch(this.onFileReadFailed) - .finally(this.onFileReadFinally) + const loadMethod = this.session.domURL && this.session.domURL.length > 0 + ? { url: this.session.domURL, parser: createNewParser } + : { url: this.session.mobsUrl, parser: () => createNewParser(false)} - // load devtools - if (this.session.devtoolsURL.length) { - update({ devtoolsLoading: true }) - loadFiles(this.session.devtoolsURL, createNewParser()) - .catch(() => - requestEFSDevtools(this.session.sessionId) + loadFiles(loadMethod.url, loadMethod.parser()) + // EFS fallback + .catch((e) => + requestEFSDom(this.session.sessionId) .then(createNewParser(false)) ) - //.catch() // not able to download the devtools file - .finally(() => update({ devtoolsLoading: false })) - } - } - - reloadWithUnprocessedFile() { - const onData = (byteArray: Uint8Array) => { - const onMessage = (msg: Message) => { this.lastMessageInFileTime = msg.time } - this.parseAndDistributeMessages(new MFileReader(byteArray, this.sessionStart), onMessage) - } - const updateState = () => - update({ - liveTimeTravel: true, - }); - - // assist will pause and skip messages to prevent timestamp related errors - this.reloadMessageManagers() - this.windowNodeCounter.reset() - - this.setMessagesLoading(true) - this.waitingForFiles = true - - return requestEFSDom(this.session.sessionId) - .then(onData) - .then(updateState) .then(this.onFileReadSuccess) .catch(this.onFileReadFailed) - .finally(this.onFileReadFinally) + .finally(this.onFileReadFinally); + + // load devtools (TODO: start after the first DOM file download) + if (isClickmap) return; + this.state.update({ devtoolsLoading: true }) + loadFiles(this.session.devtoolsURL, createNewParser()) + // EFS fallback + .catch(() => + requestEFSDevtools(this.session.sessionId) + .then(createNewParser(false)) + ) + .then(() => { + this.state.update(this.lists.getFullListsState()) // TODO: also in case of dynamic update through assist + }) + .catch(e => logger.error("Can not download the devtools file", e)) + .finally(() => this.state.update({ devtoolsLoading: false })) } - private reloadMessageManagers() { + resetMessageManagers() { this.locationEventManager = new ListWalker(); this.locationManager = new ListWalker(); this.loadedLocationManager = new ListWalker(); this.connectionInfoManger = new ListWalker(); this.clickManager = new ListWalker(); this.scrollManager = new ListWalker(); - this.resizeManager = new ListWalker([]); + this.resizeManager = new ListWalker(); this.performanceTrackManager = new PerformanceTrackManager() this.windowNodeCounter = new WindowNodeCounter(); - this.pagesManager = new PagesManager(this, this.session.isMobile) - this.mouseMoveManager = new MouseMoveManager(this); + this.pagesManager = new PagesManager(this.screen, this.session.isMobile, this.setCSSLoading) + this.mouseMoveManager = new MouseMoveManager(this.screen); this.activityManager = new ActivityManager(this.session.duration.milliseconds); } @@ -294,6 +269,7 @@ export default class MessageDistributor extends StatedScreen { /* == REFACTOR_ME == */ const lastLoadedLocationMsg = this.loadedLocationManager.moveGetLast(t, index); if (!!lastLoadedLocationMsg) { + // TODO: page-wise resources list // setListsStartTime(lastLoadedLocationMsg.time) this.navigationStartOffset = lastLoadedLocationMsg.navigationStart - this.sessionStart; } const llEvent = this.locationEventManager.moveGetLast(t, index); @@ -330,7 +306,7 @@ export default class MessageDistributor extends StatedScreen { } Object.assign(stateToUpdate, this.lists.moveGetState(t)) - Object.keys(stateToUpdate).length > 0 && update(stateToUpdate); + Object.keys(stateToUpdate).length > 0 && this.state.update(stateToUpdate); /* Sequence of the managers is important here */ // Preparing the size of "screen" @@ -341,20 +317,18 @@ export default class MessageDistributor extends StatedScreen { this.pagesManager.moveReady(t).then(() => { const lastScroll = this.scrollManager.moveGetLast(t, index); - if (!!lastScroll && this.window) { - this.window.scrollTo(lastScroll.x, lastScroll.y); + if (!!lastScroll && this.screen.window) { + this.screen.window.scrollTo(lastScroll.x, lastScroll.y); } // Moving mouse and setting :hover classes on ready view this.mouseMoveManager.move(t); const lastClick = this.clickManager.moveGetLast(t); if (!!lastClick && t - lastClick.time < 600) { // happend during last 600ms - this.cursor.click(); + this.screen.cursor.click(); } - // After all changes - redraw the marker - //this.marker.redraw(); }) - if (this.waitingForFiles && this.lastMessageTime <= t) { + if (this.waitingForFiles && this.lastMessageTime <= t && t !== this.session.duration.milliseconds) { this.setMessagesLoading(true) } } @@ -373,108 +347,90 @@ export default class MessageDistributor extends StatedScreen { return { ...msg, ...decoded }; } - private readonly incomingMessages: Message[] = [] - appendMessage(msg: Message, index: number) { - // @ts-ignore - // msg.time = this.md.getLastRecordedMessageTime() + msg.time\ - //TODO: put index in message type - this.incomingMessages.push(msg) - if (!this.waitingForFiles) { - this.distributeMessage(msg, index) - } - } - - private distributeMessage(msg: Message, index: number): void { + distributeMessage(msg: Message, index: number): void { const lastMessageTime = Math.max(msg.time, this.lastMessageTime) this.lastMessageTime = lastMessageTime + this.state.update({ lastMessageTime }) if (visualChanges.includes(msg.tp)) { this.activityManager?.updateAcctivity(msg.time); } let decoded; const time = msg.time; switch (msg.tp) { - /* Lists: */ - case "console_log": - if (msg.level === 'debug') break; - this.lists.lists.log.append(Log({ - level: msg.level, - value: msg.value, - time, - index, - })) - break; - case "fetch": - this.lists.lists.fetch.append(Resource({ - method: msg.method, - url: msg.url, - payload: msg.request, - response: msg.response, - status: msg.status, - duration: msg.duration, - type: TYPES.FETCH, - time: msg.timestamp - this.sessionStart, //~ - index, - })); - break; - /* */ - case "set_page_location": + case MType.SetPageLocation: this.locationManager.append(msg); if (msg.navigationStart > 0) { this.loadedLocationManager.append(msg); } break; - case "set_viewport_size": + case MType.SetViewportSize: this.resizeManager.append(msg); break; - case "mouse_move": + case MType.MouseMove: this.mouseMoveManager.append(msg); break; - case "mouse_click": + case MType.MouseClick: this.clickManager.append(msg); break; - case "set_viewport_scroll": + case MType.SetViewportScroll: this.scrollManager.append(msg); break; - case "performance_track": + case MType.PerformanceTrack: this.performanceTrackManager.append(msg); break; - case "set_page_visibility": + case MType.SetPageVisibility: this.performanceTrackManager.handleVisibility(msg) break; - case "connection_information": + case MType.ConnectionInformation: this.connectionInfoManger.append(msg); break; - case "o_table": + case MType.OTable: this.decoder.set(msg.key, msg.value); break; - case "redux": + /* Lists: */ + case MType.ConsoleLog: + if (msg.level === 'debug') break; + this.lists.lists.log.append( + // @ts-ignore : TODO: enums in the message schema + Log(msg) + ) + break; + case MType.ResourceTiming: + // TODO: merge `resource` and `fetch` lists into one here instead of UI + this.lists.lists.resource.insert(getResourceFromResourceTiming(msg, this.sessionStart)) + break; + case MType.Fetch: + case MType.NetworkRequest: + this.lists.lists.fetch.insert(getResourceFromNetworkRequest(msg, this.sessionStart)) + break; + case MType.Redux: decoded = this.decodeStateMessage(msg, ["state", "action"]); logger.log('redux', decoded) if (decoded != null) { this.lists.lists.redux.append(decoded); } break; - case "ng_rx": + case MType.NgRx: decoded = this.decodeStateMessage(msg, ["state", "action"]); logger.log('ngrx', decoded) if (decoded != null) { this.lists.lists.ngrx.append(decoded); } break; - case "vuex": + case MType.Vuex: decoded = this.decodeStateMessage(msg, ["state", "mutation"]); logger.log('vuex', decoded) if (decoded != null) { this.lists.lists.vuex.append(decoded); } break; - case "zustand": + case MType.Zustand: decoded = this.decodeStateMessage(msg, ["state", "mutation"]) logger.log('zustand', decoded) if (decoded != null) { this.lists.lists.zustand.append(decoded) } - case "mob_x": + case MType.MobX: decoded = this.decodeStateMessage(msg, ["payload"]); logger.log('mobx', decoded) @@ -482,52 +438,52 @@ export default class MessageDistributor extends StatedScreen { this.lists.lists.mobx.append(decoded); } break; - case "graph_ql": + case MType.GraphQl: this.lists.lists.graphql.append(msg); break; - case "profiler": + case MType.Profiler: this.lists.lists.profiles.append(msg); break; + /* ===|=== */ default: switch (msg.tp) { - case "create_document": + case MType.CreateDocument: this.windowNodeCounter.reset(); this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); break; - case "create_text_node": - case "create_element_node": + case MType.CreateTextNode: + case MType.CreateElementNode: this.windowNodeCounter.addNode(msg.id, msg.parentID); this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); break; - case "move_node": + case MType.MoveNode: this.windowNodeCounter.moveNode(msg.id, msg.parentID); this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); break; - case "remove_node": + case MType.RemoveNode: this.windowNodeCounter.removeNode(msg.id); this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); break; } this.performanceTrackManager.addNodeCountPointIfNeed(msg.time) - this.pagesManager.appendMessage(msg); + isDOMType(msg.tp) && this.pagesManager.appendMessage(msg) break; } } - getLastMessageTime(): number { - return this.lastMessageTime; + setMessagesLoading(messagesLoading: boolean) { + this.screen.display(!messagesLoading); + this.state.update({ messagesLoading, ready: !messagesLoading && !this.state.get().cssLoading }); } - getFirstMessageTime(): number { - return this.pagesManager.minTime; + private setSize({ height, width }: { height: number, width: number }) { + this.screen.scale({ height, width }); + this.state.update({ width, height }); } // TODO: clean managers? clean() { - super.clean(); - update(INITIAL_STATE); - this.assistManager.clear(); - this.incomingMessages.length = 0 + this.state.update(MessageManager.INITIAL_STATE); } } diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/Cursor.ts b/frontend/app/player/web/Screen/Cursor.ts similarity index 54% rename from frontend/app/player/MessageDistributor/StatedScreen/Screen/Cursor.ts rename to frontend/app/player/web/Screen/Cursor.ts index 54ea414fd..f2d371062 100644 --- a/frontend/app/player/MessageDistributor/StatedScreen/Screen/Cursor.ts +++ b/frontend/app/player/web/Screen/Cursor.ts @@ -4,12 +4,15 @@ import styles from './cursor.module.css'; export default class Cursor { private readonly cursor: HTMLDivElement; - private nameElement: HTMLDivElement; - private readonly position: Point = { x: -1, y: -1 } - constructor(overlay: HTMLDivElement) { + private tagElement: HTMLDivElement; + private isMobile: boolean; + + constructor(overlay: HTMLDivElement, isMobile: boolean) { this.cursor = document.createElement('div'); this.cursor.className = styles.cursor; + if (isMobile) this.cursor.style.backgroundImage = 'unset' overlay.appendChild(this.cursor); + this.isMobile = isMobile; } toggle(flag: boolean) { @@ -20,10 +23,10 @@ export default class Cursor { } } - toggleUserName(name?: string) { - if (!this.nameElement) { - this.nameElement = document.createElement('div') - Object.assign(this.nameElement.style, { + showTag(tag?: string) { + if (!this.tagElement) { + this.tagElement = document.createElement('div') + Object.assign(this.tagElement.style, { position: 'absolute', padding: '4px 6px', borderRadius: '8px', @@ -34,29 +37,28 @@ export default class Cursor { fontSize: '12px', whiteSpace: 'nowrap', }) - this.cursor.appendChild(this.nameElement) + this.cursor.appendChild(this.tagElement) } - if (!name) { - this.nameElement.style.display = 'none' + if (!tag) { + this.tagElement.style.display = 'none' } else { - this.nameElement.style.display = 'block' - const nameStr = name ? name.length > 10 ? name.slice(0, 9) + '...' : name : 'User' - this.nameElement.innerHTML = `<span>${nameStr}</span>` + this.tagElement.style.display = 'block' + const nameStr = tag.length > 10 ? tag.slice(0, 9) + '...' : tag + this.tagElement.innerHTML = `<span>${nameStr}</span>` } } move({ x, y }: Point) { - this.position.x = x; - this.position.y = y; this.cursor.style.left = x + 'px'; this.cursor.style.top = y + 'px'; } click() { - this.cursor.classList.add(styles.clicked) + const styleList = this.isMobile ? styles.clickedMobile : styles.clicked + this.cursor.classList.add(styleList) setTimeout(() => { - this.cursor.classList.remove(styles.clicked) + this.cursor.classList.remove(styleList) }, 600) } @@ -64,8 +66,4 @@ export default class Cursor { // transition // setTransitionSpeed() - getPosition(): Point { - return { x: this.position.x, y: this.position.y }; - } - } diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/Inspector.js b/frontend/app/player/web/Screen/Inspector.ts similarity index 51% rename from frontend/app/player/MessageDistributor/StatedScreen/Screen/Inspector.js rename to frontend/app/player/web/Screen/Inspector.ts index 98ba5ec0f..028f7f919 100644 --- a/frontend/app/player/MessageDistributor/StatedScreen/Screen/Inspector.js +++ b/frontend/app/player/web/Screen/Inspector.ts @@ -1,15 +1,14 @@ -//import { select } from 'optimal-select'; +import type Screen from './Screen' +import type Marker from './Marker' -export default class Inspector { - //private callbacks; - captureCallbacks = []; - bubblingCallbacks = []; - constructor(screen, marker) { - this.screen = screen; - this.marker = marker; - } +//import { select } from 'optimal-select'; - _onMouseMove = (e) => { +export default class Inspector { + // private captureCallbacks = []; + // private bubblingCallbacks = []; + constructor(private screen: Screen, private marker: Marker) {} + + private onMouseMove = (e: MouseEvent) => { // const { overlay } = this.screen; // if (!overlay.contains(e.target)) { // return; @@ -22,14 +21,14 @@ export default class Inspector { return; } - this.marker.mark(target); + this.marker.mark(target); } - _onOverlayLeave = () => { + private onOverlayLeave = () => { return this.marker.unmark(); } - _onMarkClick = () => { + private onMarkClick = () => { let target = this.marker.target; if (!target) { return @@ -57,16 +56,15 @@ export default class Inspector { // } // } - toggle(flag, clickCallback) { - this.clickCallback = clickCallback; - if (flag) { - this.screen.overlay.addEventListener('mousemove', this._onMouseMove); - this.screen.overlay.addEventListener('mouseleave', this._onOverlayLeave); - this.screen.overlay.addEventListener('click', this._onMarkClick); - } else { - this.screen.overlay.removeEventListener('mousemove', this._onMouseMove); - this.screen.overlay.removeEventListener('mouseleave', this._onOverlayLeave); - this.screen.overlay.removeEventListener('click', this._onMarkClick); - } + private clickCallback: (e: { target: Element }) => void | null = null + enable(clickCallback?: Inspector['clickCallback']) { + this.screen.overlay.addEventListener('mousemove', this.onMouseMove) + this.screen.overlay.addEventListener('mouseleave', this.onOverlayLeave) + this.screen.overlay.addEventListener('click', this.onMarkClick) } -} \ No newline at end of file + clean() { + this.screen.overlay.removeEventListener('mousemove', this.onMouseMove) + this.screen.overlay.removeEventListener('mouseleave', this.onOverlayLeave) + this.screen.overlay.removeEventListener('click', this.onMarkClick) + } +} diff --git a/frontend/app/player/web/Screen/Marker.ts b/frontend/app/player/web/Screen/Marker.ts new file mode 100644 index 000000000..d55ed5be3 --- /dev/null +++ b/frontend/app/player/web/Screen/Marker.ts @@ -0,0 +1,151 @@ +import type Screen from './Screen' +import styles from './marker.module.css'; + +const metaCharsMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' +}; + +function escapeHtml(str: string) { + return String(str).replace(/[&<>"'`=\/]/g, function (s) { + // @ts-ignore + return metaCharsMap[s]; + }); +} + + +function escapeRegExp(string: string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +} + +function safeString(string: string) { + return (escapeHtml(escapeRegExp(string))) +} + +export default class Marker { + private _target: Element | null = null; + private selector: string | null = null; + private tooltip: HTMLDivElement + private marker: HTMLDivElement + + constructor(overlay: HTMLElement, private readonly screen: Screen) { + this.tooltip = document.createElement('div'); + this.tooltip.className = styles.tooltip; + this.tooltip.appendChild(document.createElement('div')); + + const htmlStr = document.createElement('div'); + htmlStr.innerHTML = '<b>Right-click > Inspect</b> for more details.'; + this.tooltip.appendChild(htmlStr); + + const marker = document.createElement('div'); + marker.className = styles.marker; + const markerL = document.createElement('div'); + const markerR = document.createElement('div'); + const markerT = document.createElement('div'); + const markerB = document.createElement('div'); + markerL.className = styles.markerL; + markerR.className = styles.markerR; + markerT.className = styles.markerT; + markerB.className = styles.markerB; + marker.appendChild(markerL); + marker.appendChild(markerR); + marker.appendChild(markerT); + marker.appendChild(markerB); + + marker.appendChild(this.tooltip); + + overlay.appendChild(marker); + this.marker = marker; + } + + get target() { + return this._target; + } + + mark(element: Element | null) { + if (this._target === element) { + return; + } + this._target = element; + this.selector = null; + this.redraw(); + } + + unmark() { + this.mark(null) + } + + private autodefineTarget() { + // TODO: put to Screen + if (this.selector) { + try { + const fitTargets = this.screen.document.querySelectorAll(this.selector); + if (fitTargets.length === 0) { + this._target = null; + } else { + // TODO: fix getCursorTarget()? + // this._target = fitTargets[0]; + // const cursorTarget = this.screen.getCursorTarget(); + // fitTargets.forEach((target) => { + // if (target.contains(cursorTarget)) { + // this._target = target; + // } + // }); + } + } catch (e) { + console.info(e); + } + } else { + this._target = null; + } + } + + markBySelector(selector: string) { + this.selector = selector; + this.autodefineTarget(); + this.redraw(); + } + + private getTagString(el: Element) { + const attrs = el.attributes; + let str = `<span style="color:#9BBBDC">${el.tagName.toLowerCase()}</span>`; + + for (let i = 0; i < attrs.length; i++) { + let k = attrs[i]; + const attribute = k.name; + if (attribute === 'class') { + str += `<span style="color:#F29766">${'.' + safeString(k.value).split(' ').join('.')}</span>`; + } + + if (attribute === 'id') { + str += `<span style="color:#F29766">${'#' + safeString(k.value).split(' ').join('#')}</span>`; + } + } + + return str; + } + + redraw() { + if (this.selector) { + this.autodefineTarget(); + } + if (!this._target) { + this.marker.style.display = 'none'; + return; + } + const rect = this._target.getBoundingClientRect(); + this.marker.style.display = 'block'; + this.marker.style.left = rect.left + 'px'; + this.marker.style.top = rect.top + 'px'; + this.marker.style.width = rect.width + 'px'; + this.marker.style.height = rect.height + 'px'; + + this.tooltip.firstChild.innerHTML = this.getTagString(this._target); + } +} diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/BaseScreen.ts b/frontend/app/player/web/Screen/Screen.ts similarity index 68% rename from frontend/app/player/MessageDistributor/StatedScreen/Screen/BaseScreen.ts rename to frontend/app/player/web/Screen/Screen.ts index d86284851..cca56d402 100644 --- a/frontend/app/player/MessageDistributor/StatedScreen/Screen/BaseScreen.ts +++ b/frontend/app/player/web/Screen/Screen.ts @@ -1,13 +1,10 @@ -import styles from './screen.module.css'; -import { getState } from '../../../store'; +import styles from './screen.module.css' +import Cursor from './Cursor' -import type { Point } from './types'; +import type { Point, Dimensions } from './types'; -export interface State { - width: number; - height: number; -} +export type State = Dimensions export const INITIAL_STATE: State = { width: 0, @@ -15,6 +12,13 @@ export const INITIAL_STATE: State = { } +export enum ScaleMode { + Embed, + //AdjustParentWidth + AdjustParentHeight, +} + + function getElementsFromInternalPoint(doc: Document, { x, y }: Point): Element[] { // @ts-ignore (IE, Edge) if (typeof doc.msElementsFromRect === 'function') { @@ -52,15 +56,15 @@ function isIframe(el: Element): el is HTMLIFrameElement { return el.tagName === "IFRAME" } -export default abstract class BaseScreen { - public readonly overlay: HTMLDivElement; +export default class Screen { + readonly overlay: HTMLDivElement + readonly cursor: Cursor private readonly iframe: HTMLIFrameElement; - protected readonly screen: HTMLDivElement; - protected readonly controlButton: HTMLDivElement; - protected parentElement: HTMLElement | null = null; + private readonly screen: HTMLDivElement; + private parentElement: HTMLElement | null = null - constructor() { + constructor(isMobile: boolean, private scaleMode: ScaleMode = ScaleMode.Embed) { const iframe = document.createElement('iframe'); iframe.className = styles.iframe; this.iframe = iframe; @@ -75,20 +79,18 @@ export default abstract class BaseScreen { screen.appendChild(iframe); screen.appendChild(overlay); this.screen = screen; + + this.cursor = new Cursor(this.overlay, isMobile) // TODO: move outside } attach(parentElement: HTMLElement) { if (this.parentElement) { - this.parentElement = undefined - console.error("BaseScreen: Trying to attach an attached screen."); + this.parentElement = null + console.warn("BaseScreen: reattaching the screen."); } parentElement.appendChild(this.screen); - this.parentElement = parentElement; - // parentElement.onresize = this.scale; - window.addEventListener('resize', this.scale); - this.scale(); /* == For the Inspecting Document content == */ this.overlay.addEventListener('contextmenu', () => { @@ -105,9 +107,12 @@ export default abstract class BaseScreen { }) } - toggleRemoteControlStatus(isEnabled: boolean ) { - const styles = isEnabled ? { border: '2px dashed blue' } : { border: 'unset'} - return Object.assign(this.screen.style, styles) + getParentElement(): HTMLElement | null { + return this.parentElement + } + + setBorderStyle(style: { border: string }) { + return Object.assign(this.screen.style, style) } get window(): WindowProxy | null { @@ -118,9 +123,14 @@ export default abstract class BaseScreen { return this.iframe.contentDocument; } + get iframeStylesRef(): CSSStyleDeclaration { + return this.iframe.style + } + private boundingRect: DOMRect | null = null; private getBoundingClientRect(): DOMRect { if (this.boundingRect === null) { + // TODO: use this.screen instead in order to separate overlay functionality return this.boundingRect = this.overlay.getBoundingClientRect() // expensive operation? } return this.boundingRect @@ -169,15 +179,11 @@ export default abstract class BaseScreen { return this.getElementFromInternalPoint(this.getInternalViewportCoordinates(point)); } - getElementsFromPoint(point: Point): Element[] { - return this.getElementsFromInternalPoint(this.getInternalViewportCoordinates(point)); - } - - getElementBySelector(selector: string): Element | null { + getElementBySelector(selector: string) { if (!selector) return null; try { const safeSelector = selector.replace(/:/g, '\\\\3A ').replace(/\//g, '\\/'); - return this.document?.querySelector(safeSelector) || null; + return this.document?.querySelector<HTMLElement>(safeSelector) || null; } catch (e) { console.error("Can not select element. ", e) return null @@ -192,37 +198,49 @@ export default abstract class BaseScreen { this.iframe.style.display = flag ? '' : 'none'; } - private s: number = 1; + private scaleRatio: number = 1; getScale() { - return this.s; + return this.scaleRatio; } - _scale() { + scale({ height, width }: Dimensions) { if (!this.parentElement) return; - const { height, width } = getState(); const { offsetWidth, offsetHeight } = this.parentElement; - this.s = Math.min(offsetWidth / width, offsetHeight / height); - if (this.s > 1) { - this.s = 1; - } else { - this.s = Math.round(this.s * 1e3) / 1e3; + let translate = "" + let posStyles = {} + switch (this.scaleMode) { + case ScaleMode.Embed: + this.scaleRatio = Math.min(offsetWidth / width, offsetHeight / height) + translate = "translate(-50%, -50%)" + posStyles = { height: height + 'px' } + break; + case ScaleMode.AdjustParentHeight: + this.scaleRatio = offsetWidth / width + translate = "translate(-50%, 0)" + posStyles = { top: 0, height: this.document!.documentElement.getBoundingClientRect().height + 'px', } + break; } - this.screen.style.transform = `scale(${ this.s }) translate(-50%, -50%)`; - this.screen.style.width = width + 'px'; - this.screen.style.height = height + 'px'; - this.iframe.style.width = width + 'px'; - this.iframe.style.height = height + 'px'; + + if (this.scaleRatio > 1) { + this.scaleRatio = 1; + } else { + this.scaleRatio = Math.round(this.scaleRatio * 1e3) / 1e3; + } + + if (this.scaleMode === ScaleMode.AdjustParentHeight) { + this.parentElement.style.height = this.scaleRatio * height + 'px' + } + + Object.assign(this.screen.style, posStyles, { + width: width + 'px', + transform: `scale(${this.scaleRatio}) ${translate}`, + }) + Object.assign(this.iframe.style, posStyles, { + width: width + 'px', + }) this.boundingRect = this.overlay.getBoundingClientRect(); } - scale = () => { // TODO: solve classes inheritance issues in typescript - this._scale(); - } - - - clean() { - window.removeEventListener('resize', this.scale); - } } diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/cursor.module.css b/frontend/app/player/web/Screen/cursor.module.css similarity index 70% rename from frontend/app/player/MessageDistributor/StatedScreen/Screen/cursor.module.css rename to frontend/app/player/web/Screen/cursor.module.css index 7a94c99b8..93f3d05ff 100644 --- a/frontend/app/player/MessageDistributor/StatedScreen/Screen/cursor.module.css +++ b/frontend/app/player/web/Screen/cursor.module.css @@ -67,3 +67,44 @@ transform: scale3d(1.2, 1.2, 1); } } + +.cursor.clickedMobile::after { + -webkit-animation: anim-effect-sanja 1s ease-out forwards; + animation: anim-effect-sanja 1s ease-out forwards; +} + +@-webkit-keyframes anim-effect-sanja { + 0% { + opacity: 1; + -webkit-transform: scale3d(0.5, 0.5, 1); + transform: scale3d(0.5, 0.5, 1); + } + 25% { + opacity: 1; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + 100% { + opacity: 0; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes anim-effect-sanja { + 0% { + opacity: 1; + -webkit-transform: scale3d(0.5, 0.5, 1); + transform: scale3d(0.5, 0.5, 1); + } + 25% { + opacity: 1; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + 100% { + opacity: 0; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/marker.module.css b/frontend/app/player/web/Screen/marker.module.css similarity index 100% rename from frontend/app/player/MessageDistributor/StatedScreen/Screen/marker.module.css rename to frontend/app/player/web/Screen/marker.module.css diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/screen.module.css b/frontend/app/player/web/Screen/screen.module.css similarity index 100% rename from frontend/app/player/MessageDistributor/StatedScreen/Screen/screen.module.css rename to frontend/app/player/web/Screen/screen.module.css diff --git a/frontend/app/player/web/Screen/types.ts b/frontend/app/player/web/Screen/types.ts new file mode 100644 index 000000000..093649892 --- /dev/null +++ b/frontend/app/player/web/Screen/types.ts @@ -0,0 +1,9 @@ +export interface Point { + x: number + y: number +} + +export interface Dimensions { + width: number + height: number +} diff --git a/frontend/app/player/web/WebLivePlayer.ts b/frontend/app/player/web/WebLivePlayer.ts new file mode 100644 index 000000000..709692d20 --- /dev/null +++ b/frontend/app/player/web/WebLivePlayer.ts @@ -0,0 +1,95 @@ +import type { Store } from '../common/types' +import type { Message } from './messages' + +import WebPlayer from './WebPlayer' +import AssistManager from './assist/AssistManager' + +import MFileReader from './messages/MFileReader' +import { requestEFSDom } from './network/loadFiles' + +import { toast } from 'react-toastify'; // ** + + +export default class WebLivePlayer extends WebPlayer { + static readonly INITIAL_STATE = { + ...WebPlayer.INITIAL_STATE, + ...AssistManager.INITIAL_STATE, + liveTimeTravel: false, + } + + assistManager: AssistManager // public so far + private readonly incomingMessages: Message[] = [] + private historyFileIsLoading = false + private lastMessageInFileTime = 0 + private lastMessageInFileIndex = 0 + + constructor(wpState: Store<typeof WebLivePlayer.INITIAL_STATE>, private session:any, config: RTCIceServer[]) { + super(wpState, session, true) + + this.assistManager = new AssistManager( + session, + f => this.messageManager.setMessagesLoading(f), + (msg, idx) => { + this.incomingMessages.push(msg) + if (!this.historyFileIsLoading) { + // TODO: fix index-ing after historyFile-load + this.messageManager.distributeMessage(msg, idx) + } + }, + this.screen, + config, + wpState, + ) + this.assistManager.connect(session.agentToken) + } + + toggleTimetravel = async () => { + if (this.wpState.get().liveTimeTravel) { + return + } + let result = false; + this.historyFileIsLoading = true + this.messageManager.setMessagesLoading(true) // do it in one place. update unique loading states each time instead + this.messageManager.resetMessageManagers() + + try { + const bytes = await requestEFSDom(this.session.sessionId) + const fileReader = new MFileReader(bytes, this.session.startedAt) + for (let msg = fileReader.readNext();msg !== null;msg = fileReader.readNext()) { + this.messageManager.distributeMessage(msg, msg._index) + } + this.wpState.update({ + liveTimeTravel: true, + }) + result = true + // here we need to update also lists state, if we gonna use them this.messageManager.onFileReadSuccess + } catch(e) { + toast.error('Error requesting a session file') + console.error("EFS file download error:", e) + } + + // Append previously received messages + this.incomingMessages + .filter(msg => msg.time >= this.lastMessageInFileTime) + .forEach((msg, i) => this.messageManager.distributeMessage(msg, this.lastMessageInFileIndex + i)) + this.incomingMessages.length = 0 + + this.historyFileIsLoading = false + this.messageManager.setMessagesLoading(false) + return result; + } + + jumpToLive = () => { + this.wpState.update({ + live: true, + livePlay: true, + }) + this.jump(this.wpState.get().lastMessageTime) + } + + clean = () => { + this.incomingMessages.length = 0 + this.assistManager.clean() + super.clean() + } +} \ No newline at end of file diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts new file mode 100644 index 000000000..d1a56f9fd --- /dev/null +++ b/frontend/app/player/web/WebPlayer.ts @@ -0,0 +1,132 @@ +import { Log, LogLevel } from './types/log' + +import type { Store } from 'App/player' +import Player from '../player/Player' + +import MessageManager from './MessageManager' +import InspectorController from './addons/InspectorController' +import TargetMarker from './addons/TargetMarker' +import Screen, { ScaleMode } from './Screen/Screen' + + +// export type State = typeof WebPlayer.INITIAL_STATE + +export default class WebPlayer extends Player { + static readonly INITIAL_STATE = { + ...Player.INITIAL_STATE, + ...TargetMarker.INITIAL_STATE, + ...MessageManager.INITIAL_STATE, + + inspectorMode: false, + } + + private readonly inspectorController: InspectorController + protected readonly screen: Screen + protected readonly messageManager: MessageManager + + private targetMarker: TargetMarker + + constructor(protected wpState: Store<typeof WebPlayer.INITIAL_STATE>, session: any, live: boolean, isClickMap = false) { + let initialLists = live ? {} : { + event: session.events || [], + stack: session.stackEvents || [], + exceptions: session.errors?.map(({ name, ...rest }: any) => + Log({ + level: LogLevel.ERROR, + value: name, + ...rest, + }) + ) || [], + } + + const screen = new Screen(session.isMobile, isClickMap ? ScaleMode.AdjustParentHeight : ScaleMode.Embed) + const messageManager = new MessageManager(session, wpState, screen, initialLists) + super(wpState, messageManager) + this.screen = screen + this.messageManager = messageManager + if (!live) { // hack. TODO: split OfflinePlayer class + void messageManager.loadMessages(isClickMap) + } + + this.targetMarker = new TargetMarker(this.screen, wpState) + this.inspectorController = new InspectorController(screen) + + + const endTime = session.duration?.valueOf() || 0 + wpState.update({ + //@ts-ignore + session, + + live, + livePlay: live, + endTime, // : 0, + }) + + // @ts-ignore + window.playerJumpToTime = this.jump.bind(this) + + } + + attach = (parent: HTMLElement, isClickmap?: boolean) => { + this.screen.attach(parent) + if (!isClickmap) { + window.addEventListener('resize', this.scale) + this.scale() + } + } + + scale = () => { + const { width, height } = this.wpState.get() + this.screen.scale({ width, height }) + this.inspectorController.scale({ width, height }) + + this.targetMarker.updateMarkedTargets() + } + + // Inspector & marker + mark(e: Element) { + this.inspectorController.marker?.mark(e) + } + + toggleInspectorMode = (flag: boolean, clickCallback?: Parameters<InspectorController['enableInspector']>[0]) => { + if (typeof flag !== 'boolean') { + const { inspectorMode } = this.wpState.get() + flag = !inspectorMode + } + + if (flag) { + this.pause() + this.wpState.update({ inspectorMode: true }) + return this.inspectorController.enableInspector(clickCallback) + } else { + this.inspectorController.disableInspector() + this.wpState.update({ inspectorMode: false }) + } + } + + // Target Marker + setActiveTarget = (...args: Parameters<TargetMarker['setActiveTarget']>) => { + this.targetMarker.setActiveTarget(...args) + } + + markTargets = (...args: Parameters<TargetMarker['markTargets']>) => { + this.pause() + this.targetMarker.markTargets(...args) + } + + showClickmap = (...args: Parameters<TargetMarker['injectTargets']>) => { + this.screen.overlay.remove() // hack. TODO: 1.split Screen functionalities (overlay, mounter) 2. separate ClickMapPlayer class that does not create overlay + this.freeze().then(() => { + this.targetMarker.injectTargets(...args) + }) + } + + toggleUserName = (name?: string) => { + this.screen.cursor.showTag(name) + } + + clean = () => { + super.clean() + window.removeEventListener('resize', this.scale) + } +} diff --git a/frontend/app/player/web/addons/InspectorController.ts b/frontend/app/player/web/addons/InspectorController.ts new file mode 100644 index 000000000..987b9b0bc --- /dev/null +++ b/frontend/app/player/web/addons/InspectorController.ts @@ -0,0 +1,59 @@ +import Marker from '../Screen/Marker' +import Inspector from '../Screen/Inspector' +import Screen from '../Screen/Screen' +import type { Dimensions } from '../Screen/types' + + +export default class InspectorController { + private substitutor: Screen | null = null + private inspector: Inspector | null = null + marker: Marker | null = null + constructor(private screen: Screen) {} + + scale(dims: Dimensions) { + if (this.substitutor) { + this.substitutor.scale(dims) + } + } + + enableInspector(clickCallback?: (e: { target: Element }) => void): Document | null { + const parent = this.screen.getParentElement() + if (!parent) return null; + if (!this.substitutor) { + this.substitutor = new Screen() + this.marker = new Marker(this.substitutor.overlay, this.substitutor) + this.inspector = new Inspector(this.substitutor, this.marker) + //this.inspector.addClickListener(clickCallback, true) + this.substitutor.attach(parent) + } + + this.substitutor.display(false) + + const docElement = this.screen.document?.documentElement // this.substitutor.document?.importNode( + const doc = this.substitutor.document + if (doc && docElement) { + doc.open() + doc.write(docElement.outerHTML) + doc.close() + + // TODO! : copy stylesheets & cssRules? + } + this.screen.display(false); + this.inspector.enable(clickCallback); + this.substitutor.display(true); + return doc; + } + + disableInspector() { + if (this.substitutor) { + const doc = this.substitutor.document; + if (doc) { + doc.documentElement.innerHTML = ""; + } + this.inspector.clean(); + this.substitutor.display(false); + } + this.screen.display(true); + } + +} diff --git a/frontend/app/player/web/addons/TargetMarker.ts b/frontend/app/player/web/addons/TargetMarker.ts new file mode 100644 index 000000000..6629ceaec --- /dev/null +++ b/frontend/app/player/web/addons/TargetMarker.ts @@ -0,0 +1,260 @@ +import type Screen from '../Screen/Screen' +import type { Point } from '../Screen/types' +import type { Store } from '../../common/types' +import { clickmapStyles } from './clickmapStyles' + +const zIndexMap = { + 400: 3, + 200: 4, + 100: 5, + 50: 6 +} +const widths = Object.keys(zIndexMap) + .map(s => parseInt(s, 10)) + .sort((a,b) => b - a) as [400, 200, 100, 50] + +function getOffset(el: Element, innerWindow: Window) { + const rect = el.getBoundingClientRect(); + return { + fixedLeft: rect.left + innerWindow.scrollX, + fixedTop: rect.top + innerWindow.scrollY, + rect, + }; +} + +interface BoundingRect { + top: number, + left: number, + width: number, + height: number, +} + +export interface MarkedTarget { + boundingRect: BoundingRect, + el: Element, + selector: string, + count: number, + index: number, + active?: boolean, + percent: number +} + +export interface State { + markedTargets: MarkedTarget[] | null, + activeTargetIndex: number, +} + + +export default class TargetMarker { + private clickMapOverlay: HTMLDivElement | null = null + private clickContainers: HTMLDivElement[] = [] + private smallClicks: HTMLDivElement[] = [] + static INITIAL_STATE: State = { + markedTargets: null, + activeTargetIndex: 0 + } + + constructor( + private readonly screen: Screen, + private readonly store: Store<State>, + ) {} + + updateMarkedTargets() { + const { markedTargets } = this.store.get() + if (markedTargets) { + this.store.update({ + markedTargets: markedTargets.map((mt: any) => ({ + ...mt, + boundingRect: this.calculateRelativeBoundingRect(mt.el), + })), + }); + } + } + + private calculateRelativeBoundingRect(el: Element): BoundingRect { + const parentEl = this.screen.getParentElement() + if (!parentEl) return {top:0, left:0, width:0,height:0} //TODO: can be initialized(?) on mounted screen only + const { top, left, width, height } = el.getBoundingClientRect() + const s = this.screen.getScale() + const screenRect = this.screen.overlay.getBoundingClientRect() //this.screen.getBoundingClientRect() (now private) + const parentRect = parentEl.getBoundingClientRect() + + return { + top: top*s + screenRect.top - parentRect.top, + left: left*s + screenRect.left - parentRect.left, + width: width*s, + height: height*s, + } + } + + setActiveTarget(index: number) { + const window = this.screen.window + const markedTargets: MarkedTarget[] | null = this.store.get().markedTargets + const target = markedTargets && markedTargets[index] + if (target && window) { + const { fixedTop, rect } = getOffset(target.el, window) + const scrollToY = fixedTop - window.innerHeight / 1.5 + if (rect.top < 0 || rect.top > window.innerHeight) { + // behavior hack TODO: fix it somehow when they will decide to remove it from browser api + // @ts-ignore + window.scrollTo({ top: scrollToY, behavior: 'instant' }) + setTimeout(() => { + if (!markedTargets) { return } + this.store.update({ + markedTargets: markedTargets.map(t => t === target ? { + ...target, + boundingRect: this.calculateRelativeBoundingRect(target.el), + } : t) + }) + }, 0) + } + + } + this.store.update({ activeTargetIndex: index }); + } + + private actualScroll: Point | null = null + markTargets(selections: { selector: string, count: number }[] | null) { + if (selections) { + const totalCount = selections.reduce((a, b) => { + return a + b.count + }, 0); + const markedTargets: MarkedTarget[] = []; + let index = 0; + selections.forEach((s) => { + const el = this.screen.getElementBySelector(s.selector); + if (!el) return; + + markedTargets.push({ + ...s, + el, + index: index++, + percent: Math.round((s.count * 100) / totalCount), + boundingRect: this.calculateRelativeBoundingRect(el), + count: s.count, + }) + }); + this.actualScroll = this.screen.getCurrentScroll() + this.store.update({ markedTargets }); + } else { + if (this.actualScroll) { + this.screen.window?.scrollTo(this.actualScroll.x, this.actualScroll.y) + this.actualScroll = null + } + this.store.update({ markedTargets: null }); + } + } + + + injectTargets( + selections: { selector: string, count: number, clickRage?: boolean }[] | null, + onMarkerClick?: (selector: string, innerText: string) => void, + ) { + if (selections) { + const totalCount = selections.reduce((a, b) => { + return a + b.count + }, 0); + + this.clickMapOverlay?.remove() + const overlay = document.createElement("div") + const iframeSize = this.screen.iframeStylesRef + const scaleRatio = this.screen.getScale() + Object.assign(overlay.style, clickmapStyles.overlayStyle({ height: iframeSize.height, width: iframeSize.width, scale: scaleRatio })) + + this.clickMapOverlay = overlay + selections.forEach((s, i) => { + const el = this.screen.getElementBySelector(s.selector); + if (!el) return; + + const bubbleContainer = document.createElement("div") + const {top, left, width, height} = el.getBoundingClientRect() + const totalClicks = document.createElement("div") + totalClicks.innerHTML = `${s.count} ${s.count !== 1 ? 'Clicks' : 'Click'}` + Object.assign(totalClicks.style, clickmapStyles.totalClicks) + + const percent = document.createElement("div") + percent.style.fontSize = "14px" + percent.innerHTML = `${Math.round((s.count * 100) / totalCount)}% of the clicks recorded in this page` + + bubbleContainer.appendChild(totalClicks) + bubbleContainer.appendChild(percent) + const containerId = `clickmap-bubble-${i}` + bubbleContainer.id = containerId + this.clickContainers.push(bubbleContainer) + const frameWidth = iframeSize.width.replace('px', '') + + // @ts-ignore + Object.assign(bubbleContainer.style, clickmapStyles.bubbleContainer({ top, left: Math.max(100, frameWidth - left > 250 ? left : frameWidth - 220), height })) + + const border = document.createElement("div") + + let key = 0 + + if (width > 50) { + let diff = widths[key] - width + while (diff > 0) { + key++ + diff = widths[key] - width + } + } else { + key = 3 + } + const borderZindex = zIndexMap[widths[key]] + + Object.assign(border.style, clickmapStyles.highlight({ width, height, top, left, zIndex: borderZindex })) + + const smallClicksBubble = document.createElement("div") + smallClicksBubble.innerHTML = `${s.count}` + const smallClicksId = containerId + '-small' + smallClicksBubble.id = smallClicksId + this.smallClicks.push(smallClicksBubble) + + border.onclick = (e) => { + e.stopPropagation() + const innerText = el.innerText.length > 25 ? `${el.innerText.slice(0, 20)}...` : el.innerText + onMarkerClick?.(s.selector, innerText) + this.clickContainers.forEach(container => { + if (container.id === containerId) { + container.style.visibility = "visible" + } else { + container.style.visibility = "hidden" + } + }) + this.smallClicks.forEach(container => { + if (container.id !== smallClicksId) { + container.style.visibility = "visible" + } else { + container.style.visibility = "hidden" + } + }) + } + + overlay.onclick = (e) => { + e.stopPropagation() + onMarkerClick?.('', '') + this.clickContainers.forEach(container => { + container.style.visibility = "hidden" + }) + this.smallClicks.forEach(container => { + container.style.visibility = "visible" + }) + } + + Object.assign(smallClicksBubble.style, clickmapStyles.clicks({ top, height, isRage: s.clickRage })) + + border.appendChild(smallClicksBubble) + overlay.appendChild(bubbleContainer) + overlay.appendChild(border) + }); + + this.screen.getParentElement()?.appendChild(overlay) + } else { + this.store.update({ markedTargets: null }); + this.clickMapOverlay?.remove() + this.clickMapOverlay = null + this.smallClicks = [] + this.clickContainers = [] + } + } + +} diff --git a/frontend/app/player/web/addons/clickmapStyles.ts b/frontend/app/player/web/addons/clickmapStyles.ts new file mode 100644 index 000000000..0ab795ea0 --- /dev/null +++ b/frontend/app/player/web/addons/clickmapStyles.ts @@ -0,0 +1,65 @@ +export const clickmapStyles = { + overlayStyle: ({ height, width, scale }: { height: string, width: string, scale: number }) => ({ + transform: `scale(${scale}) translate(-50%, 0)`, + position: 'absolute', + top: '0px', + left: '50%', + width, + height, + background: 'rgba(0,0,0, 0.15)', + zIndex: 9 * 10e3, + transformOrigin: 'left top', + }), + totalClicks: { + fontSize: '16px', + fontWeight: '600', + }, + bubbleContainer: ({ top, left, height }: { top: number; left: number, height: number }) => ({ + position: 'absolute', + top: top > 20 ? top + 'px' : height + 2 + 'px', + width: '250px', + left: `${left}px`, + padding: '10px', + borderRadius: '6px', + background: 'white', + border: '1px solid rgba(0, 0, 0, 0.12)', + boxShadow: '0px 2px 10px 2px rgba(0,0,0,0.5)', + transform: top > 20 ? 'translate(-25%, -110%)' : 'translate(-25%, 0%)', + textAlign: 'center', + visibility: 'hidden', + zIndex: 10, + }), + highlight: ({ + width, + height, + top, + left, + zIndex, + }: { + width: number; + height: number; + top: number; + left: number; + zIndex: number; + }) => ({ + width: `${width}px`, + height: `${height}px`, + border: '2px dotted red', + cursor: 'pointer', + top: `${top}px`, + left: `${left}px`, + position: 'absolute', + zIndex, + }), + clicks: ({ top, height, isRage }: { top: number; height: number, isRage?: boolean }) => ({ + top: top > 20 ? 0 : `${height}px`, + left: 0, + position: 'absolute', + borderRadius: '999px', + padding: '6px', + background: isRage ? 'red' : 'white', + color: isRage ? 'white' : 'black', + lineHeight: '0.5', + transform: 'translate(-70%, -70%)', + }), +}; diff --git a/frontend/app/player/MessageDistributor/managers/AnnotationCanvas.ts b/frontend/app/player/web/assist/AnnotationCanvas.ts similarity index 100% rename from frontend/app/player/MessageDistributor/managers/AnnotationCanvas.ts rename to frontend/app/player/web/assist/AnnotationCanvas.ts diff --git a/frontend/app/player/web/assist/AssistManager.ts b/frontend/app/player/web/assist/AssistManager.ts new file mode 100644 index 000000000..13de4ebdd --- /dev/null +++ b/frontend/app/player/web/assist/AssistManager.ts @@ -0,0 +1,291 @@ +import type { Socket } from 'socket.io-client'; +import type Screen from '../Screen/Screen'; +import type { Store } from '../../common/types' +import type { Message } from '../messages'; +import MStreamReader from '../messages/MStreamReader'; +import JSONRawMessageReader from '../messages/JSONRawMessageReader' +import Call, { CallingState } from './Call'; +import RemoteControl, { RemoteControlStatus } from './RemoteControl' +import ScreenRecording, { SessionRecordingStatus } from './ScreenRecording' + + +export { + RemoteControlStatus, + SessionRecordingStatus, + CallingState, +} + +export enum ConnectionStatus { + Connecting, + WaitingMessages, + Connected, + Inactive, + Disconnected, + Error, + Closed, +} + + +export function getStatusText(status: ConnectionStatus): string { + switch(status) { + case ConnectionStatus.Closed: + return 'Closed...'; + case ConnectionStatus.Connecting: + return "Connecting..."; + case ConnectionStatus.Connected: + return ""; + case ConnectionStatus.Inactive: + return "Client tab is inactive"; + case ConnectionStatus.Disconnected: + return "Disconnected"; + case ConnectionStatus.Error: + return "Something went wrong. Try to reload the page."; + case ConnectionStatus.WaitingMessages: + return "Connected. Waiting for the data... (The tab might be inactive)" + } +} + +// export interface State { +// peerConnectionStatus: ConnectionStatus; +// assistStart: number; +// } + +const MAX_RECONNECTION_COUNT = 4; + +export default class AssistManager { + static readonly INITIAL_STATE = { + peerConnectionStatus: ConnectionStatus.Connecting, + assistStart: 0, + ...Call.INITIAL_STATE, + ...RemoteControl.INITIAL_STATE, + ...ScreenRecording.INITIAL_STATE, + } + // TODO: Session type + constructor( + private session: any, + private setMessagesLoading: (flag: boolean) => void, + private handleMessage: (m: Message, index: number) => void, + private screen: Screen, + private config: RTCIceServer[], + private store: Store<typeof AssistManager.INITIAL_STATE>, + ) {} + + private get borderStyle() { + const { recordingState, remoteControl } = this.store.get() + + const isRecordingActive = recordingState === SessionRecordingStatus.Recording + const isControlActive = remoteControl === RemoteControlStatus.Enabled + // recording gets priority here + if (isRecordingActive) return { border: '2px dashed red' } + if (isControlActive) return { border: '2px dashed blue' } + return { border: 'unset'} + } + + private setStatus(status: ConnectionStatus) { + if (this.store.get().peerConnectionStatus === ConnectionStatus.Disconnected && + status !== ConnectionStatus.Connected) { + return + } + + if (status === ConnectionStatus.Connecting) { + this.setMessagesLoading(true); + } else { + this.setMessagesLoading(false); + } + if (status === ConnectionStatus.Connected) { + this.screen.display(true); + } else { + this.screen.display(false); + } + this.store.update({ peerConnectionStatus: status }); + } + + private get peerID(): string { + return `${this.session.projectKey}-${this.session.sessionId}` + } + + private socketCloseTimeout: ReturnType<typeof setTimeout> | undefined + private onVisChange = () => { + this.socketCloseTimeout && clearTimeout(this.socketCloseTimeout) + if (document.hidden) { + this.socketCloseTimeout = setTimeout(() => { + const state = this.store.get() + if (document.hidden && + // TODO: should it be RemoteControlStatus.Disabled? (check) + (state.calling === CallingState.NoCall && state.remoteControl === RemoteControlStatus.Enabled)) { + this.socket?.close() + } + }, 30000) + } else { + this.socket?.open() + } + } + + private socket: Socket | null = null + private disconnectTimeout: ReturnType<typeof setTimeout> | undefined + private inactiveTimeout: ReturnType<typeof setTimeout> | undefined + private clearDisconnectTimeout() { + this.disconnectTimeout && clearTimeout(this.disconnectTimeout) + this.disconnectTimeout = undefined + } + private clearInactiveTimeout() { + this.inactiveTimeout && clearTimeout(this.inactiveTimeout) + this.inactiveTimeout = undefined + } + connect(agentToken: string) { + const jmr = new JSONRawMessageReader() + const reader = new MStreamReader(jmr, this.session.startedAt) + let waitingForMessages = true + + const now = +new Date() + this.store.update({ assistStart: now }) + + // @ts-ignore + import('socket.io-client').then(({ default: io }) => { + if (this.socket != null || this.cleaned) { return } + // @ts-ignore + const urlObject = new URL(window.env.API_EDP || window.location.origin) // does it handle ssl automatically? + + const socket: Socket = this.socket = io(urlObject.origin, { + multiplex: true, + path: '/ws-assist/socket', + auth: { + token: agentToken + }, + query: { + peerId: this.peerID, + identity: "agent", + agentInfo: JSON.stringify({ + ...this.session.agentInfo, + query: document.location.search + }) + } + }) + socket.on("connect", () => { + waitingForMessages = true + this.setStatus(ConnectionStatus.WaitingMessages) // TODO: reconnect happens frequently on bad network + }) + socket.on('messages', messages => { + jmr.append(messages) // as RawMessage[] + + if (waitingForMessages) { + waitingForMessages = false // TODO: more explicit + this.setStatus(ConnectionStatus.Connected) + } + + for (let msg = reader.readNext();msg !== null;msg = reader.readNext()) { + this.handleMessage(msg, msg._index) + } + }) + + socket.on('SESSION_RECONNECTED', () => { + this.clearDisconnectTimeout() + this.clearInactiveTimeout() + this.setStatus(ConnectionStatus.Connected) + }) + + socket.on('UPDATE_SESSION', ({ active }) => { + this.clearDisconnectTimeout() + !this.inactiveTimeout && this.setStatus(ConnectionStatus.Connected) + if (typeof active === "boolean") { + this.clearInactiveTimeout() + if (active) { + this.setStatus(ConnectionStatus.Connected) + } else { + this.inactiveTimeout = setTimeout(() => this.setStatus(ConnectionStatus.Inactive), 5000) + } + } + }) + socket.on('SESSION_DISCONNECTED', e => { + waitingForMessages = true + this.clearDisconnectTimeout() + this.disconnectTimeout = setTimeout(() => { + this.setStatus(ConnectionStatus.Disconnected) + }, 30000) + }) + socket.on('error', e => { + console.warn("Socket error: ", e ) + this.setStatus(ConnectionStatus.Error); + }) + + // Maybe do lazy initialization for all? + // TODO: socket proxy (depend on interfaces) + this.callManager = new Call( + this.store, + socket, + this.config, + this.peerID, + ) + this.remoteControl = new RemoteControl( + this.store, + socket, + this.screen, + this.session.agentInfo, + () => this.screen.setBorderStyle(this.borderStyle) + ) + this.screenRecording = new ScreenRecording( + this.store, + socket, + this.session.agentInfo, + () => this.screen.setBorderStyle(this.borderStyle), + ) + + document.addEventListener('visibilitychange', this.onVisChange) + }) + } + + + /* ==== ScreenRecording ==== */ + private screenRecording: ScreenRecording | null = null + requestRecording = (...args: Parameters<ScreenRecording['requestRecording']>) => { + return this.screenRecording?.requestRecording(...args) + } + stopRecording = (...args: Parameters<ScreenRecording['stopRecording']>) => { + return this.screenRecording?.stopRecording(...args) + } + + + /* ==== RemoteControl ==== */ + private remoteControl: RemoteControl | null = null + requestReleaseRemoteControl = (...args: Parameters<RemoteControl['requestReleaseRemoteControl']>) => { + return this.remoteControl?.requestReleaseRemoteControl(...args) + } + releaseRemoteControl = (...args: Parameters<RemoteControl['releaseRemoteControl']>) => { + return this.remoteControl?.releaseRemoteControl(...args) + } + toggleAnnotation = (...args: Parameters<RemoteControl['toggleAnnotation']>) => { + return this.remoteControl?.toggleAnnotation(...args) + } + + /* ==== Call ==== */ + private callManager: Call | null = null + initiateCallEnd = async (...args: Parameters<Call['initiateCallEnd']>) => { + return this.callManager?.initiateCallEnd(...args) + } + setCallArgs = (...args: Parameters<Call['setCallArgs']>) => { + return this.callManager?.setCallArgs(...args) + } + call = (...args: Parameters<Call['call']>) => { + return this.callManager?.call(...args) + } + toggleVideoLocalStream = (...args: Parameters<Call['toggleVideoLocalStream']>) => { + return this.callManager?.toggleVideoLocalStream(...args) + } + addPeerCall = (...args: Parameters<Call['addPeerCall']>) => { + return this.callManager?.addPeerCall(...args) + } + + + /* ==== Cleaning ==== */ + private cleaned = false + clean() { + this.cleaned = true // sometimes cleaned before modules loaded + this.remoteControl?.clean() + this.callManager?.clean() + this.socket?.close() + this.clearDisconnectTimeout() + this.clearInactiveTimeout() + this.socketCloseTimeout && clearTimeout(this.socketCloseTimeout) + document.removeEventListener('visibilitychange', this.onVisChange) + } +} diff --git a/frontend/app/player/web/assist/Call.ts b/frontend/app/player/web/assist/Call.ts new file mode 100644 index 000000000..a771794fa --- /dev/null +++ b/frontend/app/player/web/assist/Call.ts @@ -0,0 +1,278 @@ +import type Peer from 'peerjs'; +import type { MediaConnection } from 'peerjs'; + +import type { LocalStream } from './LocalStream'; +import type { Socket } from './types' +import type { Store } from '../../common/types' + +import appStore from 'App/store'; + + +export enum CallingState { + NoCall, + Connecting, + Requesting, + Reconnecting, + OnCall, +} + +export interface State { + calling: CallingState; +} + +export default class Call { + static readonly INITIAL_STATE: Readonly<State> = { + calling: CallingState.NoCall + } + + private _peer: Peer | null = null + private connectionAttempts: number = 0 + private callConnection: MediaConnection[] = [] + private videoStreams: Record<string, MediaStreamTrack> = {} + + constructor( + private store: Store<State>, + private socket: Socket, + private config: RTCIceServer[], + private peerID: string, + ) { + socket.on('call_end', this.onRemoteCallEnd) + socket.on('videofeed', ({ streamId, enabled }) => { + console.log(streamId, enabled) + console.log(this.videoStreams) + if (this.videoStreams[streamId]) { + this.videoStreams[streamId].enabled = enabled + } + console.log(this.videoStreams) + }) + let reconnecting = false + socket.on('SESSION_DISCONNECTED', () => { + if (this.store.get().calling === CallingState.OnCall) { + this.store.update({ calling: CallingState.Reconnecting }) + reconnecting = true + } else if (this.store.get().calling === CallingState.Requesting){ + this.store.update({ calling: CallingState.NoCall }) + } + }) + socket.on('messages', () => { + if (reconnecting) { // 'messages' come frequently, so it is better to have Reconnecting + this._callSessionPeer() + reconnecting = false + } + }) + socket.on("disconnect", () => { + this.store.update({ calling: CallingState.NoCall }) + }) + } + + private getPeer(): Promise<Peer> { + if (this._peer && !this._peer.disconnected) { return Promise.resolve(this._peer) } + + // @ts-ignore + const urlObject = new URL(window.env.API_EDP || window.location.origin) + + // @ts-ignore TODO: set module in ts settings + return import('peerjs').then(({ default: Peer }) => { + if (this.cleaned) {return Promise.reject("Already cleaned")} + const peerOpts: Peer.PeerJSOption = { + host: urlObject.hostname, + path: '/assist', + port: urlObject.port === "" ? (location.protocol === 'https:' ? 443 : 80 ): parseInt(urlObject.port), + } + if (this.config) { + peerOpts['config'] = { + iceServers: this.config, + //@ts-ignore + sdpSemantics: 'unified-plan', + iceTransportPolicy: 'relay', + }; + } + const peer = this._peer = new Peer(peerOpts) + peer.on('call', call => { + console.log('getting call from', call.peer) + call.answer(this.callArgs.localStream.stream) + this.callConnection.push(call) + + this.callArgs.localStream.onVideoTrack(vTrack => { + const sender = call.peerConnection.getSenders().find(s => s.track?.kind === "video") + if (!sender) { + console.warn("No video sender found") + return + } + sender.replaceTrack(vTrack) + }) + + call.on('stream', stream => { + this.videoStreams[call.peer] = stream.getVideoTracks()[0] + this.callArgs && this.callArgs.onStream(stream) + }); + // call.peerConnection.addEventListener("track", e => console.log('newtrack',e.track)) + + call.on("close", this.onRemoteCallEnd) + call.on("error", (e) => { + console.error("PeerJS error (on call):", e) + this.initiateCallEnd(); + this.callArgs && this.callArgs.onError && this.callArgs.onError(); + }); + }) + peer.on('error', e => { + if (e.type === 'disconnected') { + return peer.reconnect() + } else if (e.type !== 'peer-unavailable') { + console.error(`PeerJS error (on peer). Type ${e.type}`, e); + } + + // call-reconnection connected + // if (['peer-unavailable', 'network', 'webrtc'].includes(e.type)) { + // this.setStatus(this.connectionAttempts++ < MAX_RECONNECTION_COUNT + // ? ConnectionStatus.Connecting + // : ConnectionStatus.Disconnected); + // Reconnect... + }) + + return new Promise(resolve => { + peer.on("open", () => resolve(peer)) + }) + }); + + } + + + private handleCallEnd() { + this.callArgs && this.callArgs.onCallEnd() + this.callConnection[0] && this.callConnection[0].close() + this.store.update({ calling: CallingState.NoCall }) + this.callArgs = null + // TODO: We have it separated, right? (check) + //this.toggleAnnotation(false) + } + private onRemoteCallEnd = () => { + if (this.store.get().calling === CallingState.Requesting) { + this.callArgs && this.callArgs.onReject() + this.callConnection[0] && this.callConnection[0].close() + this.store.update({ calling: CallingState.NoCall }) + this.callArgs = null + // TODO: We have it separated, right? (check) + //this.toggleAnnotation(false) + } else { + this.handleCallEnd() + } + } + + initiateCallEnd = async () => { + this.socket?.emit("call_end", appStore.getState().getIn([ 'user', 'account', 'name'])) + this.handleCallEnd() + // TODO: We have it separated, right? (check) + // const remoteControl = this.store.get().remoteControl + // if (remoteControl === RemoteControlStatus.Enabled) { + // this.socket.emit("release_control") + // this.toggleRemoteControl(false) + // } + } + + + private callArgs: { + localStream: LocalStream, + onStream: (s: MediaStream)=>void, + onCallEnd: () => void, + onReject: () => void, + onError?: ()=> void, + } | null = null + + setCallArgs( + localStream: LocalStream, + onStream: (s: MediaStream)=>void, + onCallEnd: () => void, + onReject: () => void, + onError?: (e?: any)=> void, + ) { + this.callArgs = { + localStream, + onStream, + onCallEnd, + onReject, + onError, + } + } + + call(thirdPartyPeers?: string[]): { end: () => void } { + if (thirdPartyPeers && thirdPartyPeers.length > 0) { + this.addPeerCall(thirdPartyPeers) + } else { + this._callSessionPeer() + } + return { + end: this.initiateCallEnd, + } + } + + toggleVideoLocalStream(enabled: boolean) { + this.getPeer().then((peer) => { + this.socket.emit('videofeed', { streamId: peer.id, enabled }) + }) + } + + + /** Connecting to the other agents that are already + * in the call with the user + */ + addPeerCall(thirdPartyPeers: string[]) { + thirdPartyPeers.forEach(peer => this._peerConnection(peer)) + } + + /** Connecting to the app user */ + private _callSessionPeer() { + if (![CallingState.NoCall, CallingState.Reconnecting].includes(this.store.get().calling)) { return } + this.store.update({ calling: CallingState.Connecting }) + this._peerConnection(this.peerID); + this.socket.emit("_agent_name", appStore.getState().getIn([ 'user', 'account', 'name'])) + } + + private async _peerConnection(remotePeerId: string) { + try { + const peer = await this.getPeer(); + const call = peer.call(remotePeerId, this.callArgs.localStream.stream) + this.callConnection.push(call) + + this.callArgs.localStream.onVideoTrack(vTrack => { + const sender = call.peerConnection.getSenders().find(s => s.track?.kind === "video") + if (!sender) { + console.warn("No video sender found") + return + } + sender.replaceTrack(vTrack) + }) + + call.on('stream', stream => { + this.store.get().calling !== CallingState.OnCall && this.store.update({ calling: CallingState.OnCall }) + + this.videoStreams[call.peer] = stream.getVideoTracks()[0] + + this.callArgs && this.callArgs.onStream(stream) + }); + // call.peerConnection.addEventListener("track", e => console.log('newtrack',e.track)) + + call.on("close", this.onRemoteCallEnd) + call.on("error", (e) => { + console.error("PeerJS error (on call):", e) + this.initiateCallEnd(); + this.callArgs && this.callArgs.onError && this.callArgs.onError(); + }); + } catch (e) { + console.error(e) + } + } + + private cleaned: boolean = false + clean() { + this.cleaned = true // sometimes cleaned before modules loaded + this.initiateCallEnd() + if (this._peer) { + console.log("destroying peer...") + const peer = this._peer; // otherwise it calls reconnection on data chan close + this._peer = null; + peer.disconnect(); + peer.destroy(); + } + } +} \ No newline at end of file diff --git a/frontend/app/player/MessageDistributor/managers/LocalStream.ts b/frontend/app/player/web/assist/LocalStream.ts similarity index 100% rename from frontend/app/player/MessageDistributor/managers/LocalStream.ts rename to frontend/app/player/web/assist/LocalStream.ts diff --git a/frontend/app/player/web/assist/RemoteControl.ts b/frontend/app/player/web/assist/RemoteControl.ts new file mode 100644 index 000000000..e262e75b8 --- /dev/null +++ b/frontend/app/player/web/assist/RemoteControl.ts @@ -0,0 +1,172 @@ +import AnnotationCanvas from './AnnotationCanvas'; +import type { Socket } from './types' +import type Screen from '../Screen/Screen' +import type { Store } from '../../common/types' + + +export enum RemoteControlStatus { + Disabled = 0, + Requesting, + Enabled, +} + +export interface State { + annotating: boolean + remoteControl: RemoteControlStatus +} + +export default class RemoteControl { + static readonly INITIAL_STATE: Readonly<State> = { + remoteControl: RemoteControlStatus.Disabled, + annotating: false, + } + + constructor( + private store: Store<State>, + private socket: Socket, + private screen: Screen, + private agentInfo: Object, + private onToggle: (active: boolean) => void, + ){ + socket.on("control_granted", id => { + this.toggleRemoteControl(id === socket.id) + }) + socket.on("control_rejected", id => { + id === socket.id && this.toggleRemoteControl(false) + }) + socket.on('SESSION_DISCONNECTED', () => { + if (this.store.get().remoteControl === RemoteControlStatus.Requesting) { + this.toggleRemoteControl(false) // else its remaining + } + }) + socket.on("disconnect", () => { + this.toggleRemoteControl(false) + }) + socket.on("error", () => { + this.toggleRemoteControl(false) + }) + } + + private onMouseMove = (e: MouseEvent): void => { + const data = this.screen.getInternalCoordinates(e) + this.socket.emit("move", [ data.x, data.y ]) + } + + private onWheel = (e: WheelEvent): void => { + e.preventDefault() + //throttling makes movements less smooth, so it is omitted + //this.onMouseMove(e) + this.socket.emit("scroll", [ e.deltaX, e.deltaY ]) + } + + private onMouseClick = (e: MouseEvent): void => { + if (this.store.get().annotating) { return; } // ignore clicks while annotating + + const data = this.screen.getInternalViewportCoordinates(e) + // const el = this.screen.getElementFromPoint(e); // requires requestiong node_id from domManager + const el = this.screen.getElementFromInternalPoint(data) + if (el instanceof HTMLElement) { + el.focus() + el.oninput = e => { + if (el instanceof HTMLTextAreaElement + || el instanceof HTMLInputElement + ) { + this.socket && this.socket.emit("input", el.value) + } else if (el.isContentEditable) { + this.socket && this.socket.emit("input", el.innerText) + } + } + // TODO: send "focus" event to assist with the nodeID + el.onkeydown = e => { + if (e.key == "Tab") { + e.preventDefault() + } + } + el.onblur = () => { + el.oninput = null + el.onblur = null + } + } + this.socket.emit("click", [ data.x, data.y ]); + } + + private toggleRemoteControl(enable: boolean){ + if (enable) { + this.screen.overlay.addEventListener("mousemove", this.onMouseMove) + this.screen.overlay.addEventListener("click", this.onMouseClick) + this.screen.overlay.addEventListener("wheel", this.onWheel) + this.store.update({ remoteControl: RemoteControlStatus.Enabled }) + } else { + this.screen.overlay.removeEventListener("mousemove", this.onMouseMove) + this.screen.overlay.removeEventListener("click", this.onMouseClick) + this.screen.overlay.removeEventListener("wheel", this.onWheel) + this.store.update({ remoteControl: RemoteControlStatus.Disabled }) + this.toggleAnnotation(false) + } + this.onToggle(enable) + } + + requestReleaseRemoteControl = () => { + const remoteControl = this.store.get().remoteControl + if (remoteControl === RemoteControlStatus.Requesting) { return } + if (remoteControl === RemoteControlStatus.Disabled) { + this.store.update({ remoteControl: RemoteControlStatus.Requesting }) + this.socket.emit("request_control", JSON.stringify({ + ...this.agentInfo, + query: document.location.search + })) + } else { + this.releaseRemoteControl() + } + } + + releaseRemoteControl = () => { + this.socket.emit("release_control") + this.toggleRemoteControl(false) + } + + private annot: AnnotationCanvas | null = null + + toggleAnnotation(enable?: boolean) { + if (typeof enable !== "boolean") { + enable = !!this.store.get().annotating + } + if (enable && !this.annot) { + const annot = this.annot = new AnnotationCanvas() + annot.mount(this.screen.overlay) + annot.canvas.addEventListener("mousedown", e => { + const data = this.screen.getInternalViewportCoordinates(e) + annot.start([ data.x, data.y ]) + this.socket.emit("startAnnotation", [ data.x, data.y ]) + }) + annot.canvas.addEventListener("mouseleave", () => { + annot.stop() + this.socket.emit("stopAnnotation") + }) + annot.canvas.addEventListener("mouseup", () => { + annot.stop() + this.socket.emit("stopAnnotation") + }) + annot.canvas.addEventListener("mousemove", e => { + if (!annot.isPainting()) { return } + + const data = this.screen.getInternalViewportCoordinates(e) + annot.move([ data.x, data.y ]) + this.socket.emit("moveAnnotation", [ data.x, data.y ]) + }) + this.store.update({ annotating: true }) + } else if (!enable && !!this.annot) { + this.annot.remove() + this.annot = null + this.store.update({ annotating: false }) + } + } + + clean() { + this.toggleRemoteControl(false) + if (this.annot) { + this.annot.remove() + this.annot = null + } + } +} \ No newline at end of file diff --git a/frontend/app/player/web/assist/ScreenRecording.ts b/frontend/app/player/web/assist/ScreenRecording.ts new file mode 100644 index 000000000..90ad76020 --- /dev/null +++ b/frontend/app/player/web/assist/ScreenRecording.ts @@ -0,0 +1,70 @@ +import { toast } from 'react-toastify' + +import type { Socket } from './types' +import type { Store } from '../../common/types' + + +export enum SessionRecordingStatus { + Off, + Requesting, + Recording +} + +export interface State { + recordingState: SessionRecordingStatus; +} + +export default class ScreenRecording { + static readonly INITIAL_STATE: Readonly<State> = { + recordingState: SessionRecordingStatus.Off, + } + constructor( + private store: Store<State>, + private socket: Socket, + private agentInfo: Object, + private onToggle: (active: boolean) => void, + ) { + socket.on('recording_accepted', () => { + this.toggleRecording(true) + }) + socket.on('recording_rejected', () => { + this.toggleRecording(false) + }) + socket.on('recording_busy', () => { + this.onRecordingBusy() + }) + } + + private onRecordingBusy = () => { + toast.error("This session is already being recorded by another agent") + } + + requestRecording = () => { + const recordingState = this.store.get().recordingState + if (recordingState === SessionRecordingStatus.Requesting) return; + + this.store.update({ recordingState: SessionRecordingStatus.Requesting }) + this.socket.emit("request_recording", JSON.stringify({ + ...this.agentInfo, + query: document.location.search, + })) + } + + stopRecording = () => { + const recordingState = this.store.get().recordingState + if (recordingState === SessionRecordingStatus.Off) return; + + this.socket.emit("stop_recording") + this.toggleRecording(false) + } + + private toggleRecording = (isAccepted: boolean) => { + this.store.update({ + recordingState: isAccepted + ? SessionRecordingStatus.Recording + : SessionRecordingStatus.Off, + }) + + this.onToggle(isAccepted) + } +} \ No newline at end of file diff --git a/frontend/app/player/web/assist/types.ts b/frontend/app/player/web/assist/types.ts new file mode 100644 index 000000000..ae567bb14 --- /dev/null +++ b/frontend/app/player/web/assist/types.ts @@ -0,0 +1,8 @@ +import type { Socket as SocketIO } from 'socket.io-client'; + + +export interface Socket { + emit: SocketIO['emit'], + on: SocketIO['on'], + id: SocketIO['id'], +} diff --git a/frontend/app/player/MessageDistributor/managers/ActivityManager.ts b/frontend/app/player/web/managers/ActivityManager.ts similarity index 85% rename from frontend/app/player/MessageDistributor/managers/ActivityManager.ts rename to frontend/app/player/web/managers/ActivityManager.ts index 412fefdce..383f29c15 100644 --- a/frontend/app/player/MessageDistributor/managers/ActivityManager.ts +++ b/frontend/app/player/web/managers/ActivityManager.ts @@ -1,8 +1,8 @@ -import ListWalker from './ListWalker'; +import ListWalker from '../../common/ListWalker'; class SkipIntervalCls { - constructor(private readonly start = 0, private readonly end = 0) {} + constructor(readonly start = 0, readonly end = 0) {} get time(): number { return this.start; @@ -12,7 +12,7 @@ class SkipIntervalCls { } } -export type SkipInterval = InstanceType<typeof SkipIntervalCls>; // exporting only class' type +export type SkipInterval = InstanceType<typeof SkipIntervalCls>; export default class ActivityManager extends ListWalker<SkipInterval> { diff --git a/frontend/app/player/MessageDistributor/managers/DOM/DOMManager.ts b/frontend/app/player/web/managers/DOM/DOMManager.ts similarity index 79% rename from frontend/app/player/MessageDistributor/managers/DOM/DOMManager.ts rename to frontend/app/player/web/managers/DOM/DOMManager.ts index 2688abb14..bb4c999ae 100644 --- a/frontend/app/player/MessageDistributor/managers/DOM/DOMManager.ts +++ b/frontend/app/player/web/managers/DOM/DOMManager.ts @@ -1,9 +1,10 @@ import logger from 'App/logger'; -import type StatedScreen from '../../StatedScreen'; -import type { Message, SetNodeScroll, CreateElementNode } from '../../messages'; +import type Screen from '../../Screen/Screen'; +import type { Message, SetNodeScroll } from '../../messages'; -import ListWalker from '../ListWalker'; +import { MType } from '../../messages'; +import ListWalker from '../../../common/ListWalker'; import StylesManager, { rewriteNodeStyleSheet } from './StylesManager'; import FocusManager from './FocusManager'; import { @@ -42,6 +43,7 @@ export default class DOMManager extends ListWalker<Message> { private activeIframeRoots: Map<number, number> = new Map() private styleSheets: Map<number, CSSStyleSheet> = new Map() private ppStyleSheets: Map<number, PostponedStyleSheet> = new Map() + private stringDict: Record<number,string> = {} private upperBodyId: number = -1; @@ -51,16 +53,17 @@ export default class DOMManager extends ListWalker<Message> { constructor( - private readonly screen: StatedScreen, + private readonly screen: Screen, private readonly isMobile: boolean, - public readonly time: number + public readonly time: number, + setCssLoading: ConstructorParameters<typeof StylesManager>[1], ) { super() - this.stylesManager = new StylesManager(screen) + this.stylesManager = new StylesManager(screen, setCssLoading) } append(m: Message): void { - if (m.tp === "set_node_scroll") { + if (m.tp === MType.SetNodeScroll) { let scrollManager = this.nodeScrollManagers.get(m.id) if (!scrollManager) { scrollManager = new ListWalker() @@ -69,15 +72,15 @@ export default class DOMManager extends ListWalker<Message> { scrollManager.append(m) return } - if (m.tp === "set_node_focus") { + if (m.tp === MType.SetNodeFocus) { this.focusManager.append(m) return } - if (m.tp === "create_element_node") { + if (m.tp === MType.CreateElementNode) { if(m.tag === "BODY" && this.upperBodyId === -1) { this.upperBodyId = m.id } - } else if (m.tp === "set_node_attribute" && + } else if (m.tp === MType.SetNodeAttribute && (IGNORED_ATTRS.includes(m.name) || !ATTR_NAME_REGEXP.test(m.name))) { logger.log("Ignorring message: ", m) return; // Ignoring @@ -132,13 +135,38 @@ export default class DOMManager extends ListWalker<Message> { parent.insertChildAt(child, index) } + private setNodeAttribute(msg: { id: number, name: string, value: string }) { + let { name, value } = msg; + const vn = this.vElements.get(msg.id) + if (!vn) { logger.error("Node not found", msg); return } + if (vn.node.tagName === "INPUT" && name === "name") { + // Otherwise binds local autocomplete values (maybe should ignore on the tracker level) + return + } + if (name === "href" && vn.node.tagName === "LINK") { + // @ts-ignore ?global ENV type // It've been done on backend (remove after testing in saas) + // if (value.startsWith(window.env.ASSETS_HOST || window.location.origin + '/assets')) { + // value = value.replace("?", "%3F"); + // } + if (!value.startsWith("http")) { return } + // blob:... value happened here. https://foss.openreplay.com/3/session/7013553567419137 + // that resulted in that link being unable to load and having 4sec timeout in the below function. + this.stylesManager.setStyleHandlers(vn.node as HTMLLinkElement, value); + } + if (vn.node.namespaceURI === 'http://www.w3.org/2000/svg' && value.startsWith("url(")) { + value = "url(#" + (value.split("#")[1] ||")") + } + vn.setAttribute(name, value) + this.removeBodyScroll(msg.id, vn) + } + private applyMessage = (msg: Message): Promise<any> | undefined => { let node: Node | undefined let vn: VNode | undefined let doc: Document | null let styleSheet: CSSStyleSheet | PostponedStyleSheet | undefined switch (msg.tp) { - case "create_document": + case MType.CreateDocument: doc = this.screen.document; if (!doc) { logger.error("No root iframe document found", msg) @@ -158,17 +186,18 @@ export default class DOMManager extends ListWalker<Message> { this.vRoots.clear() this.vRoots.set(0, vDoc) // watchout: id==0 for both Document and documentElement // this is done for the AdoptedCSS logic - // todo: start from 0 (sync logic with tracker) + // todo: start from 0-node (sync logic with tracker) this.vTexts.clear() this.stylesManager.reset() this.activeIframeRoots.clear() + this.stringDict = {} return - case "create_text_node": + case MType.CreateTextNode: vn = new VText() this.vTexts.set(msg.id, vn) this.insertNode(msg) return - case "create_element_node": + case MType.CreateElementNode: let element: Element if (msg.svg) { element = document.createElementNS('http://www.w3.org/2000/svg', msg.tag) @@ -188,45 +217,37 @@ export default class DOMManager extends ListWalker<Message> { vn.enforceInsertion() } return - case "move_node": + case MType.MoveNode: this.insertNode(msg); return - case "remove_node": + case MType.RemoveNode: vn = this.vElements.get(msg.id) || this.vTexts.get(msg.id) if (!vn) { logger.error("Node not found", msg); return } if (!vn.parentNode) { logger.error("Parent node not found", msg); return } vn.parentNode.removeChild(vn) return - case "set_node_attribute": - let { name, value } = msg; - vn = this.vElements.get(msg.id) - if (!vn) { logger.error("Node not found", msg); return } - if (vn.node.tagName === "INPUT" && name === "name") { - // Otherwise binds local autocomplete values (maybe should ignore on the tracker level) - return - } - if (name === "href" && vn.node.tagName === "LINK") { - // @ts-ignore ?global ENV type // It've been done on backend (remove after testing in saas) - // if (value.startsWith(window.env.ASSETS_HOST || window.location.origin + '/assets')) { - // value = value.replace("?", "%3F"); - // } - if (!value.startsWith("http")) { return } - // blob:... value happened here. https://foss.openreplay.com/3/session/7013553567419137 - // that resulted in that link being unable to load and having 4sec timeout in the below function. - this.stylesManager.setStyleHandlers(vn.node as HTMLLinkElement, value); - } - if (vn.node.namespaceURI === 'http://www.w3.org/2000/svg' && value.startsWith("url(")) { - value = "url(#" + (value.split("#")[1] ||")") - } - vn.setAttribute(name, value) - this.removeBodyScroll(msg.id, vn) + case MType.SetNodeAttribute: + this.setNodeAttribute(msg) return - case "remove_node_attribute": + case MType.StringDict: + this.stringDict[msg.key] = msg.value + return + case MType.SetNodeAttributeDict: + this.stringDict[msg.nameKey] === undefined && logger.error("No dictionary key for msg 'name': ", msg) + this.stringDict[msg.valueKey] === undefined && logger.error("No dictionary key for msg 'value': ", msg) + if (this.stringDict[msg.nameKey] === undefined || this.stringDict[msg.valueKey] === undefined ) { return } + this.setNodeAttribute({ + id: msg.id, + name: this.stringDict[msg.nameKey], + value: this.stringDict[msg.valueKey], + }) + return + case MType.RemoveNodeAttribute: vn = this.vElements.get(msg.id) if (!vn) { logger.error("Node not found", msg); return } vn.removeAttribute(msg.name) return - case "set_input_value": + case MType.SetInputValue: vn = this.vElements.get(msg.id) if (!vn) { logger.error("Node not found", msg); return } const nodeWithValue = vn.node @@ -246,13 +267,13 @@ export default class DOMManager extends ListWalker<Message> { } nodeWithValue.value = val return - case "set_input_checked": + case MType.SetInputChecked: vn = this.vElements.get(msg.id) if (!vn) { logger.error("Node not found", msg); return } (vn.node as HTMLInputElement).checked = msg.checked return - case "set_node_data": - case "set_css_data": // mbtodo: remove css transitions when timeflow is not natural (on jumps) + case MType.SetNodeData: + case MType.SetCssData: // mbtodo: remove css transitions when timeflow is not natural (on jumps) vn = this.vTexts.get(msg.id) if (!vn) { logger.error("Node not found", msg); return } vn.setData(msg.data) @@ -261,13 +282,13 @@ export default class DOMManager extends ListWalker<Message> { // TODO: move to message parsing doc && rewriteNodeStyleSheet(doc, vn.node) } - if (msg.tp === "set_css_data") { // Styles in priority (do we need inlines as well?) + if (msg.tp === MType.SetCssData) { // Styles in priority (do we need inlines as well?) vn.applyChanges() } return // @depricated since 4.0.2 in favor of adopted_ss_insert/delete_rule + add_owner as being common case for StyleSheets - case "css_insert_rule": + case MType.CssInsertRule: vn = this.vElements.get(msg.id) if (!vn) { logger.error("Node not found", msg); return } if (!(vn instanceof VStyleElement)) { @@ -276,7 +297,7 @@ export default class DOMManager extends ListWalker<Message> { } vn.onStyleSheet(sheet => insertRule(sheet, msg)) return - case "css_delete_rule": + case MType.CssDeleteRule: vn = this.vElements.get(msg.id) if (!vn) { logger.error("Node not found", msg); return } if (!(vn instanceof VStyleElement)) { @@ -287,7 +308,7 @@ export default class DOMManager extends ListWalker<Message> { return // end @depricated - case "create_i_frame_document": + case MType.CreateIFrameDocument: vn = this.vElements.get(msg.frameID) if (!vn) { logger.error("Node not found", msg); return } vn.enforceInsertion() @@ -318,7 +339,7 @@ export default class DOMManager extends ListWalker<Message> { logger.warn("Context message host is not Element", msg) } return - case "adopted_ss_insert_rule": + case MType.AdoptedSsInsertRule: styleSheet = this.styleSheets.get(msg.sheetID) || this.ppStyleSheets.get(msg.sheetID) if (!styleSheet) { logger.warn("No stylesheet was created for ", msg) @@ -326,7 +347,7 @@ export default class DOMManager extends ListWalker<Message> { } insertRule(styleSheet, msg) return - case "adopted_ss_delete_rule": + case MType.AdoptedSsDeleteRule: styleSheet = this.styleSheets.get(msg.sheetID) || this.ppStyleSheets.get(msg.sheetID) if (!styleSheet) { logger.warn("No stylesheet was created for ", msg) @@ -335,7 +356,7 @@ export default class DOMManager extends ListWalker<Message> { deleteRule(styleSheet, msg) return - case "adopted_ss_replace": + case MType.AdoptedSsReplace: styleSheet = this.styleSheets.get(msg.sheetID) if (!styleSheet) { logger.warn("No stylesheet was created for ", msg) @@ -344,7 +365,7 @@ export default class DOMManager extends ListWalker<Message> { // @ts-ignore styleSheet.replaceSync(msg.text) return - case "adopted_ss_add_owner": + case MType.AdoptedSsAddOwner: vn = this.vRoots.get(msg.id) if (!vn) { // non-constructed case @@ -369,7 +390,7 @@ export default class DOMManager extends ListWalker<Message> { //@ts-ignore vn.node.adoptedStyleSheets = [...vn.node.adoptedStyleSheets, styleSheet] return - case "adopted_ss_remove_owner": + case MType.AdoptedSsRemoveOwner: styleSheet = this.styleSheets.get(msg.sheetID) if (!styleSheet) { logger.warn("No stylesheet was created for ", msg) @@ -380,7 +401,7 @@ export default class DOMManager extends ListWalker<Message> { //@ts-ignore vn.node.adoptedStyleSheets = [...vn.node.adoptedStyleSheets].filter(s => s !== styleSheet) return - case "load_font_face": + case MType.LoadFontFace: vn = this.vRoots.get(msg.parentID) if (!vn) { logger.error("Node not found", msg); return } if (vn instanceof VShadowRoot) { logger.error(`Node ${vn} expected to be a Document`, msg); return } diff --git a/frontend/app/player/MessageDistributor/managers/DOM/FocusManager.ts b/frontend/app/player/web/managers/DOM/FocusManager.ts similarity index 93% rename from frontend/app/player/MessageDistributor/managers/DOM/FocusManager.ts rename to frontend/app/player/web/managers/DOM/FocusManager.ts index 6f80ed16c..174335473 100644 --- a/frontend/app/player/MessageDistributor/managers/DOM/FocusManager.ts +++ b/frontend/app/player/web/managers/DOM/FocusManager.ts @@ -1,7 +1,7 @@ import logger from 'App/logger'; import type { SetNodeFocus } from '../../messages'; import type { VElement } from './VirtualDOM'; -import ListWalker from '../ListWalker'; +import ListWalker from '../../../common/ListWalker'; const FOCUS_CLASS = "-openreplay-focus" diff --git a/frontend/app/player/MessageDistributor/managers/DOM/StylesManager.ts b/frontend/app/player/web/managers/DOM/StylesManager.ts similarity index 54% rename from frontend/app/player/MessageDistributor/managers/DOM/StylesManager.ts rename to frontend/app/player/web/managers/DOM/StylesManager.ts index b6dddcdd9..295b95d2f 100644 --- a/frontend/app/player/MessageDistributor/managers/DOM/StylesManager.ts +++ b/frontend/app/player/web/managers/DOM/StylesManager.ts @@ -1,11 +1,8 @@ -import type StatedScreen from '../../StatedScreen'; +import type Screen from '../../Screen/Screen'; import type { CssInsertRule, CssDeleteRule } from '../../messages'; type CSSRuleMessage = CssInsertRule | CssDeleteRule; -import ListWalker from '../ListWalker'; - - const HOVER_CN = "-openreplay-hover"; const HOVER_SELECTOR = `.${HOVER_CN}`; @@ -21,17 +18,14 @@ export function rewriteNodeStyleSheet(doc: Document, node: HTMLLinkElement | HTM } } -export default class StylesManager extends ListWalker<CSSRuleMessage> { +export default class StylesManager { private linkLoadingCount: number = 0; private linkLoadPromises: Array<Promise<void>> = []; private skipCSSLinks: Array<string> = []; // should be common for all pages - constructor(private readonly screen: StatedScreen) { - super(); - } + constructor(private readonly screen: Screen, private readonly setLoading: (flag: boolean) => void) {} reset():void { - super.reset(); this.linkLoadingCount = 0; this.linkLoadPromises = []; @@ -43,7 +37,7 @@ export default class StylesManager extends ListWalker<CSSRuleMessage> { const promise = new Promise<void>((resolve) => { if (this.skipCSSLinks.includes(value)) resolve(); this.linkLoadingCount++; - this.screen.setCSSLoading(true); + this.setLoading(true); const addSkipAndResolve = () => { this.skipCSSLinks.push(value); // watch out resolve() @@ -62,44 +56,13 @@ export default class StylesManager extends ListWalker<CSSRuleMessage> { clearTimeout(timeoutId); this.linkLoadingCount--; if (this.linkLoadingCount === 0) { - this.screen.setCSSLoading(false); + this.setLoading(false); } }); this.linkLoadPromises.push(promise); } - private manageRule = (msg: CSSRuleMessage):void => { - // if (msg.tp === "css_insert_rule") { - // let styleSheet = this.#screen.document.styleSheets[ msg.stylesheetID ]; - // if (!styleSheet) { - // logger.log("No stylesheet with corresponding ID found: ", msg) - // styleSheet = this.#screen.document.styleSheets[0]; - // if (!styleSheet) { - // return; - // } - // } - // try { - // styleSheet.insertRule(msg.rule, msg.index); - // } catch (e) { - // logger.log(e, msg) - // //const index = Math.min(msg.index, styleSheet.cssRules.length); - // styleSheet.insertRule(msg.rule, styleSheet.cssRules.length); - // //styleSheet.ownerNode.innerHTML += msg.rule; - // } - // } - // if (msg.tp === "css_delete_rule") { - // // console.warn('Warning: STYLESHEET_DELETE_RULE msg') - // const styleSheet = this.#screen.document.styleSheets[msg.stylesheetID]; - // if (!styleSheet) { - // logger.log("No stylesheet with corresponding ID found: ", msg) - // return; - // } - // styleSheet.deleteRule(msg.index); - // } - } - - moveReady(t: number): Promise<void> { + moveReady(t: number): Promise<void[]> { return Promise.all(this.linkLoadPromises) - .then(() => this.moveApply(t, this.manageRule)); } } diff --git a/frontend/app/player/MessageDistributor/managers/DOM/VirtualDOM.ts b/frontend/app/player/web/managers/DOM/VirtualDOM.ts similarity index 100% rename from frontend/app/player/MessageDistributor/managers/DOM/VirtualDOM.ts rename to frontend/app/player/web/managers/DOM/VirtualDOM.ts diff --git a/frontend/app/player/MessageDistributor/managers/DOM/safeCSSRules.ts b/frontend/app/player/web/managers/DOM/safeCSSRules.ts similarity index 100% rename from frontend/app/player/MessageDistributor/managers/DOM/safeCSSRules.ts rename to frontend/app/player/web/managers/DOM/safeCSSRules.ts diff --git a/frontend/app/player/web/managers/MouseMoveManager.ts b/frontend/app/player/web/managers/MouseMoveManager.ts new file mode 100644 index 000000000..1b19d7e5b --- /dev/null +++ b/frontend/app/player/web/managers/MouseMoveManager.ts @@ -0,0 +1,49 @@ +import type Screen from '../Screen/Screen' +import type { MouseMove } from '../messages' + +import ListWalker from '../../common/ListWalker' + +const HOVER_CLASS = "-openreplay-hover"; +const HOVER_CLASS_DEPR = "-asayer-hover"; + +export default class MouseMoveManager extends ListWalker<MouseMove> { + private hoverElements: Array<Element> = [] + + constructor(private screen: Screen) {super()} + + // private getCursorTarget() { + // return this.screen.getElementFromInternalPoint(this.current) + // } + + private getCursorTargets() { + return this.screen.getElementsFromInternalPoint(this.current) + } + + private updateHover(): void { + const curHoverElements = this.getCursorTargets() + const diffAdd = curHoverElements.filter(elem => !this.hoverElements.includes(elem)) + const diffRemove = this.hoverElements.filter(elem => !curHoverElements.includes(elem)) + this.hoverElements = curHoverElements + diffAdd.forEach(elem => { + elem.classList.add(HOVER_CLASS) + elem.classList.add(HOVER_CLASS_DEPR) + }) + diffRemove.forEach(elem => { + elem.classList.remove(HOVER_CLASS) + elem.classList.remove(HOVER_CLASS_DEPR) + }) + } + + reset(): void { + this.hoverElements.length = 0 + } + + move(t: number) { + const lastMouseMove = this.moveGetLast(t) + if (!!lastMouseMove) { + this.screen.cursor.move(lastMouseMove) + //window.getComputedStyle(this.screen.getCursorTarget()).cursor === 'pointer' // might nfluence performance though + this.updateHover() + } + } +} diff --git a/frontend/app/player/MessageDistributor/managers/PagesManager.ts b/frontend/app/player/web/managers/PagesManager.ts similarity index 72% rename from frontend/app/player/MessageDistributor/managers/PagesManager.ts rename to frontend/app/player/web/managers/PagesManager.ts index 9a4398246..dbc64bb72 100644 --- a/frontend/app/player/MessageDistributor/managers/PagesManager.ts +++ b/frontend/app/player/web/managers/PagesManager.ts @@ -1,28 +1,26 @@ -import type StatedScreen from '../StatedScreen'; +import type Screen from '../Screen/Screen'; import type { Message } from '../messages'; -import ListWalker from './ListWalker'; +import { MType } from '../messages'; +import ListWalker from '../../common/ListWalker'; import DOMManager from './DOM/DOMManager'; export default class PagesManager extends ListWalker<DOMManager> { private currentPage: DOMManager | null = null - private isMobile: boolean; - private screen: StatedScreen; - - constructor(screen: StatedScreen, isMobile: boolean) { - super() - this.screen = screen - this.isMobile = isMobile - } + constructor( + private screen: Screen, + private isMobile: boolean, + private setCssLoading: ConstructorParameters<typeof DOMManager>[3], +) { super() } /* Assumed that messages added in a correct time sequence. */ appendMessage(m: Message): void { - if (m.tp === "create_document") { - super.append(new DOMManager(this.screen, this.isMobile, m.time)) + if (m.tp === MType.CreateDocument) { + super.append(new DOMManager(this.screen, this.isMobile, m.time, this.setCssLoading)) } if (this.last === null) { // Log wrong diff --git a/frontend/app/player/MessageDistributor/managers/PerformanceTrackManager.ts b/frontend/app/player/web/managers/PerformanceTrackManager.ts similarity index 98% rename from frontend/app/player/MessageDistributor/managers/PerformanceTrackManager.ts rename to frontend/app/player/web/managers/PerformanceTrackManager.ts index c4a4a8e63..98ce2c0b9 100644 --- a/frontend/app/player/MessageDistributor/managers/PerformanceTrackManager.ts +++ b/frontend/app/player/web/managers/PerformanceTrackManager.ts @@ -1,6 +1,6 @@ import type { PerformanceTrack, SetPageVisibility } from '../messages'; -import ListWalker from './ListWalker'; +import ListWalker from '../../common/ListWalker'; export type PerformanceChartPoint = { time: number, diff --git a/frontend/app/player/MessageDistributor/managers/WindowNodeCounter.ts b/frontend/app/player/web/managers/WindowNodeCounter.ts similarity index 100% rename from frontend/app/player/MessageDistributor/managers/WindowNodeCounter.ts rename to frontend/app/player/web/managers/WindowNodeCounter.ts diff --git a/frontend/app/player/web/messages/JSONRawMessageReader.ts b/frontend/app/player/web/messages/JSONRawMessageReader.ts new file mode 100644 index 000000000..ca193c326 --- /dev/null +++ b/frontend/app/player/web/messages/JSONRawMessageReader.ts @@ -0,0 +1,36 @@ +import type { RawMessage } from './raw.gen' +import type { TrackerMessage } from './tracker.gen' +import translate from './tracker.gen' +import { TP_MAP } from './tracker-legacy.gen' +import resolveURL from './urlBasedResolver' + + +function legacyTranslate(msg: any): RawMessage | null { + const type = TP_MAP[msg._id as keyof typeof TP_MAP] + if (!type) { // msg._id can be other than keyof TP_MAP, in fact + return null + } + msg.tp = type + delete msg._id + return msg as RawMessage +} + + +export default class JSONRawMessageReader { + constructor(private messages: TrackerMessage[] = []){} + append(messages: TrackerMessage[]) { + this.messages = this.messages.concat(messages) + } + readMessage(): RawMessage | null { + let msg = this.messages.shift() + if (!msg) { return null } + const rawMsg = Array.isArray(msg) + ? translate(msg) + : legacyTranslate(msg) + if (!rawMsg) { + return this.readMessage() + } + return resolveURL(rawMsg) + } + +} diff --git a/frontend/app/player/MessageDistributor/messages/MFileReader.ts b/frontend/app/player/web/messages/MFileReader.ts similarity index 72% rename from frontend/app/player/MessageDistributor/messages/MFileReader.ts rename to frontend/app/player/web/messages/MFileReader.ts index 1b4bb5751..d1b131595 100644 --- a/frontend/app/player/MessageDistributor/messages/MFileReader.ts +++ b/frontend/app/player/web/messages/MFileReader.ts @@ -1,7 +1,9 @@ -import type { Message } from './message'; -import type { RawMessage } from './raw'; -import logger from 'App/logger'; -import RawMessageReader from './RawMessageReader'; +import type { Message } from './message.gen'; +import type { RawMessage } from './raw.gen'; +import { MType } from './raw.gen'; +import RawMessageReader from './RawMessageReader.gen'; +import resolveURL from './urlBasedResolver' + // TODO: composition instead of inheritance // needSkipMessage() and next() methods here use buf and p protected properties, @@ -10,7 +12,7 @@ export default class MFileReader extends RawMessageReader { private pLastMessageID: number = 0 private currentTime: number public error: boolean = false - constructor(data: Uint8Array, private startTime?: number) { + constructor(data: Uint8Array, private startTime?: number, private logger=console) { super(data) } @@ -42,12 +44,12 @@ export default class MFileReader extends RawMessageReader { return msg } catch (e) { this.error = true - logger.error("Read message error:", e) + this.logger.error("Read message error:", e) return null } } - next(): [ Message, number] | null { + readNext(): Message & { _index: number } | null { if (this.error || !this.hasNextByte()) { return null } @@ -57,7 +59,8 @@ export default class MFileReader extends RawMessageReader { if (!skippedMessage) { return null } - logger.group("Openreplay: Skipping messages ", skippedMessage) + this.logger.group("Openreplay: Skipping messages ", skippedMessage) + } this.pLastMessageID = this.p @@ -67,20 +70,20 @@ export default class MFileReader extends RawMessageReader { return null } - if (rMsg.tp === "timestamp") { + if (rMsg.tp === MType.Timestamp) { if (!this.startTime) { this.startTime = rMsg.timestamp } this.currentTime = rMsg.timestamp - this.startTime - return this.next() + return this.readNext() } const index = this.getLastMessageID() - const msg = Object.assign(rMsg, { + const msg = Object.assign(resolveURL(rMsg), { time: this.currentTime, _index: index, }) - return [msg, index] + return msg } } diff --git a/frontend/app/player/MessageDistributor/messages/MStreamReader.ts b/frontend/app/player/web/messages/MStreamReader.ts similarity index 59% rename from frontend/app/player/MessageDistributor/messages/MStreamReader.ts rename to frontend/app/player/web/messages/MStreamReader.ts index ede3719ac..a61e374cd 100644 --- a/frontend/app/player/MessageDistributor/messages/MStreamReader.ts +++ b/frontend/app/player/web/messages/MStreamReader.ts @@ -1,20 +1,20 @@ -import type { Message } from './message' -import type { RawMessage } from './raw' -import RawMessageReader from './RawMessageReader' +import type { Message } from './message.gen' +import type { RawMessage } from './raw.gen' +import { MType } from './raw.gen' interface RawMessageReaderI { readMessage(): RawMessage | null } export default class MStreamReader { - constructor(private readonly r: RawMessageReaderI = new RawMessageReader(), private startTs: number = 0){} + constructor(private readonly r: RawMessageReaderI, private startTs: number = 0){} private t: number = 0 private idx: number = 0 - readNext(): Message | null { + readNext(): Message & { _index: number } | null { let msg = this.r.readMessage() if (msg === null) { return null } - if (msg.tp === "timestamp") { + if (msg.tp === MType.Timestamp) { this.startTs = this.startTs || msg.timestamp this.t = msg.timestamp - this.startTs return this.readNext() diff --git a/frontend/app/player/MessageDistributor/messages/PrimitiveReader.ts b/frontend/app/player/web/messages/PrimitiveReader.ts similarity index 100% rename from frontend/app/player/MessageDistributor/messages/PrimitiveReader.ts rename to frontend/app/player/web/messages/PrimitiveReader.ts diff --git a/frontend/app/player/MessageDistributor/messages/RawMessageReader.ts b/frontend/app/player/web/messages/RawMessageReader.gen.ts similarity index 82% rename from frontend/app/player/MessageDistributor/messages/RawMessageReader.ts rename to frontend/app/player/web/messages/RawMessageReader.gen.ts index 80ebb30b1..793f609f5 100644 --- a/frontend/app/player/MessageDistributor/messages/RawMessageReader.ts +++ b/frontend/app/player/web/messages/RawMessageReader.gen.ts @@ -2,7 +2,8 @@ /* eslint-disable */ import PrimitiveReader from './PrimitiveReader' -import type { RawMessage } from './raw' +import { MType } from './raw.gen' +import type { RawMessage } from './raw.gen' export default class RawMessageReader extends PrimitiveReader { @@ -21,7 +22,7 @@ export default class RawMessageReader extends PrimitiveReader { case 0: { const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() } return { - tp: "timestamp", + tp: MType.Timestamp, timestamp, }; } @@ -31,7 +32,7 @@ export default class RawMessageReader extends PrimitiveReader { const referrer = this.readString(); if (referrer === null) { return resetPointer() } const navigationStart = this.readUint(); if (navigationStart === null) { return resetPointer() } return { - tp: "set_page_location", + tp: MType.SetPageLocation, url, referrer, navigationStart, @@ -42,7 +43,7 @@ export default class RawMessageReader extends PrimitiveReader { const width = this.readUint(); if (width === null) { return resetPointer() } const height = this.readUint(); if (height === null) { return resetPointer() } return { - tp: "set_viewport_size", + tp: MType.SetViewportSize, width, height, }; @@ -52,7 +53,7 @@ export default class RawMessageReader extends PrimitiveReader { const x = this.readInt(); if (x === null) { return resetPointer() } const y = this.readInt(); if (y === null) { return resetPointer() } return { - tp: "set_viewport_scroll", + tp: MType.SetViewportScroll, x, y, }; @@ -61,7 +62,7 @@ export default class RawMessageReader extends PrimitiveReader { case 7: { return { - tp: "create_document", + tp: MType.CreateDocument, }; } @@ -73,7 +74,7 @@ export default class RawMessageReader extends PrimitiveReader { const tag = this.readString(); if (tag === null) { return resetPointer() } const svg = this.readBoolean(); if (svg === null) { return resetPointer() } return { - tp: "create_element_node", + tp: MType.CreateElementNode, id, parentID, index, @@ -87,7 +88,7 @@ export default class RawMessageReader extends PrimitiveReader { const parentID = this.readUint(); if (parentID === null) { return resetPointer() } const index = this.readUint(); if (index === null) { return resetPointer() } return { - tp: "create_text_node", + tp: MType.CreateTextNode, id, parentID, index, @@ -99,7 +100,7 @@ export default class RawMessageReader extends PrimitiveReader { const parentID = this.readUint(); if (parentID === null) { return resetPointer() } const index = this.readUint(); if (index === null) { return resetPointer() } return { - tp: "move_node", + tp: MType.MoveNode, id, parentID, index, @@ -109,7 +110,7 @@ export default class RawMessageReader extends PrimitiveReader { case 11: { const id = this.readUint(); if (id === null) { return resetPointer() } return { - tp: "remove_node", + tp: MType.RemoveNode, id, }; } @@ -119,7 +120,7 @@ export default class RawMessageReader extends PrimitiveReader { const name = this.readString(); if (name === null) { return resetPointer() } const value = this.readString(); if (value === null) { return resetPointer() } return { - tp: "set_node_attribute", + tp: MType.SetNodeAttribute, id, name, value, @@ -130,7 +131,7 @@ export default class RawMessageReader extends PrimitiveReader { const id = this.readUint(); if (id === null) { return resetPointer() } const name = this.readString(); if (name === null) { return resetPointer() } return { - tp: "remove_node_attribute", + tp: MType.RemoveNodeAttribute, id, name, }; @@ -140,7 +141,7 @@ export default class RawMessageReader extends PrimitiveReader { const id = this.readUint(); if (id === null) { return resetPointer() } const data = this.readString(); if (data === null) { return resetPointer() } return { - tp: "set_node_data", + tp: MType.SetNodeData, id, data, }; @@ -150,7 +151,7 @@ export default class RawMessageReader extends PrimitiveReader { const id = this.readUint(); if (id === null) { return resetPointer() } const data = this.readString(); if (data === null) { return resetPointer() } return { - tp: "set_css_data", + tp: MType.SetCssData, id, data, }; @@ -161,7 +162,7 @@ export default class RawMessageReader extends PrimitiveReader { const x = this.readInt(); if (x === null) { return resetPointer() } const y = this.readInt(); if (y === null) { return resetPointer() } return { - tp: "set_node_scroll", + tp: MType.SetNodeScroll, id, x, y, @@ -173,7 +174,7 @@ export default class RawMessageReader extends PrimitiveReader { const value = this.readString(); if (value === null) { return resetPointer() } const mask = this.readInt(); if (mask === null) { return resetPointer() } return { - tp: "set_input_value", + tp: MType.SetInputValue, id, value, mask, @@ -184,7 +185,7 @@ export default class RawMessageReader extends PrimitiveReader { const id = this.readUint(); if (id === null) { return resetPointer() } const checked = this.readBoolean(); if (checked === null) { return resetPointer() } return { - tp: "set_input_checked", + tp: MType.SetInputChecked, id, checked, }; @@ -194,17 +195,39 @@ export default class RawMessageReader extends PrimitiveReader { const x = this.readUint(); if (x === null) { return resetPointer() } const y = this.readUint(); if (y === null) { return resetPointer() } return { - tp: "mouse_move", + tp: MType.MouseMove, x, y, }; } + case 21: { + const type = this.readString(); if (type === null) { return resetPointer() } + const method = this.readString(); if (method === null) { return resetPointer() } + const url = this.readString(); if (url === null) { return resetPointer() } + const request = this.readString(); if (request === null) { return resetPointer() } + const response = this.readString(); if (response === null) { return resetPointer() } + const status = this.readUint(); if (status === null) { return resetPointer() } + const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() } + const duration = this.readUint(); if (duration === null) { return resetPointer() } + return { + tp: MType.NetworkRequest, + type, + method, + url, + request, + response, + status, + timestamp, + duration, + }; + } + case 22: { const level = this.readString(); if (level === null) { return resetPointer() } const value = this.readString(); if (value === null) { return resetPointer() } return { - tp: "console_log", + tp: MType.ConsoleLog, level, value, }; @@ -215,7 +238,7 @@ export default class RawMessageReader extends PrimitiveReader { const rule = this.readString(); if (rule === null) { return resetPointer() } const index = this.readUint(); if (index === null) { return resetPointer() } return { - tp: "css_insert_rule", + tp: MType.CssInsertRule, id, rule, index, @@ -226,7 +249,7 @@ export default class RawMessageReader extends PrimitiveReader { const id = this.readUint(); if (id === null) { return resetPointer() } const index = this.readUint(); if (index === null) { return resetPointer() } return { - tp: "css_delete_rule", + tp: MType.CssDeleteRule, id, index, }; @@ -241,7 +264,7 @@ export default class RawMessageReader extends PrimitiveReader { const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() } const duration = this.readUint(); if (duration === null) { return resetPointer() } return { - tp: "fetch", + tp: MType.Fetch, method, url, request, @@ -258,7 +281,7 @@ export default class RawMessageReader extends PrimitiveReader { const args = this.readString(); if (args === null) { return resetPointer() } const result = this.readString(); if (result === null) { return resetPointer() } return { - tp: "profiler", + tp: MType.Profiler, name, duration, args, @@ -270,7 +293,7 @@ export default class RawMessageReader extends PrimitiveReader { const key = this.readString(); if (key === null) { return resetPointer() } const value = this.readString(); if (value === null) { return resetPointer() } return { - tp: "o_table", + tp: MType.OTable, key, value, }; @@ -281,7 +304,7 @@ export default class RawMessageReader extends PrimitiveReader { const state = this.readString(); if (state === null) { return resetPointer() } const duration = this.readUint(); if (duration === null) { return resetPointer() } return { - tp: "redux", + tp: MType.Redux, action, state, duration, @@ -292,7 +315,7 @@ export default class RawMessageReader extends PrimitiveReader { const mutation = this.readString(); if (mutation === null) { return resetPointer() } const state = this.readString(); if (state === null) { return resetPointer() } return { - tp: "vuex", + tp: MType.Vuex, mutation, state, }; @@ -302,7 +325,7 @@ export default class RawMessageReader extends PrimitiveReader { const type = this.readString(); if (type === null) { return resetPointer() } const payload = this.readString(); if (payload === null) { return resetPointer() } return { - tp: "mob_x", + tp: MType.MobX, type, payload, }; @@ -313,7 +336,7 @@ export default class RawMessageReader extends PrimitiveReader { const state = this.readString(); if (state === null) { return resetPointer() } const duration = this.readUint(); if (duration === null) { return resetPointer() } return { - tp: "ng_rx", + tp: MType.NgRx, action, state, duration, @@ -326,7 +349,7 @@ export default class RawMessageReader extends PrimitiveReader { const variables = this.readString(); if (variables === null) { return resetPointer() } const response = this.readString(); if (response === null) { return resetPointer() } return { - tp: "graph_ql", + tp: MType.GraphQl, operationKind, operationName, variables, @@ -340,7 +363,7 @@ export default class RawMessageReader extends PrimitiveReader { const totalJSHeapSize = this.readUint(); if (totalJSHeapSize === null) { return resetPointer() } const usedJSHeapSize = this.readUint(); if (usedJSHeapSize === null) { return resetPointer() } return { - tp: "performance_track", + tp: MType.PerformanceTrack, frames, ticks, totalJSHeapSize, @@ -348,11 +371,55 @@ export default class RawMessageReader extends PrimitiveReader { }; } + case 50: { + const key = this.readUint(); if (key === null) { return resetPointer() } + const value = this.readString(); if (value === null) { return resetPointer() } + return { + tp: MType.StringDict, + key, + value, + }; + } + + case 51: { + const id = this.readUint(); if (id === null) { return resetPointer() } + const nameKey = this.readUint(); if (nameKey === null) { return resetPointer() } + const valueKey = this.readUint(); if (valueKey === null) { return resetPointer() } + return { + tp: MType.SetNodeAttributeDict, + id, + nameKey, + valueKey, + }; + } + + case 53: { + const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() } + const duration = this.readUint(); if (duration === null) { return resetPointer() } + const ttfb = this.readUint(); if (ttfb === null) { return resetPointer() } + const headerSize = this.readUint(); if (headerSize === null) { return resetPointer() } + const encodedBodySize = this.readUint(); if (encodedBodySize === null) { return resetPointer() } + const decodedBodySize = this.readUint(); if (decodedBodySize === null) { return resetPointer() } + const url = this.readString(); if (url === null) { return resetPointer() } + const initiator = this.readString(); if (initiator === null) { return resetPointer() } + return { + tp: MType.ResourceTiming, + timestamp, + duration, + ttfb, + headerSize, + encodedBodySize, + decodedBodySize, + url, + initiator, + }; + } + case 54: { const downlink = this.readUint(); if (downlink === null) { return resetPointer() } const type = this.readString(); if (type === null) { return resetPointer() } return { - tp: "connection_information", + tp: MType.ConnectionInformation, downlink, type, }; @@ -361,7 +428,7 @@ export default class RawMessageReader extends PrimitiveReader { case 55: { const hidden = this.readBoolean(); if (hidden === null) { return resetPointer() } return { - tp: "set_page_visibility", + tp: MType.SetPageVisibility, hidden, }; } @@ -372,7 +439,7 @@ export default class RawMessageReader extends PrimitiveReader { const source = this.readString(); if (source === null) { return resetPointer() } const descriptors = this.readString(); if (descriptors === null) { return resetPointer() } return { - tp: "load_font_face", + tp: MType.LoadFontFace, parentID, family, source, @@ -383,7 +450,7 @@ export default class RawMessageReader extends PrimitiveReader { case 58: { const id = this.readInt(); if (id === null) { return resetPointer() } return { - tp: "set_node_focus", + tp: MType.SetNodeFocus, id, }; } @@ -397,7 +464,7 @@ export default class RawMessageReader extends PrimitiveReader { const containerId = this.readString(); if (containerId === null) { return resetPointer() } const containerName = this.readString(); if (containerName === null) { return resetPointer() } return { - tp: "long_task", + tp: MType.LongTask, timestamp, duration, context, @@ -414,7 +481,7 @@ export default class RawMessageReader extends PrimitiveReader { const value = this.readString(); if (value === null) { return resetPointer() } const baseURL = this.readString(); if (baseURL === null) { return resetPointer() } return { - tp: "set_node_attribute_url_based", + tp: MType.SetNodeAttributeURLBased, id, name, value, @@ -427,7 +494,7 @@ export default class RawMessageReader extends PrimitiveReader { const data = this.readString(); if (data === null) { return resetPointer() } const baseURL = this.readString(); if (baseURL === null) { return resetPointer() } return { - tp: "set_css_data_url_based", + tp: MType.SetCssDataURLBased, id, data, baseURL, @@ -440,7 +507,7 @@ export default class RawMessageReader extends PrimitiveReader { const index = this.readUint(); if (index === null) { return resetPointer() } const baseURL = this.readString(); if (baseURL === null) { return resetPointer() } return { - tp: "css_insert_rule_url_based", + tp: MType.CssInsertRuleURLBased, id, rule, index, @@ -454,7 +521,7 @@ export default class RawMessageReader extends PrimitiveReader { const label = this.readString(); if (label === null) { return resetPointer() } const selector = this.readString(); if (selector === null) { return resetPointer() } return { - tp: "mouse_click", + tp: MType.MouseClick, id, hesitationTime, label, @@ -466,7 +533,7 @@ export default class RawMessageReader extends PrimitiveReader { const frameID = this.readUint(); if (frameID === null) { return resetPointer() } const id = this.readUint(); if (id === null) { return resetPointer() } return { - tp: "create_i_frame_document", + tp: MType.CreateIFrameDocument, frameID, id, }; @@ -477,7 +544,7 @@ export default class RawMessageReader extends PrimitiveReader { const text = this.readString(); if (text === null) { return resetPointer() } const baseURL = this.readString(); if (baseURL === null) { return resetPointer() } return { - tp: "adopted_ss_replace_url_based", + tp: MType.AdoptedSsReplaceURLBased, sheetID, text, baseURL, @@ -488,7 +555,7 @@ export default class RawMessageReader extends PrimitiveReader { const sheetID = this.readUint(); if (sheetID === null) { return resetPointer() } const text = this.readString(); if (text === null) { return resetPointer() } return { - tp: "adopted_ss_replace", + tp: MType.AdoptedSsReplace, sheetID, text, }; @@ -500,7 +567,7 @@ export default class RawMessageReader extends PrimitiveReader { const index = this.readUint(); if (index === null) { return resetPointer() } const baseURL = this.readString(); if (baseURL === null) { return resetPointer() } return { - tp: "adopted_ss_insert_rule_url_based", + tp: MType.AdoptedSsInsertRuleURLBased, sheetID, rule, index, @@ -513,7 +580,7 @@ export default class RawMessageReader extends PrimitiveReader { const rule = this.readString(); if (rule === null) { return resetPointer() } const index = this.readUint(); if (index === null) { return resetPointer() } return { - tp: "adopted_ss_insert_rule", + tp: MType.AdoptedSsInsertRule, sheetID, rule, index, @@ -524,7 +591,7 @@ export default class RawMessageReader extends PrimitiveReader { const sheetID = this.readUint(); if (sheetID === null) { return resetPointer() } const index = this.readUint(); if (index === null) { return resetPointer() } return { - tp: "adopted_ss_delete_rule", + tp: MType.AdoptedSsDeleteRule, sheetID, index, }; @@ -534,7 +601,7 @@ export default class RawMessageReader extends PrimitiveReader { const sheetID = this.readUint(); if (sheetID === null) { return resetPointer() } const id = this.readUint(); if (id === null) { return resetPointer() } return { - tp: "adopted_ss_add_owner", + tp: MType.AdoptedSsAddOwner, sheetID, id, }; @@ -544,7 +611,7 @@ export default class RawMessageReader extends PrimitiveReader { const sheetID = this.readUint(); if (sheetID === null) { return resetPointer() } const id = this.readUint(); if (id === null) { return resetPointer() } return { - tp: "adopted_ss_remove_owner", + tp: MType.AdoptedSsRemoveOwner, sheetID, id, }; @@ -554,7 +621,7 @@ export default class RawMessageReader extends PrimitiveReader { const mutation = this.readString(); if (mutation === null) { return resetPointer() } const state = this.readString(); if (state === null) { return resetPointer() } return { - tp: "zustand", + tp: MType.Zustand, mutation, state, }; @@ -572,7 +639,7 @@ export default class RawMessageReader extends PrimitiveReader { const userDeviceType = this.readString(); if (userDeviceType === null) { return resetPointer() } const userCountry = this.readString(); if (userCountry === null) { return resetPointer() } return { - tp: "ios_session_start", + tp: MType.IosSessionStart, timestamp, projectID, trackerVersion, @@ -592,7 +659,7 @@ export default class RawMessageReader extends PrimitiveReader { const name = this.readString(); if (name === null) { return resetPointer() } const payload = this.readString(); if (payload === null) { return resetPointer() } return { - tp: "ios_custom_event", + tp: MType.IosCustomEvent, timestamp, length, name, @@ -608,7 +675,7 @@ export default class RawMessageReader extends PrimitiveReader { const width = this.readUint(); if (width === null) { return resetPointer() } const height = this.readUint(); if (height === null) { return resetPointer() } return { - tp: "ios_screen_changes", + tp: MType.IosScreenChanges, timestamp, length, x, @@ -625,7 +692,7 @@ export default class RawMessageReader extends PrimitiveReader { const x = this.readUint(); if (x === null) { return resetPointer() } const y = this.readUint(); if (y === null) { return resetPointer() } return { - tp: "ios_click_event", + tp: MType.IosClickEvent, timestamp, length, label, @@ -640,7 +707,7 @@ export default class RawMessageReader extends PrimitiveReader { const name = this.readString(); if (name === null) { return resetPointer() } const value = this.readUint(); if (value === null) { return resetPointer() } return { - tp: "ios_performance_event", + tp: MType.IosPerformanceEvent, timestamp, length, name, @@ -654,7 +721,7 @@ export default class RawMessageReader extends PrimitiveReader { const severity = this.readString(); if (severity === null) { return resetPointer() } const content = this.readString(); if (content === null) { return resetPointer() } return { - tp: "ios_log", + tp: MType.IosLog, timestamp, length, severity, @@ -673,7 +740,7 @@ export default class RawMessageReader extends PrimitiveReader { const method = this.readString(); if (method === null) { return resetPointer() } const status = this.readUint(); if (status === null) { return resetPointer() } return { - tp: "ios_network_call", + tp: MType.IosNetworkCall, timestamp, length, duration, diff --git a/frontend/app/player/web/messages/filters.gen.ts b/frontend/app/player/web/messages/filters.gen.ts new file mode 100644 index 000000000..cd664201e --- /dev/null +++ b/frontend/app/player/web/messages/filters.gen.ts @@ -0,0 +1,9 @@ +// Auto-generated, do not edit +/* eslint-disable */ + +import { MType } from './raw.gen' + +const DOM_TYPES = [0,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19,20,37,38,49,50,51,54,55,57,58,59,60,61,67,69,70,71,72,73,74,75,76,77,90,93,96,100,102,103,105] +export function isDOMType(t: MType) { + return DOM_TYPES.includes(t) +} \ No newline at end of file diff --git a/frontend/app/player/web/messages/index.ts b/frontend/app/player/web/messages/index.ts new file mode 100644 index 000000000..401980555 --- /dev/null +++ b/frontend/app/player/web/messages/index.ts @@ -0,0 +1,2 @@ +export * from './message.gen' +export { MType } from './raw.gen' \ No newline at end of file diff --git a/frontend/app/player/MessageDistributor/messages/message.ts b/frontend/app/player/web/messages/message.gen.ts similarity index 91% rename from frontend/app/player/MessageDistributor/messages/message.ts rename to frontend/app/player/web/messages/message.gen.ts index 3925fb235..b39d9ce46 100644 --- a/frontend/app/player/MessageDistributor/messages/message.ts +++ b/frontend/app/player/web/messages/message.gen.ts @@ -2,7 +2,7 @@ /* eslint-disable */ import type { Timed } from './timed' -import type { RawMessage } from './raw' +import type { RawMessage } from './raw.gen' import type { RawTimestamp, RawSetPageLocation, @@ -21,6 +21,7 @@ import type { RawSetInputValue, RawSetInputChecked, RawMouseMove, + RawNetworkRequest, RawConsoleLog, RawCssInsertRule, RawCssDeleteRule, @@ -33,6 +34,9 @@ import type { RawNgRx, RawGraphQl, RawPerformanceTrack, + RawStringDict, + RawSetNodeAttributeDict, + RawResourceTiming, RawConnectionInformation, RawSetPageVisibility, RawLoadFontFace, @@ -58,7 +62,7 @@ import type { RawIosPerformanceEvent, RawIosLog, RawIosNetworkCall, -} from './raw' +} from './raw.gen' export type Message = RawMessage & Timed @@ -97,6 +101,8 @@ export type SetInputChecked = RawSetInputChecked & Timed export type MouseMove = RawMouseMove & Timed +export type NetworkRequest = RawNetworkRequest & Timed + export type ConsoleLog = RawConsoleLog & Timed export type CssInsertRule = RawCssInsertRule & Timed @@ -121,6 +127,12 @@ export type GraphQl = RawGraphQl & Timed export type PerformanceTrack = RawPerformanceTrack & Timed +export type StringDict = RawStringDict & Timed + +export type SetNodeAttributeDict = RawSetNodeAttributeDict & Timed + +export type ResourceTiming = RawResourceTiming & Timed + export type ConnectionInformation = RawConnectionInformation & Timed export type SetPageVisibility = RawSetPageVisibility & Timed diff --git a/frontend/app/player/MessageDistributor/messages/raw.ts b/frontend/app/player/web/messages/raw.gen.ts similarity index 56% rename from frontend/app/player/MessageDistributor/messages/raw.ts rename to frontend/app/player/web/messages/raw.gen.ts index 0d744e2fd..b51edb40e 100644 --- a/frontend/app/player/MessageDistributor/messages/raw.ts +++ b/frontend/app/player/web/messages/raw.gen.ts @@ -1,38 +1,99 @@ // Auto-generated, do not edit /* eslint-disable */ +export const enum MType { + Timestamp = 0, + SetPageLocation = 4, + SetViewportSize = 5, + SetViewportScroll = 6, + CreateDocument = 7, + CreateElementNode = 8, + CreateTextNode = 9, + MoveNode = 10, + RemoveNode = 11, + SetNodeAttribute = 12, + RemoveNodeAttribute = 13, + SetNodeData = 14, + SetCssData = 15, + SetNodeScroll = 16, + SetInputValue = 18, + SetInputChecked = 19, + MouseMove = 20, + NetworkRequest = 21, + ConsoleLog = 22, + CssInsertRule = 37, + CssDeleteRule = 38, + Fetch = 39, + Profiler = 40, + OTable = 41, + Redux = 44, + Vuex = 45, + MobX = 46, + NgRx = 47, + GraphQl = 48, + PerformanceTrack = 49, + StringDict = 50, + SetNodeAttributeDict = 51, + ResourceTiming = 53, + ConnectionInformation = 54, + SetPageVisibility = 55, + LoadFontFace = 57, + SetNodeFocus = 58, + LongTask = 59, + SetNodeAttributeURLBased = 60, + SetCssDataURLBased = 61, + CssInsertRuleURLBased = 67, + MouseClick = 69, + CreateIFrameDocument = 70, + AdoptedSsReplaceURLBased = 71, + AdoptedSsReplace = 72, + AdoptedSsInsertRuleURLBased = 73, + AdoptedSsInsertRule = 74, + AdoptedSsDeleteRule = 75, + AdoptedSsAddOwner = 76, + AdoptedSsRemoveOwner = 77, + Zustand = 79, + IosSessionStart = 90, + IosCustomEvent = 93, + IosScreenChanges = 96, + IosClickEvent = 100, + IosPerformanceEvent = 102, + IosLog = 103, + IosNetworkCall = 105, +} + export interface RawTimestamp { - tp: "timestamp", + tp: MType.Timestamp, timestamp: number, } export interface RawSetPageLocation { - tp: "set_page_location", + tp: MType.SetPageLocation, url: string, referrer: string, navigationStart: number, } export interface RawSetViewportSize { - tp: "set_viewport_size", + tp: MType.SetViewportSize, width: number, height: number, } export interface RawSetViewportScroll { - tp: "set_viewport_scroll", + tp: MType.SetViewportScroll, x: number, y: number, } export interface RawCreateDocument { - tp: "create_document", + tp: MType.CreateDocument, } export interface RawCreateElementNode { - tp: "create_element_node", + tp: MType.CreateElementNode, id: number, parentID: number, index: number, @@ -41,96 +102,108 @@ export interface RawCreateElementNode { } export interface RawCreateTextNode { - tp: "create_text_node", + tp: MType.CreateTextNode, id: number, parentID: number, index: number, } export interface RawMoveNode { - tp: "move_node", + tp: MType.MoveNode, id: number, parentID: number, index: number, } export interface RawRemoveNode { - tp: "remove_node", + tp: MType.RemoveNode, id: number, } export interface RawSetNodeAttribute { - tp: "set_node_attribute", + tp: MType.SetNodeAttribute, id: number, name: string, value: string, } export interface RawRemoveNodeAttribute { - tp: "remove_node_attribute", + tp: MType.RemoveNodeAttribute, id: number, name: string, } export interface RawSetNodeData { - tp: "set_node_data", + tp: MType.SetNodeData, id: number, data: string, } export interface RawSetCssData { - tp: "set_css_data", + tp: MType.SetCssData, id: number, data: string, } export interface RawSetNodeScroll { - tp: "set_node_scroll", + tp: MType.SetNodeScroll, id: number, x: number, y: number, } export interface RawSetInputValue { - tp: "set_input_value", + tp: MType.SetInputValue, id: number, value: string, mask: number, } export interface RawSetInputChecked { - tp: "set_input_checked", + tp: MType.SetInputChecked, id: number, checked: boolean, } export interface RawMouseMove { - tp: "mouse_move", + tp: MType.MouseMove, x: number, y: number, } +export interface RawNetworkRequest { + tp: MType.NetworkRequest, + type: string, + method: string, + url: string, + request: string, + response: string, + status: number, + timestamp: number, + duration: number, +} + export interface RawConsoleLog { - tp: "console_log", + tp: MType.ConsoleLog, level: string, value: string, } export interface RawCssInsertRule { - tp: "css_insert_rule", + tp: MType.CssInsertRule, id: number, rule: string, index: number, } export interface RawCssDeleteRule { - tp: "css_delete_rule", + tp: MType.CssDeleteRule, id: number, index: number, } export interface RawFetch { - tp: "fetch", + tp: MType.Fetch, method: string, url: string, request: string, @@ -141,7 +214,7 @@ export interface RawFetch { } export interface RawProfiler { - tp: "profiler", + tp: MType.Profiler, name: string, duration: number, args: string, @@ -149,39 +222,39 @@ export interface RawProfiler { } export interface RawOTable { - tp: "o_table", + tp: MType.OTable, key: string, value: string, } export interface RawRedux { - tp: "redux", + tp: MType.Redux, action: string, state: string, duration: number, } export interface RawVuex { - tp: "vuex", + tp: MType.Vuex, mutation: string, state: string, } export interface RawMobX { - tp: "mob_x", + tp: MType.MobX, type: string, payload: string, } export interface RawNgRx { - tp: "ng_rx", + tp: MType.NgRx, action: string, state: string, duration: number, } export interface RawGraphQl { - tp: "graph_ql", + tp: MType.GraphQl, operationKind: string, operationName: string, variables: string, @@ -189,26 +262,51 @@ export interface RawGraphQl { } export interface RawPerformanceTrack { - tp: "performance_track", + tp: MType.PerformanceTrack, frames: number, ticks: number, totalJSHeapSize: number, usedJSHeapSize: number, } +export interface RawStringDict { + tp: MType.StringDict, + key: number, + value: string, +} + +export interface RawSetNodeAttributeDict { + tp: MType.SetNodeAttributeDict, + id: number, + nameKey: number, + valueKey: number, +} + +export interface RawResourceTiming { + tp: MType.ResourceTiming, + timestamp: number, + duration: number, + ttfb: number, + headerSize: number, + encodedBodySize: number, + decodedBodySize: number, + url: string, + initiator: string, +} + export interface RawConnectionInformation { - tp: "connection_information", + tp: MType.ConnectionInformation, downlink: number, type: string, } export interface RawSetPageVisibility { - tp: "set_page_visibility", + tp: MType.SetPageVisibility, hidden: boolean, } export interface RawLoadFontFace { - tp: "load_font_face", + tp: MType.LoadFontFace, parentID: number, family: string, source: string, @@ -216,12 +314,12 @@ export interface RawLoadFontFace { } export interface RawSetNodeFocus { - tp: "set_node_focus", + tp: MType.SetNodeFocus, id: number, } export interface RawLongTask { - tp: "long_task", + tp: MType.LongTask, timestamp: number, duration: number, context: number, @@ -232,7 +330,7 @@ export interface RawLongTask { } export interface RawSetNodeAttributeURLBased { - tp: "set_node_attribute_url_based", + tp: MType.SetNodeAttributeURLBased, id: number, name: string, value: string, @@ -240,14 +338,14 @@ export interface RawSetNodeAttributeURLBased { } export interface RawSetCssDataURLBased { - tp: "set_css_data_url_based", + tp: MType.SetCssDataURLBased, id: number, data: string, baseURL: string, } export interface RawCssInsertRuleURLBased { - tp: "css_insert_rule_url_based", + tp: MType.CssInsertRuleURLBased, id: number, rule: string, index: number, @@ -255,7 +353,7 @@ export interface RawCssInsertRuleURLBased { } export interface RawMouseClick { - tp: "mouse_click", + tp: MType.MouseClick, id: number, hesitationTime: number, label: string, @@ -263,26 +361,26 @@ export interface RawMouseClick { } export interface RawCreateIFrameDocument { - tp: "create_i_frame_document", + tp: MType.CreateIFrameDocument, frameID: number, id: number, } export interface RawAdoptedSsReplaceURLBased { - tp: "adopted_ss_replace_url_based", + tp: MType.AdoptedSsReplaceURLBased, sheetID: number, text: string, baseURL: string, } export interface RawAdoptedSsReplace { - tp: "adopted_ss_replace", + tp: MType.AdoptedSsReplace, sheetID: number, text: string, } export interface RawAdoptedSsInsertRuleURLBased { - tp: "adopted_ss_insert_rule_url_based", + tp: MType.AdoptedSsInsertRuleURLBased, sheetID: number, rule: string, index: number, @@ -290,38 +388,38 @@ export interface RawAdoptedSsInsertRuleURLBased { } export interface RawAdoptedSsInsertRule { - tp: "adopted_ss_insert_rule", + tp: MType.AdoptedSsInsertRule, sheetID: number, rule: string, index: number, } export interface RawAdoptedSsDeleteRule { - tp: "adopted_ss_delete_rule", + tp: MType.AdoptedSsDeleteRule, sheetID: number, index: number, } export interface RawAdoptedSsAddOwner { - tp: "adopted_ss_add_owner", + tp: MType.AdoptedSsAddOwner, sheetID: number, id: number, } export interface RawAdoptedSsRemoveOwner { - tp: "adopted_ss_remove_owner", + tp: MType.AdoptedSsRemoveOwner, sheetID: number, id: number, } export interface RawZustand { - tp: "zustand", + tp: MType.Zustand, mutation: string, state: string, } export interface RawIosSessionStart { - tp: "ios_session_start", + tp: MType.IosSessionStart, timestamp: number, projectID: number, trackerVersion: string, @@ -335,7 +433,7 @@ export interface RawIosSessionStart { } export interface RawIosCustomEvent { - tp: "ios_custom_event", + tp: MType.IosCustomEvent, timestamp: number, length: number, name: string, @@ -343,7 +441,7 @@ export interface RawIosCustomEvent { } export interface RawIosScreenChanges { - tp: "ios_screen_changes", + tp: MType.IosScreenChanges, timestamp: number, length: number, x: number, @@ -353,7 +451,7 @@ export interface RawIosScreenChanges { } export interface RawIosClickEvent { - tp: "ios_click_event", + tp: MType.IosClickEvent, timestamp: number, length: number, label: string, @@ -362,7 +460,7 @@ export interface RawIosClickEvent { } export interface RawIosPerformanceEvent { - tp: "ios_performance_event", + tp: MType.IosPerformanceEvent, timestamp: number, length: number, name: string, @@ -370,7 +468,7 @@ export interface RawIosPerformanceEvent { } export interface RawIosLog { - tp: "ios_log", + tp: MType.IosLog, timestamp: number, length: number, severity: string, @@ -378,7 +476,7 @@ export interface RawIosLog { } export interface RawIosNetworkCall { - tp: "ios_network_call", + tp: MType.IosNetworkCall, timestamp: number, length: number, duration: number, @@ -391,4 +489,4 @@ export interface RawIosNetworkCall { } -export type RawMessage = RawTimestamp | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawRedux | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawConnectionInformation | RawSetPageVisibility | RawLoadFontFace | RawSetNodeFocus | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawCssInsertRuleURLBased | RawMouseClick | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawZustand | RawIosSessionStart | RawIosCustomEvent | RawIosScreenChanges | RawIosClickEvent | RawIosPerformanceEvent | RawIosLog | RawIosNetworkCall; +export type RawMessage = RawTimestamp | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawNetworkRequest | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawRedux | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawStringDict | RawSetNodeAttributeDict | RawResourceTiming | RawConnectionInformation | RawSetPageVisibility | RawLoadFontFace | RawSetNodeFocus | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawCssInsertRuleURLBased | RawMouseClick | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawZustand | RawIosSessionStart | RawIosCustomEvent | RawIosScreenChanges | RawIosClickEvent | RawIosPerformanceEvent | RawIosLog | RawIosNetworkCall; diff --git a/frontend/app/player/MessageDistributor/messages/timed.ts b/frontend/app/player/web/messages/timed.ts similarity index 100% rename from frontend/app/player/MessageDistributor/messages/timed.ts rename to frontend/app/player/web/messages/timed.ts diff --git a/frontend/app/player/web/messages/tracker-legacy.gen.ts b/frontend/app/player/web/messages/tracker-legacy.gen.ts new file mode 100644 index 000000000..bd8ba9c85 --- /dev/null +++ b/frontend/app/player/web/messages/tracker-legacy.gen.ts @@ -0,0 +1,64 @@ +// Auto-generated, do not edit + +import { MType } from './raw.gen' + +export const TP_MAP = { + 0: MType.Timestamp, + 4: MType.SetPageLocation, + 5: MType.SetViewportSize, + 6: MType.SetViewportScroll, + 7: MType.CreateDocument, + 8: MType.CreateElementNode, + 9: MType.CreateTextNode, + 10: MType.MoveNode, + 11: MType.RemoveNode, + 12: MType.SetNodeAttribute, + 13: MType.RemoveNodeAttribute, + 14: MType.SetNodeData, + 15: MType.SetCssData, + 16: MType.SetNodeScroll, + 18: MType.SetInputValue, + 19: MType.SetInputChecked, + 20: MType.MouseMove, + 21: MType.NetworkRequest, + 22: MType.ConsoleLog, + 37: MType.CssInsertRule, + 38: MType.CssDeleteRule, + 39: MType.Fetch, + 40: MType.Profiler, + 41: MType.OTable, + 44: MType.Redux, + 45: MType.Vuex, + 46: MType.MobX, + 47: MType.NgRx, + 48: MType.GraphQl, + 49: MType.PerformanceTrack, + 50: MType.StringDict, + 51: MType.SetNodeAttributeDict, + 53: MType.ResourceTiming, + 54: MType.ConnectionInformation, + 55: MType.SetPageVisibility, + 57: MType.LoadFontFace, + 58: MType.SetNodeFocus, + 59: MType.LongTask, + 60: MType.SetNodeAttributeURLBased, + 61: MType.SetCssDataURLBased, + 67: MType.CssInsertRuleURLBased, + 69: MType.MouseClick, + 70: MType.CreateIFrameDocument, + 71: MType.AdoptedSsReplaceURLBased, + 72: MType.AdoptedSsReplace, + 73: MType.AdoptedSsInsertRuleURLBased, + 74: MType.AdoptedSsInsertRule, + 75: MType.AdoptedSsDeleteRule, + 76: MType.AdoptedSsAddOwner, + 77: MType.AdoptedSsRemoveOwner, + 79: MType.Zustand, + 90: MType.IosSessionStart, + 93: MType.IosCustomEvent, + 96: MType.IosScreenChanges, + 100: MType.IosClickEvent, + 102: MType.IosPerformanceEvent, + 103: MType.IosLog, + 105: MType.IosNetworkCall, +} as const diff --git a/frontend/app/player/MessageDistributor/messages/tracker.ts b/frontend/app/player/web/messages/tracker.gen.ts similarity index 73% rename from frontend/app/player/MessageDistributor/messages/tracker.ts rename to frontend/app/player/web/messages/tracker.gen.ts index 41e36f466..2c171011c 100644 --- a/frontend/app/player/MessageDistributor/messages/tracker.ts +++ b/frontend/app/player/web/messages/tracker.gen.ts @@ -1,24 +1,10 @@ // Auto-generated, do not edit /* eslint-disable */ -import type { RawMessage } from './raw' +import type { RawMessage } from './raw.gen' +import { MType } from './raw.gen' -type TrBatchMetadata = [ - type: 81, - version: number, - pageNo: number, - firstIndex: number, - timestamp: number, - location: string, -] - -type TrPartitionedMessage = [ - type: 82, - partNo: number, - partTotal: number, -] - type TrTimestamp = [ type: 0, timestamp: number, @@ -127,6 +113,18 @@ type TrMouseMove = [ y: number, ] +type TrNetworkRequest = [ + type: 21, + type: string, + method: string, + url: string, + request: string, + response: string, + status: number, + timestamp: number, + duration: number, +] + type TrConsoleLog = [ type: 22, level: string, @@ -153,14 +151,7 @@ type TrPageRenderTiming = [ timeToInteractive: number, ] -type TrJSExceptionDeprecated = [ - type: 25, - name: string, - message: string, - payload: string, -] - -type TrRawCustomEvent = [ +type TrCustomEvent = [ type: 27, name: string, payload: string, @@ -267,6 +258,19 @@ type TrPerformanceTrack = [ usedJSHeapSize: number, ] +type TrStringDict = [ + type: 50, + key: number, + value: string, +] + +type TrSetNodeAttributeDict = [ + type: 51, + id: number, + nameKey: number, + valueKey: number, +] + type TrResourceTiming = [ type: 53, timestamp: number, @@ -396,12 +400,6 @@ type TrAdoptedSSRemoveOwner = [ id: number, ] -type TrZustand = [ - type: 79, - mutation: string, - state: string, -] - type TrJSException = [ type: 78, name: string, @@ -410,22 +408,43 @@ type TrJSException = [ metadata: string, ] +type TrZustand = [ + type: 79, + mutation: string, + state: string, +] -export type TrackerMessage = TrBatchMetadata | TrPartitionedMessage | TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrJSExceptionDeprecated | TrRawCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrRedux | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrResourceTiming | TrConnectionInformation | TrSetPageVisibility | TrLoadFontFace | TrSetNodeFocus | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrZustand | TrJSException +type TrBatchMetadata = [ + type: 81, + version: number, + pageNo: number, + firstIndex: number, + timestamp: number, + location: string, +] + +type TrPartitionedMessage = [ + type: 82, + partNo: number, + partTotal: number, +] + + +export type TrackerMessage = TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrNetworkRequest | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrRedux | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrStringDict | TrSetNodeAttributeDict | TrResourceTiming | TrConnectionInformation | TrSetPageVisibility | TrLoadFontFace | TrSetNodeFocus | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrJSException | TrZustand | TrBatchMetadata | TrPartitionedMessage export default function translate(tMsg: TrackerMessage): RawMessage | null { switch(tMsg[0]) { case 0: { return { - tp: "timestamp", + tp: MType.Timestamp, timestamp: tMsg[1], } } case 4: { return { - tp: "set_page_location", + tp: MType.SetPageLocation, url: tMsg[1], referrer: tMsg[2], navigationStart: tMsg[3], @@ -434,7 +453,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 5: { return { - tp: "set_viewport_size", + tp: MType.SetViewportSize, width: tMsg[1], height: tMsg[2], } @@ -442,7 +461,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 6: { return { - tp: "set_viewport_scroll", + tp: MType.SetViewportScroll, x: tMsg[1], y: tMsg[2], } @@ -450,14 +469,14 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 7: { return { - tp: "create_document", + tp: MType.CreateDocument, } } case 8: { return { - tp: "create_element_node", + tp: MType.CreateElementNode, id: tMsg[1], parentID: tMsg[2], index: tMsg[3], @@ -468,7 +487,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 9: { return { - tp: "create_text_node", + tp: MType.CreateTextNode, id: tMsg[1], parentID: tMsg[2], index: tMsg[3], @@ -477,7 +496,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 10: { return { - tp: "move_node", + tp: MType.MoveNode, id: tMsg[1], parentID: tMsg[2], index: tMsg[3], @@ -486,14 +505,14 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 11: { return { - tp: "remove_node", + tp: MType.RemoveNode, id: tMsg[1], } } case 12: { return { - tp: "set_node_attribute", + tp: MType.SetNodeAttribute, id: tMsg[1], name: tMsg[2], value: tMsg[3], @@ -502,7 +521,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 13: { return { - tp: "remove_node_attribute", + tp: MType.RemoveNodeAttribute, id: tMsg[1], name: tMsg[2], } @@ -510,7 +529,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 14: { return { - tp: "set_node_data", + tp: MType.SetNodeData, id: tMsg[1], data: tMsg[2], } @@ -518,7 +537,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 16: { return { - tp: "set_node_scroll", + tp: MType.SetNodeScroll, id: tMsg[1], x: tMsg[2], y: tMsg[3], @@ -527,7 +546,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 18: { return { - tp: "set_input_value", + tp: MType.SetInputValue, id: tMsg[1], value: tMsg[2], mask: tMsg[3], @@ -536,7 +555,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 19: { return { - tp: "set_input_checked", + tp: MType.SetInputChecked, id: tMsg[1], checked: tMsg[2], } @@ -544,15 +563,29 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 20: { return { - tp: "mouse_move", + tp: MType.MouseMove, x: tMsg[1], y: tMsg[2], } } + case 21: { + return { + tp: MType.NetworkRequest, + type: tMsg[1], + method: tMsg[2], + url: tMsg[3], + request: tMsg[4], + response: tMsg[5], + status: tMsg[6], + timestamp: tMsg[7], + duration: tMsg[8], + } + } + case 22: { return { - tp: "console_log", + tp: MType.ConsoleLog, level: tMsg[1], value: tMsg[2], } @@ -560,7 +593,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 37: { return { - tp: "css_insert_rule", + tp: MType.CssInsertRule, id: tMsg[1], rule: tMsg[2], index: tMsg[3], @@ -569,7 +602,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 38: { return { - tp: "css_delete_rule", + tp: MType.CssDeleteRule, id: tMsg[1], index: tMsg[2], } @@ -577,7 +610,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 39: { return { - tp: "fetch", + tp: MType.Fetch, method: tMsg[1], url: tMsg[2], request: tMsg[3], @@ -590,7 +623,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 40: { return { - tp: "profiler", + tp: MType.Profiler, name: tMsg[1], duration: tMsg[2], args: tMsg[3], @@ -600,7 +633,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 41: { return { - tp: "o_table", + tp: MType.OTable, key: tMsg[1], value: tMsg[2], } @@ -608,7 +641,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 44: { return { - tp: "redux", + tp: MType.Redux, action: tMsg[1], state: tMsg[2], duration: tMsg[3], @@ -617,7 +650,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 45: { return { - tp: "vuex", + tp: MType.Vuex, mutation: tMsg[1], state: tMsg[2], } @@ -625,7 +658,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 46: { return { - tp: "mob_x", + tp: MType.MobX, type: tMsg[1], payload: tMsg[2], } @@ -633,7 +666,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 47: { return { - tp: "ng_rx", + tp: MType.NgRx, action: tMsg[1], state: tMsg[2], duration: tMsg[3], @@ -642,7 +675,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 48: { return { - tp: "graph_ql", + tp: MType.GraphQl, operationKind: tMsg[1], operationName: tMsg[2], variables: tMsg[3], @@ -652,7 +685,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 49: { return { - tp: "performance_track", + tp: MType.PerformanceTrack, frames: tMsg[1], ticks: tMsg[2], totalJSHeapSize: tMsg[3], @@ -660,9 +693,40 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { } } + case 50: { + return { + tp: MType.StringDict, + key: tMsg[1], + value: tMsg[2], + } + } + + case 51: { + return { + tp: MType.SetNodeAttributeDict, + id: tMsg[1], + nameKey: tMsg[2], + valueKey: tMsg[3], + } + } + + case 53: { + return { + tp: MType.ResourceTiming, + timestamp: tMsg[1], + duration: tMsg[2], + ttfb: tMsg[3], + headerSize: tMsg[4], + encodedBodySize: tMsg[5], + decodedBodySize: tMsg[6], + url: tMsg[7], + initiator: tMsg[8], + } + } + case 54: { return { - tp: "connection_information", + tp: MType.ConnectionInformation, downlink: tMsg[1], type: tMsg[2], } @@ -670,14 +734,14 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 55: { return { - tp: "set_page_visibility", + tp: MType.SetPageVisibility, hidden: tMsg[1], } } case 57: { return { - tp: "load_font_face", + tp: MType.LoadFontFace, parentID: tMsg[1], family: tMsg[2], source: tMsg[3], @@ -687,14 +751,14 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 58: { return { - tp: "set_node_focus", + tp: MType.SetNodeFocus, id: tMsg[1], } } case 59: { return { - tp: "long_task", + tp: MType.LongTask, timestamp: tMsg[1], duration: tMsg[2], context: tMsg[3], @@ -707,7 +771,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 60: { return { - tp: "set_node_attribute_url_based", + tp: MType.SetNodeAttributeURLBased, id: tMsg[1], name: tMsg[2], value: tMsg[3], @@ -717,7 +781,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 61: { return { - tp: "set_css_data_url_based", + tp: MType.SetCssDataURLBased, id: tMsg[1], data: tMsg[2], baseURL: tMsg[3], @@ -726,7 +790,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 67: { return { - tp: "css_insert_rule_url_based", + tp: MType.CssInsertRuleURLBased, id: tMsg[1], rule: tMsg[2], index: tMsg[3], @@ -736,7 +800,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 69: { return { - tp: "mouse_click", + tp: MType.MouseClick, id: tMsg[1], hesitationTime: tMsg[2], label: tMsg[3], @@ -746,7 +810,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 70: { return { - tp: "create_i_frame_document", + tp: MType.CreateIFrameDocument, frameID: tMsg[1], id: tMsg[2], } @@ -754,7 +818,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 71: { return { - tp: "adopted_ss_replace_url_based", + tp: MType.AdoptedSsReplaceURLBased, sheetID: tMsg[1], text: tMsg[2], baseURL: tMsg[3], @@ -763,7 +827,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 73: { return { - tp: "adopted_ss_insert_rule_url_based", + tp: MType.AdoptedSsInsertRuleURLBased, sheetID: tMsg[1], rule: tMsg[2], index: tMsg[3], @@ -773,7 +837,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 75: { return { - tp: "adopted_ss_delete_rule", + tp: MType.AdoptedSsDeleteRule, sheetID: tMsg[1], index: tMsg[2], } @@ -781,7 +845,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 76: { return { - tp: "adopted_ss_add_owner", + tp: MType.AdoptedSsAddOwner, sheetID: tMsg[1], id: tMsg[2], } @@ -789,7 +853,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 77: { return { - tp: "adopted_ss_remove_owner", + tp: MType.AdoptedSsRemoveOwner, sheetID: tMsg[1], id: tMsg[2], } @@ -797,7 +861,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { case 79: { return { - tp: "zustand", + tp: MType.Zustand, mutation: tMsg[1], state: tMsg[2], } diff --git a/frontend/app/player/web/messages/urlBasedResolver.ts b/frontend/app/player/web/messages/urlBasedResolver.ts new file mode 100644 index 000000000..53bf1ed81 --- /dev/null +++ b/frontend/app/player/web/messages/urlBasedResolver.ts @@ -0,0 +1,69 @@ +import type { + RawMessage, + RawSetNodeAttributeURLBased, + RawSetNodeAttribute, + RawSetCssDataURLBased, + RawSetCssData, + RawCssInsertRuleURLBased, + RawCssInsertRule, + RawAdoptedSsInsertRuleURLBased, + RawAdoptedSsInsertRule, + RawAdoptedSsReplaceURLBased, + RawAdoptedSsReplace, +} from './raw.gen' +import { MType } from './raw.gen' +import { resolveURL, resolveCSS } from './urlResolve' + +// type PickMessage<T extends MType> = Extract<RawMessage, { tp: T }>; +// type ResolversMap = { +// [Key in MType]: (event: PickMessage<Key>) => RawMessage +// } + +// TODO: commonURLBased logic for feilds +const resolvers = { + [MType.SetNodeAttributeURLBased]: (msg: RawSetNodeAttributeURLBased): RawSetNodeAttribute => + ({ + ...msg, + value: msg.name === 'src' || msg.name === 'href' + ? resolveURL(msg.baseURL, msg.value) + : (msg.name === 'style' + ? resolveCSS(msg.baseURL, msg.value) + : msg.value + ), + tp: MType.SetNodeAttribute, + }), + [MType.SetCssDataURLBased]: (msg: RawSetCssDataURLBased): RawSetCssData => + ({ + ...msg, + data: resolveCSS(msg.baseURL, msg.data), + tp: MType.SetCssData, + }), + [MType.CssInsertRuleURLBased]: (msg: RawCssInsertRuleURLBased): RawCssInsertRule => + ({ + ...msg, + rule: resolveCSS(msg.baseURL, msg.rule), + tp: MType.CssInsertRule, + }), + [MType.AdoptedSsInsertRuleURLBased]: (msg: RawAdoptedSsInsertRuleURLBased): RawAdoptedSsInsertRule => + ({ + ...msg, + rule: resolveCSS(msg.baseURL, msg.rule), + tp: MType.AdoptedSsInsertRule, + }), + [MType.AdoptedSsReplaceURLBased]: (msg: RawAdoptedSsReplaceURLBased): RawAdoptedSsReplace => + ({ + ...msg, + text: resolveCSS(msg.baseURL, msg.text), + tp: MType.AdoptedSsReplace, + }), +} as const + + +export default function resolve(msg: RawMessage): RawMessage { + // @ts-ignore --- any idea? + if (resolvers[msg.tp]) { + // @ts-ignore + return resolvers[msg.tp](msg) + } + return msg +} \ No newline at end of file diff --git a/frontend/app/player/MessageDistributor/messages/urlResolve.ts b/frontend/app/player/web/messages/urlResolve.ts similarity index 100% rename from frontend/app/player/MessageDistributor/messages/urlResolve.ts rename to frontend/app/player/web/messages/urlResolve.ts diff --git a/frontend/app/player/MessageDistributor/network/crypto.ts b/frontend/app/player/web/network/crypto.ts similarity index 100% rename from frontend/app/player/MessageDistributor/network/crypto.ts rename to frontend/app/player/web/network/crypto.ts diff --git a/frontend/app/player/web/network/loadFiles.ts b/frontend/app/player/web/network/loadFiles.ts new file mode 100644 index 000000000..8d8142716 --- /dev/null +++ b/frontend/app/player/web/network/loadFiles.ts @@ -0,0 +1,56 @@ +import APIClient from 'App/api_client'; + +const ALLOWED_404 = "No-file-and-this-is-ok" +const NO_BACKUP_FILE = "No-efs-file" +export const NO_URLS = 'No-urls-provided' + + +export async function loadFiles( + urls: string[], + onData: (data: Uint8Array) => void, +): Promise<any> { + if (!urls.length) { + throw NO_URLS + } + try { + for (let url of urls) { + const response = await window.fetch(url) + const data = await processAPIStreamResponse(response, url !== url[0]) + onData(data) + } + } catch(e) { + if (e === ALLOWED_404) { + return + } + throw e + } +} + +export async function requestEFSDom(sessionId: string) { + return await requestEFSMobFile(sessionId + "/dom.mob") +} + +export async function requestEFSDevtools(sessionId: string) { + return await requestEFSMobFile(sessionId + "/devtools.mob") +} + +async function requestEFSMobFile(filename: string) { + const api = new APIClient() + const res = await api.fetch('/unprocessed/' + filename) + if (res.status >= 400) { + throw NO_BACKUP_FILE + } + return await processAPIStreamResponse(res, false) +} + +const processAPIStreamResponse = (response: Response, skippable: boolean) => { + return new Promise<ArrayBuffer>((res, rej) => { + if (response.status === 404 && skippable) { + return rej(ALLOWED_404) + } + if (response.status >= 400) { + return rej(`Bad file status code ${response.status}. Url: ${response.url}`) + } + res(response.arrayBuffer()) + }).then(buffer => new Uint8Array(buffer)) +} diff --git a/frontend/app/player/web/storageSelectors.ts b/frontend/app/player/web/storageSelectors.ts new file mode 100644 index 000000000..1fa1ea32b --- /dev/null +++ b/frontend/app/player/web/storageSelectors.ts @@ -0,0 +1,43 @@ +import { State } from './Lists' + +export enum StorageType { + REDUX = "redux", + MOBX = "mobx", + VUEX = "vuex", + NGRX = "ngrx", + ZUSTAND = "zustand", + NONE = 0, +} + +export const STORAGE_TYPES = StorageType // TODO: update name everywhere + +export function selectStorageType(state: State): StorageType { + if (!state.reduxList) return StorageType.NONE + if (state.reduxList.length > 0) { + return StorageType.REDUX + } else if (state.vuexList.length > 0) { + return StorageType.VUEX + } else if (state.mobxList.length > 0) { + return StorageType.MOBX + } else if (state.ngrxList.length > 0) { + return StorageType.NGRX + } else if (state.zustandList.length > 0) { + return StorageType.ZUSTAND + } + return StorageType.NONE +} + +export function selectStorageList(state: State) { + const key = selectStorageType(state) + if (key !== StorageType.NONE) { + return state[`${key}List`] + } + return [] +} +export function selectStorageListNow(state: State) { + const key = selectStorageType(state) + if (key !== StorageType.NONE) { + return state[`${key}ListNow`] + } + return [] +} diff --git a/frontend/app/player/web/types/index.ts b/frontend/app/player/web/types/index.ts new file mode 100644 index 000000000..2f5f8e464 --- /dev/null +++ b/frontend/app/player/web/types/index.ts @@ -0,0 +1,2 @@ +export * from './log' +export * from './resource' \ No newline at end of file diff --git a/frontend/app/player/web/types/log.ts b/frontend/app/player/web/types/log.ts new file mode 100644 index 000000000..22a20d33c --- /dev/null +++ b/frontend/app/player/web/types/log.ts @@ -0,0 +1,23 @@ +export const enum LogLevel { + INFO = 'info', + LOG = 'log', + //ASSERT = 'assert', //? + WARN = 'warn', + ERROR = 'error', + EXCEPTION = 'exception', +} + +export interface ILog { + level: LogLevel + value: string + time: number + index?: number + errorId?: string +} + +export const Log = (log: ILog) => ({ + isRed: log.level === LogLevel.EXCEPTION || log.level === LogLevel.ERROR, + isYellow: log.level === LogLevel.WARN, + ...log +}) + diff --git a/frontend/app/player/web/types/resource.ts b/frontend/app/player/web/types/resource.ts new file mode 100644 index 000000000..c1b48ce9d --- /dev/null +++ b/frontend/app/player/web/types/resource.ts @@ -0,0 +1,114 @@ +import type { ResourceTiming, NetworkRequest, Fetch } from '../messages' + +export const enum ResourceType { + XHR = 'xhr', + FETCH = 'fetch', + SCRIPT = 'script', + CSS = 'css', + IMG = 'img', + MEDIA = 'media', + OTHER = 'other', +} + +function getURLExtention(url: string): string { + const pts = url.split("?")[0].split(".") + return pts[pts.length-1] || "" +} + +// maybe move this thing to the tracker +function getResourceType(initiator: string, url: string): ResourceType { + switch (initiator) { + case "xmlhttprequest": + case "fetch": + return ResourceType.FETCH + case "img": + return ResourceType.IMG + default: + switch (getURLExtention(url)) { + case "css": + return ResourceType.CSS + case "js": + return ResourceType.SCRIPT + case "png": + case "gif": + case "jpg": + case "jpeg": + case "svg": + return ResourceType.IMG + case "mp4": + case "mkv": + case "ogg": + case "webm": + case "avi": + case "mp3": + return ResourceType.MEDIA + default: + return ResourceType.OTHER + } + } +} + +function getResourceName(url: string) { + return url + .split('/') + .filter((s) => s !== '') + .pop(); +} + + +const YELLOW_BOUND = 10; +const RED_BOUND = 80; + + +interface IResource { + //index: number, + time: number, + type: ResourceType, + url: string, + status: string, + method: string, + duration: number, + success: boolean, + ttfb?: number, + request?: string, + response?: string, + headerSize?: number, + encodedBodySize?: number, + decodedBodySize?: number, + responseBodySize?: number, +} + + +export const Resource = (resource: IResource) => ({ + ...resource, + name: getResourceName(resource.url), + isRed: !resource.success, //|| resource.score >= RED_BOUND, + isYellow: false, // resource.score < RED_BOUND && resource.score >= YELLOW_BOUND, +}) + + +export function getResourceFromResourceTiming(msg: ResourceTiming, sessStart: number) { + const success = msg.duration > 0 // might be duration=0 when cached + const type = getResourceType(msg.initiator, msg.url) + return Resource({ + ...msg, + type, + method: type === ResourceType.FETCH ? ".." : "GET", // should be GET for all non-XHR/Fetch resources, right? + success, + status: success ? '2xx-3xx' : '4xx-5xx', + time: Math.max(0, msg.timestamp - sessStart) + }) +} + +export function getResourceFromNetworkRequest(msg: NetworkRequest | Fetch, sessStart: number) { + return Resource({ + ...msg, + // @ts-ignore + type: msg?.type === "xhr" ? ResourceType.XHR : ResourceType.FETCH, + success: msg.status < 400, + status: String(msg.status), + time: Math.max(0, msg.timestamp - sessStart), + }) +} + + diff --git a/frontend/app/routes.js b/frontend/app/routes.js index 90de53012..411b48ba5 100644 --- a/frontend/app/routes.js +++ b/frontend/app/routes.js @@ -84,10 +84,11 @@ export const onboarding = (tab = routerOBTabString) => `/onboarding/${ tab }`; export const sessions = params => queried('/sessions', params); export const assist = params => queried('/assist', params); - +export const recordings = params => queried("/recordings", params); +export const multiviewIndex = params => queried('/multiview', params); +export const multiview = (sessionsQuery = ':sessionsquery', hash) => hashed(`/multiview/${sessionsQuery}`, hash); export const session = (sessionId = ':sessionId', hash) => hashed(`/session/${ sessionId }`, hash); export const liveSession = (sessionId = ':sessionId', params, hash) => hashed(queried(`/assist/${ sessionId }`, params), hash); -// export const liveSession = (sessionId = ':sessionId', hash) => hashed(`/live/session/${ sessionId }`, hash); export const errors = params => queried('/errors', params); export const error = (id = ':errorId', hash) => hashed(`/errors/${ id }`, hash); @@ -123,7 +124,10 @@ const REQUIRED_SITE_ID_ROUTES = [ session(''), sessions(), assist(), - + recordings(), + multiview(), + multiviewIndex(), + metrics(), metricDetails(''), metricDetailsSub(''), @@ -172,6 +176,7 @@ const SITE_CHANGE_AVALIABLE_ROUTES = [ sessions(), funnels(), assist(), + recordings(), dashboard(), dashboardSelected(), metrics(), diff --git a/frontend/app/services/AlertsService.ts b/frontend/app/services/AlertsService.ts new file mode 100644 index 000000000..7ba1702e9 --- /dev/null +++ b/frontend/app/services/AlertsService.ts @@ -0,0 +1,49 @@ +import APIClient from 'App/api_client'; +import Alert, { IAlert } from "Types/alert"; + +export default class AlertsService { + private client: APIClient; + + constructor(client?: APIClient) { + this.client = client ? client : new APIClient(); + } + + initClient(client?: APIClient) { + this.client = client || new APIClient(); + } + + save(instance: Alert): Promise<IAlert> { + return this.client.post(instance['alertId'] ? `/alerts/${instance['alertId']}` : '/alerts', instance.toData()) + .then(response => response.json()) + .then(response => response.data || {}) + .catch(Promise.reject) + } + + fetchTriggerOptions(): Promise<{ name: string, value: string | number }[]> { + return this.client.get('/alerts/triggers') + .then(r => r.json()) + .then(j => j.data || []) + .catch(Promise.reject) + } + + fetchList(): Promise<IAlert[]> { + return this.client.get('/alerts') + .then(r => r.json()) + .then(j => j.data || []) + .catch(Promise.reject) + } + + fetch(id: string): Promise<IAlert> { + return this.client.get(`/alerts/${id}`) + .then(r => r.json()) + .then(j => j.data || {}) + .catch(Promise.reject) + } + + remove(id: string): Promise<IAlert> { + return this.client.delete(`/alerts/${id}`) + .then(r => r.json()) + .then(j => j.data || {}) + .catch(Promise.reject) + } +} \ No newline at end of file diff --git a/frontend/app/services/BaseService.ts b/frontend/app/services/BaseService.ts index 2af1897f8..5cca28d4e 100644 --- a/frontend/app/services/BaseService.ts +++ b/frontend/app/services/BaseService.ts @@ -1,4 +1,5 @@ import APIClient from 'App/api_client'; + export default class BaseService { client: APIClient; diff --git a/frontend/app/services/ConfigService.ts b/frontend/app/services/ConfigService.ts new file mode 100644 index 000000000..6afda4858 --- /dev/null +++ b/frontend/app/services/ConfigService.ts @@ -0,0 +1,17 @@ +import BaseService from './BaseService'; + +export interface WeeklyReport { + weeklyReport: boolean +} + +export default class ConfigService extends BaseService { + async fetchWeeklyReport(): Promise<WeeklyReport> { + return this.client.get('/config/weekly_report') + .then(r => r.json()).then(j => j.data) + } + + async editWeeklyReport(config: WeeklyReport): Promise<WeeklyReport> { + return this.client.post('/config/weekly_report', config) + .then(r => r.json()).then(j => j.data) + } +} \ No newline at end of file diff --git a/frontend/app/services/ErrorService.ts b/frontend/app/services/ErrorService.ts index 5b3520b5c..9e9402b45 100644 --- a/frontend/app/services/ErrorService.ts +++ b/frontend/app/services/ErrorService.ts @@ -1,16 +1,17 @@ import BaseService from './BaseService'; -import { fetchErrorCheck } from 'App/utils' export default class ErrorService extends BaseService { all(params: any = {}): Promise<any[]> { return this.client.post('/errors/search', params) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || []); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || []) + .catch(e => Promise.reject(e)) } one(id: string): Promise<any> { return this.client.get(`/errors/${id}`) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || {}); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } } \ No newline at end of file diff --git a/frontend/app/services/MetricService.ts b/frontend/app/services/MetricService.ts index a5734aff0..5b97ec4ec 100644 --- a/frontend/app/services/MetricService.ts +++ b/frontend/app/services/MetricService.ts @@ -1,6 +1,6 @@ import Widget from "App/mstore/types/widget"; import APIClient from 'App/api_client'; -import { fetchErrorCheck } from "App/utils"; +import { CLICKMAP } from "App/constants/card"; export default class MetricService { private client: APIClient; @@ -18,7 +18,7 @@ export default class MetricService { * @returns {Promise<any>} */ getMetrics(): Promise<any> { - return this.client.get('/metrics') + return this.client.get('/cards') .then((response: { json: () => any; }) => response.json()) .then((response: { data: any; }) => response.data || []); } @@ -29,24 +29,25 @@ export default class MetricService { * @returns {Promise<any>} */ getMetric(metricId: string): Promise<any> { - return this.client.get('/metrics/' + metricId) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || {}); + return this.client.get('/cards/' + metricId) + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } /** * Save a metric. - * @param metric + * @param metric * @returns */ - saveMetric(metric: Widget, dashboardId?: string): Promise<any> { + saveMetric(metric: Widget): Promise<any> { const data = metric.toJson() const isCreating = !data[Widget.ID_KEY]; - const method = isCreating ? 'post' : 'put'; - const url = isCreating ? '/metrics' : '/metrics/' + data[Widget.ID_KEY]; - return this.client[method](url, data) - .then(fetchErrorCheck) + const url = isCreating ? '/cards' : '/cards/' + data[Widget.ID_KEY]; + return this.client.post(url, data) + .then(r => r.json()) .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } /** @@ -55,7 +56,7 @@ export default class MetricService { * @returns {Promise<any>} */ deleteMetric(metricId: string): Promise<any> { - return this.client.delete('/metrics/' + metricId) + return this.client.delete('/cards/' + metricId) .then((response: { json: () => any; }) => response.json()) .then((response: { data: any; }) => response.data); } @@ -66,37 +67,46 @@ export default class MetricService { * @returns {Promise<any>} */ getTemplates(): Promise<any> { - return this.client.get('/metrics/templates') + return this.client.get('/cards/templates') .then((response: { json: () => any; }) => response.json()) .then((response: { data: any; }) => response.data || []); } getMetricChartData(metric: Widget, data: any, isWidget: boolean = false): Promise<any> { - const path = isWidget ? `/metrics/${metric.metricId}/chart` : `/metrics/try`; + if ( + metric.metricType === CLICKMAP + && document.location.pathname.split('/').pop() === 'metrics' + && (document.location.pathname.indexOf('dashboard') !== -1 && document.location.pathname.indexOf('metric') === -1) + ) { + return Promise.resolve({}) + } + const path = isWidget ? `/cards/${metric.metricId}/chart` : `/cards/try`; return this.client.post(path, data) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || {}); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } /** * Fetch sessions from the server. - * @param filter + * @param metricId {String} + * @param filter * @returns */ fetchSessions(metricId: string, filter: any): Promise<any> { - return this.client.post(metricId ? `/metrics/${metricId}/sessions` : '/metrics/try/sessions', filter) + return this.client.post(metricId ? `/cards/${metricId}/sessions` : '/cards/try/sessions', filter) .then((response: { json: () => any; }) => response.json()) .then((response: { data: any; }) => response.data || []); } fetchIssues(filter: string): Promise<any> { - return this.client.post(`/metrics/try/issues`, filter) + return this.client.post(`/cards/try/issues`, filter) .then((response: { json: () => any; }) => response.json()) .then((response: { data: any; }) => response.data || {}); } fetchIssue(metricId: string, issueId: string, params: any): Promise<any> { - return this.client.post(`/custom_metrics/${metricId}/issues/${issueId}/sessions`, params) + return this.client.post(`/cards/${metricId}/issues/${issueId}/sessions`, params) .then((response: { json: () => any; }) => response.json()) .then((response: { data: any; }) => response.data || {}); } diff --git a/frontend/app/services/NotesService.ts b/frontend/app/services/NotesService.ts index dc4ed28b3..d66316129 100644 --- a/frontend/app/services/NotesService.ts +++ b/frontend/app/services/NotesService.ts @@ -32,6 +32,7 @@ export interface Note { tag: iTag timestamp: number userId: number + userName: string } export interface NotesFilter { @@ -45,78 +46,61 @@ export interface NotesFilter { } export default class NotesService { - private client: APIClient; + private client: APIClient; - constructor(client?: APIClient) { - this.client = client ? client : new APIClient(); - } + constructor(client?: APIClient) { + this.client = client ? client : new APIClient(); + } - initClient(client?: APIClient) { - this.client = client || new APIClient(); - } + initClient(client?: APIClient) { + this.client = client || new APIClient(); + } - fetchNotes(filter: NotesFilter): Promise<Note[]> { - return this.client.post('/notes', filter).then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error getting notes: ' + r.status) - } - }) - } + fetchNotes(filter: NotesFilter): Promise<Note[]> { + return this.client.post('/notes', filter).then(r => { + return r.json().then(r => r.data) + }) + } - getNotesBySessionId(sessionID: string): Promise<Note[]> { - return this.client.get(`/sessions/${sessionID}/notes`) + getNotesBySessionId(sessionID: string): Promise<Note[]> { + return this.client.get(`/sessions/${sessionID}/notes`) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error getting notes for ' +sessionID + ' cuz: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } - addNote(sessionID: string, note: WriteNote): Promise<Note> { - return this.client.post(`/sessions/${sessionID}/notes`, note) + addNote(sessionID: string, note: WriteNote): Promise<Note> { + return this.client.post(`/sessions/${sessionID}/notes`, note) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error adding note: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } - updateNote(noteID: string, note: WriteNote): Promise<Note> { - return this.client.post(`/notes/${noteID}`, note) + updateNote(noteID: string, note: WriteNote): Promise<Note> { + return this.client.post(`/notes/${noteID}`, note) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error updating note: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } - deleteNote(noteID: number) { - return this.client.delete(`/notes/${noteID}`) + deleteNote(noteID: number) { + return this.client.delete(`/notes/${noteID}`) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error deleting note: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } - sendSlackNotification(noteId: string, webhook: string) { - return this.client.get(`/notes/${noteId}/slack/${webhook}`) + sendSlackNotification(noteId: string, webhook: string) { + return this.client.get(`/notes/${noteId}/slack/${webhook}`) .then(r => { - if (r.ok) { - return r.json().then(r => r.data) - } else { - throw new Error('Error sending slack notif: ' + r.status) - } + return r.json().then(r => r.data) }) - } + } + + sendMsTeamsNotification(noteId: string, webhook: string) { + return this.client.get(`/notes/${noteId}/msteams/${webhook}`) + .then(r => { + return r.json().then(r => r.data) + }) + } } diff --git a/frontend/app/services/RecordingsService.ts b/frontend/app/services/RecordingsService.ts new file mode 100644 index 000000000..6dd3845c5 --- /dev/null +++ b/frontend/app/services/RecordingsService.ts @@ -0,0 +1,83 @@ +import APIClient from 'App/api_client'; + +interface RecordingData { + name: string; + duration: number; + sessionId: string; +} + +interface FetchFilter { + page: number; + limit: number; + order: 'asc' | 'desc'; + search: string; +} + +export interface IRecord { + createdAt: number; + createdBy: string; + duration: number; + name: string; + recordId: number; + sessionId: number; + userId: number; + URL?: string; +} + +export default class RecordingsService { + private client: APIClient; + + constructor(client?: APIClient) { + this.client = client ? client : new APIClient(); + } + + initClient(client?: APIClient) { + this.client = client || new APIClient(); + } + + reserveUrl(siteId: string, recordingData: RecordingData): Promise<{ URL: string; key: string }> { + return this.client.put(`/${siteId}/assist/save`, recordingData).then((r) => { + return r.json().then((j) => j.data); + }); + } + + saveFile(url: string, file: Blob) { + return fetch(url, { + method: 'PUT', + headers: { 'Content-Type': 'video/webm' }, + body: file, + }).then((r) => { + return true; + }); + } + + confirmFile(siteId: string, recordingData: RecordingData, key: string): Promise<any> { + return this.client.put(`/${siteId}/assist/save/done`, { ...recordingData, key }).then((r) => { + return r.json().then((j) => j.data); + }); + } + + fetchRecordings(filters: FetchFilter): Promise<IRecord[]> { + return this.client.post(`/assist/records`, filters).then((r) => { + return r.json().then((j) => j.data); + }); + } + + fetchRecording(id: number): Promise<IRecord> { + return this.client.get(`/assist/records/${id}`).then((r) => { + return r.json().then((j) => j.data); + }); + } + + updateRecordingName(id: number, name: string): Promise<IRecord> { + return this.client.post(`/assist/records/${id}`, { name }).then((r) => { + return r.json().then((j) => j.data); + }); + } + + deleteRecording(id: number): Promise<any> { + return this.client.delete(`/assist/records/${id}`).then((r) => { + return r.json().then((j) => j.data); + }); + } +} diff --git a/frontend/app/services/SessionService.ts b/frontend/app/services/SessionService.ts index 01964e41a..6537a2dbb 100644 --- a/frontend/app/services/SessionService.ts +++ b/frontend/app/services/SessionService.ts @@ -1,32 +1,81 @@ import APIClient from 'App/api_client'; -import { fetchErrorCheck } from 'App/utils'; +import { ISession } from 'Types/session/session'; +import { IErrorStack } from 'Types/session/errorStack'; +import { clean as cleanParams } from 'App/api_client'; export default class SettingsService { - private client: APIClient; + private client: APIClient; - constructor(client?: APIClient) { - this.client = client ? client : new APIClient(); - } + constructor(client?: APIClient) { + this.client = client ? client : new APIClient(); + } - initClient(client?: APIClient) { - this.client = client || new APIClient(); - } + initClient(client?: APIClient) { + this.client = client || new APIClient(); + } - saveCaptureRate(data: any) { - return this.client.post('/sample_rate', data); - } + saveCaptureRate(data: any) { + return this.client.post('/sample_rate', data); + } - fetchCaptureRate() { - return this.client - .get('/sample_rate') - .then((response) => response.json()) - .then((response) => response.data || 0); - } + fetchCaptureRate() { + return this.client + .get('/sample_rate') + .then((response) => response.json()) + .then((response) => response.data || 0); + } - getSessions(filter: any) { - return this.client - .post('/sessions/search', filter) - .then(fetchErrorCheck) - .then((response) => response.data || []); - } + getSessions(filter: any): Promise<{ sessions: ISession[], total: number }> { + return this.client + .post('/sessions/search', filter) + .then(r => r.json()) + .then((response) => response.data || []) + .catch(e => Promise.reject(e)) + } + + getSessionInfo(sessionId: string, isLive?: boolean): Promise<ISession> { + return this.client + .get(isLive ? `/assist/sessions/${sessionId}` : `/sessions/${sessionId}`) + .then((r) => r.json()) + .then((j) => j.data || {}) + .catch(console.error); + } + + getLiveSessions(filter: any): Promise<{ sessions: ISession[] }> { + return this.client + .post('/assist/sessions', cleanParams(filter)) + .then(r => r.json()) + .then((response) => response.data || []) + .catch(e => Promise.reject(e)) + } + + getErrorStack(sessionId: string, errorId: string): Promise<{ trace: IErrorStack[] }> { + return this.client + .get(`/sessions/${sessionId}/errors/${errorId}/sourcemaps`) + .then(r => r.json()) + .then(j => j.data || {}) + .catch(e => Promise.reject(e)) + } + + getAutoplayList(params = {}): Promise<{ sessionId: string }[]> { + return this.client + .post('/sessions/search/ids', cleanParams(params)) + .then(r => r.json()) + .then(j => j.data || []) + .catch(e => Promise.reject(e)) + } + + toggleFavorite(sessionId: string): Promise<any> { + return this.client + .get(`/sessions/${sessionId}/favorite`) + .catch(Promise.reject) + } + + getClickMap(params = {}): Promise<any[]> { + return this.client + .post('/heatmaps/url', params) + .then(r => r.json()) + .then(j => j.data || []) + .catch(Promise.reject) + } } diff --git a/frontend/app/services/UserService.ts b/frontend/app/services/UserService.ts index 7a515d910..705040eb2 100644 --- a/frontend/app/services/UserService.ts +++ b/frontend/app/services/UserService.ts @@ -1,6 +1,5 @@ import APIClient from 'App/api_client'; import { IUser } from 'App/mstore/types/user' -import { fetchErrorCheck } from 'App/utils' export default class UserService { private client: APIClient; @@ -29,12 +28,14 @@ export default class UserService { const data = user.toSave(); if (user.userId) { return this.client.put('/client/members/' + user.userId, data) - .then(fetchErrorCheck) + .then(r => r.json()) .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } else { return this.client.post('/client/members', data) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || {}); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } } @@ -46,8 +47,9 @@ export default class UserService { delete(userId: string) { return this.client.delete('/client/members/' + userId) - .then(fetchErrorCheck) - .then((response: { data: any; }) => response.data || {}); + .then(r => r.json()) + .then((response: { data: any; }) => response.data || {}) + .catch(e => Promise.reject(e)) } getRoles() { diff --git a/frontend/app/services/WebhookService.ts b/frontend/app/services/WebhookService.ts new file mode 100644 index 000000000..7b1073867 --- /dev/null +++ b/frontend/app/services/WebhookService.ts @@ -0,0 +1,22 @@ +import BaseService from './BaseService'; +import Webhook, { IWebhook } from "Types/webhook"; + +export default class WebhookService extends BaseService { + fetchList(): Promise<IWebhook[]> { + return this.client.get('/webhooks') + .then(r => r.json()) + .then(j => j.data || []) + } + + saveWebhook(inst: Webhook) { + return this.client.put('/webhooks', inst) + .then(r => r.json()) + .then(j => j.data || {}) + } + + removeWebhook(id: Webhook["webhookId"]) { + return this.client.delete('/webhooks/' + id) + .then(r => r.json()) + .then(j => j.data || {}) + } +} \ No newline at end of file diff --git a/frontend/app/services/index.ts b/frontend/app/services/index.ts index 6de2d300b..816113e68 100644 --- a/frontend/app/services/index.ts +++ b/frontend/app/services/index.ts @@ -6,6 +6,10 @@ import UserService from "./UserService"; import AuditService from './AuditService'; import ErrorService from "./ErrorService"; import NotesService from "./NotesService"; +import RecordingsService from "./RecordingsService"; +import ConfigService from './ConfigService' +import AlertsService from './AlertsService' +import WebhookService from './WebhookService' export const dashboardService = new DashboardService(); export const metricService = new MetricService(); @@ -15,3 +19,22 @@ export const funnelService = new FunnelService(); export const auditService = new AuditService(); export const errorService = new ErrorService(); export const notesService = new NotesService(); +export const recordingsService = new RecordingsService(); +export const configService = new ConfigService(); +export const alertsService = new AlertsService(); +export const webhookService = new WebhookService(); + +export const services = [ + dashboardService, + metricService, + sessionService, + userService, + funnelService, + auditService, + errorService, + notesService, + recordingsService, + configService, + alertsService, + webhookService, +] \ No newline at end of file diff --git a/frontend/app/store.js b/frontend/app/store.js index dd1434a0c..54b429ada 100644 --- a/frontend/app/store.js +++ b/frontend/app/store.js @@ -4,26 +4,35 @@ import { Map } from 'immutable'; import indexReducer from './duck'; import apiMiddleware from './api_middleware'; import LocalStorage from './local_storage'; +import { initialState as initUserState, UPDATE_JWT } from './duck/user' + +// TODO @remove after few days +localStorage.removeItem('jwt') const storage = new LocalStorage({ - jwt: String, + user: Object, }); const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ && window.env.NODE_ENV === "development" ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose; const storageState = storage.state(); -const initialState = Map({ - jwt: storageState.jwt, - // TODO: store user -}); +const initialState = Map({ user: initUserState.update('jwt', () => storageState.user?.jwt || null) }); const store = createStore(indexReducer, initialState, composeEnhancers(applyMiddleware(thunk, apiMiddleware))); store.subscribe(() => { const state = store.getState(); + storage.sync({ - jwt: state.get('jwt') + user: state.get('user') }); }); +window.getJWT = () => { + console.log(JSON.stringify(storage.state().user?.jwt || 'not logged in')); +} +window.setJWT = (jwt) => { + store.dispatch({ type: UPDATE_JWT, data: jwt }) +} + export default store; diff --git a/frontend/app/styles/colors-autogen.css b/frontend/app/styles/colors-autogen.css index 1af69f93d..1d1bd5ab1 100644 --- a/frontend/app/styles/colors-autogen.css +++ b/frontend/app/styles/colors-autogen.css @@ -37,6 +37,42 @@ .fill-borderColor { fill: $borderColor } .fill-transparent { fill: $transparent } .fill-figmaColors { fill: $figmaColors } +.hover-fill-main:hover svg { fill: $main } +.hover-fill-gray-light-shade:hover svg { fill: $gray-light-shade } +.hover-fill-gray-lightest:hover svg { fill: $gray-lightest } +.hover-fill-gray-light:hover svg { fill: $gray-light } +.hover-fill-gray-bg:hover svg { fill: $gray-bg } +.hover-fill-gray-medium:hover svg { fill: $gray-medium } +.hover-fill-gray-dark:hover svg { fill: $gray-dark } +.hover-fill-gray-darkest:hover svg { fill: $gray-darkest } +.hover-fill-teal:hover svg { fill: $teal } +.hover-fill-teal-dark:hover svg { fill: $teal-dark } +.hover-fill-teal-light:hover svg { fill: $teal-light } +.hover-fill-tealx:hover svg { fill: $tealx } +.hover-fill-tealx-light:hover svg { fill: $tealx-light } +.hover-fill-tealx-light-border:hover svg { fill: $tealx-light-border } +.hover-fill-tealx-lightest:hover svg { fill: $tealx-lightest } +.hover-fill-orange:hover svg { fill: $orange } +.hover-fill-yellow:hover svg { fill: $yellow } +.hover-fill-yellow2:hover svg { fill: $yellow2 } +.hover-fill-orange-dark:hover svg { fill: $orange-dark } +.hover-fill-green:hover svg { fill: $green } +.hover-fill-green2:hover svg { fill: $green2 } +.hover-fill-green-dark:hover svg { fill: $green-dark } +.hover-fill-red:hover svg { fill: $red } +.hover-fill-red2:hover svg { fill: $red2 } +.hover-fill-red-lightest:hover svg { fill: $red-lightest } +.hover-fill-blue:hover svg { fill: $blue } +.hover-fill-blue2:hover svg { fill: $blue2 } +.hover-fill-active-blue:hover svg { fill: $active-blue } +.hover-fill-bg-blue:hover svg { fill: $bg-blue } +.hover-fill-active-blue-border:hover svg { fill: $active-blue-border } +.hover-fill-pink:hover svg { fill: $pink } +.hover-fill-light-blue-bg:hover svg { fill: $light-blue-bg } +.hover-fill-white:hover svg { fill: $white } +.hover-fill-borderColor:hover svg { fill: $borderColor } +.hover-fill-transparent:hover svg { fill: $transparent } +.hover-fill-figmaColors:hover svg { fill: $figmaColors } /* color */ .color-main { color: $main } diff --git a/frontend/app/styles/general.css b/frontend/app/styles/general.css index a21cfe239..e86178033 100644 --- a/frontend/app/styles/general.css +++ b/frontend/app/styles/general.css @@ -359,4 +359,14 @@ p { .dev-row { transition: all 0.5s; +} + +.hover-color-teal:hover { + color: $teal !important; + & div { + color: $teal !important; + } + & svg { + fill: $teal !important; + } } \ No newline at end of file diff --git a/frontend/app/styles/import.css b/frontend/app/styles/import.css index 8a75b6eab..88dbb8a57 100644 --- a/frontend/app/styles/import.css +++ b/frontend/app/styles/import.css @@ -4,8 +4,6 @@ @import 'rc-time-picker/assets/index.css'; @import 'highlight.js/styles/github-gist.css'; @import 'react-tippy/dist/tippy.css'; -@import 'react-date-range/dist/styles.css'; -@import 'react-date-range/dist/theme/default.css'; @import 'react-daterange-picker.css'; @import 'rc-time-picker.css'; diff --git a/frontend/app/svg/ca-no-alerts.svg b/frontend/app/svg/ca-no-alerts.svg index 998ba4e05..cfe825f14 100644 --- a/frontend/app/svg/ca-no-alerts.svg +++ b/frontend/app/svg/ca-no-alerts.svg @@ -1,4 +1,42 @@ -<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg"> -<rect width="100" height="100" rx="13.1579" fill="#3EAAAF" fill-opacity="0.08"/> -<path d="M39.7195 69.75H75.375C74.2911 69.0286 73.3559 67.7454 72.5475 66.125C69.285 59.6 68.125 47.565 68.125 44.375C68.125 43.418 68.0525 42.4791 67.9075 41.562L64.5072 44.9623C64.5797 47.5288 65.0764 52.7705 66.1639 57.9398C66.7475 60.7201 67.5305 63.6165 68.5672 66.125H43.3445L39.7195 69.75ZM59.9506 33.8263C57.9857 31.9692 55.5391 30.7008 52.8891 30.165L50 29.5741L47.1109 30.1578C43.8331 30.8256 40.8869 32.6057 38.771 35.1966C36.6551 37.7874 35.4995 41.0299 35.5 44.375C35.5 46.6515 35.0143 52.3391 33.8361 57.9398C33.6549 58.8025 33.4555 59.6761 33.2344 60.5425L27.2423 66.5346C27.3148 66.4005 27.3873 66.2664 27.4561 66.125C30.7114 59.6 31.875 47.565 31.875 44.375C31.875 35.6025 38.11 28.28 46.3931 26.6089C46.3425 26.1049 46.3981 25.5958 46.5564 25.1146C46.7146 24.6333 46.9719 24.1906 47.3118 23.815C47.6516 23.4393 48.0664 23.139 48.5295 22.9335C48.9925 22.728 49.4934 22.6219 50 22.6219C50.5066 22.6219 51.0075 22.728 51.4705 22.9335C51.9336 23.139 52.3484 23.4393 52.6882 23.815C53.0281 24.1906 53.2854 24.6333 53.4436 25.1146C53.6019 25.5958 53.6575 26.1049 53.6069 26.6089C57.0216 27.2976 60.0884 28.947 62.5135 31.2634L59.9506 33.8263V33.8263ZM57.25 73.375C57.25 75.2978 56.4862 77.1419 55.1265 78.5015C53.7669 79.8612 51.9228 80.625 50 80.625C48.0772 80.625 46.2331 79.8612 44.8735 78.5015C43.5138 77.1419 42.75 75.2978 42.75 73.375H57.25ZM23.2656 75.6406C22.9051 76.0012 22.7026 76.4902 22.7026 77C22.7026 77.5099 22.9051 77.9989 23.2656 78.3594C23.6262 78.7199 24.1151 78.9225 24.625 78.9225C25.1349 78.9225 25.6238 78.7199 25.9844 78.3594L79.4531 24.8906C79.6316 24.7121 79.7732 24.5002 79.8699 24.267C79.9665 24.0337 80.0162 23.7837 80.0162 23.5313C80.0162 23.2788 79.9665 23.0288 79.8699 22.7956C79.7732 22.5623 79.6316 22.3504 79.4531 22.1719C79.2746 21.9934 79.0627 21.8518 78.8294 21.7552C78.5962 21.6586 78.3462 21.6088 78.0937 21.6088C77.8413 21.6088 77.5913 21.6586 77.3581 21.7552C77.1248 21.8518 76.9129 21.9934 76.7344 22.1719L23.2656 75.6406Z" fill="#3EAAAF" fill-opacity="0.5"/> +<svg width="210" height="200" viewBox="0 0 210 200" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_9_195)"> +<rect width="210" height="200" fill="white"/> +<path d="M27.8174 199.451C30.2217 198.986 32.2674 198.573 35.4878 198.189C42.3668 197.37 50.5828 196.869 58.9692 196.478C77.7443 195.603 98.1449 195.502 117.512 195.964C130.215 196.267 142.14 196.862 154.008 197.575C159.59 197.91 166.176 198.384 174.18 198.876" stroke="black" stroke-linecap="round"/> +<circle cx="101.718" cy="91.953" r="84.3749" fill="#FDF9F3"/> +<path d="M136.53 66.3708C137.02 66.7308 137.61 67.0008 137.98 67.4608C146.41 77.9508 149.66 90.1408 149.65 103.371C149.65 104.841 149.29 106.301 149.1 107.771C149 108.561 148.92 109.361 148.76 110.841C157.66 105.541 166.12 100.711 175.44 97.7108C175.44 96.3008 175.3 95.1008 175.49 93.9508C175.59 93.3208 176.16 92.4708 176.69 92.3108C177.14 92.1808 177.97 92.7808 178.4 93.2608C178.94 93.8708 179.15 94.7508 179.66 95.4008C180.13 96.0008 180.74 96.7508 181.41 96.8908C185.91 97.8808 186.95 101.381 187.46 105.171C187.68 106.791 187.41 108.491 187.29 110.151C187.16 111.971 186.5 113.601 185.01 114.731C183.8 115.651 183.45 116.711 184.05 118.201C185.41 118.751 186.9 119.221 188.26 119.931C190.81 121.271 192.41 123.531 193.79 125.991C195.03 128.211 196.47 130.241 199.5 130.761C197.47 133.621 194.74 135.241 192.06 136.931C187.49 139.801 182.36 140.941 177.12 141.691C174.74 142.031 173.58 141.681 171.63 140.231C173.99 137.521 174.49 134.201 174.85 130.761C175.23 127.151 175.92 123.591 179.15 121.021C178.93 120.181 178.71 119.341 178.3 117.751C177.07 120.901 175.05 122.121 172.62 123.271C165.03 126.851 157.63 130.821 149.99 134.721C149.99 135.651 149.99 136.551 149.99 137.461C149.99 146.081 149.27 154.621 147.7 163.101C146.95 167.171 145.22 170.591 142.08 173.321C140.83 174.411 139.67 175.621 138.53 176.821C136.42 179.041 133.63 180.081 130.94 181.281C129.26 182.031 127.51 182.601 125.62 183.321C125.56 184.291 125.49 185.271 125.43 186.171C128.23 186.851 130.95 187.291 133.53 188.171C137.29 189.461 140.15 191.471 140.76 197.041C139.63 197.371 138.45 197.831 137.23 198.051C133.5 198.711 129.77 199.321 126.02 199.871C121.74 200.501 117.55 199.701 113.37 198.871C112.28 198.651 111.13 198.661 110.1 198.311C109.02 197.941 108.05 197.261 107.03 196.721C107.39 196.741 107.76 196.721 108.11 196.801C113.69 198.041 119.27 199.261 125.05 198.861C127.35 198.701 129.66 198.581 131.94 198.241C134.54 197.851 137.11 197.231 139.72 196.701C139.15 193.271 137.57 190.951 134.73 189.791C132.36 188.821 129.85 188.131 127.35 187.551C126.46 187.341 125.3 187.641 124.43 188.061C119.31 190.561 114.02 190.891 108.55 189.571C108.04 189.451 107.5 189.391 106.74 189.261C106.74 190.521 106.7 191.601 106.75 192.681C106.82 194.051 106.97 195.421 107.09 196.791C106.77 196.511 106.22 196.281 106.16 195.951C105.7 193.421 104.97 190.901 106.04 188.321C106.34 187.611 106.31 186.761 106.46 185.781H94.0996C93.3996 187.721 93.7496 189.471 94.0596 191.301C95.2496 198.351 94.6696 198.611 88.1796 200.101C83.1496 201.251 78.0696 200.451 73.0396 199.811C70.4996 199.491 67.9796 198.961 65.4596 198.471C64.4696 198.281 63.4696 198.051 62.5196 197.701C60.2096 196.861 59.6896 195.511 60.6996 193.191C62.8396 188.271 67.4996 185.551 72.9796 186.041C73.5296 186.091 74.0696 186.211 74.5796 186.301C75.5296 184.391 75.5696 184.291 73.9296 183.651C70.0196 182.131 66.4096 180.071 63.1496 177.481C61.2196 175.941 59.5696 174.051 57.7796 172.331C57.1996 171.771 56.5696 171.261 55.9596 170.731C55.4896 169.101 55.0296 167.471 54.6096 166.011C48.9796 164.911 44.1396 162.881 39.5396 159.841C39.6096 166.911 44.2496 171.531 50.8596 171.351C52.5896 171.301 54.2996 170.901 56.0196 170.651C53.7196 173.101 50.7596 172.621 48.0396 172.081C42.0796 170.911 38.9996 165.921 38.6096 161.751C38.4796 160.351 38.4796 158.871 37.3696 157.611C36.8796 157.051 36.9296 155.821 37.0496 154.941C37.7596 149.731 38.3196 144.481 39.3796 139.341C41.0696 131.131 44.1596 123.401 48.6096 116.301C50.3696 113.501 51.9596 110.951 51.3096 107.421C50.9096 105.271 51.0296 103.011 51.0496 100.801C51.1296 90.4608 53.0196 80.6708 59.0396 71.8908C63.3196 65.6408 67.9196 59.8708 74.3796 55.7308C77.8496 53.5008 81.5396 51.9208 85.5696 51.0908C86.7996 50.8408 87.9796 50.4008 89.4596 49.9708C88.8596 49.1508 88.5496 48.4008 87.9996 48.0308C84.3296 45.5708 80.7696 42.8508 75.9596 43.0808C74.5896 43.1408 73.2096 42.7508 71.8296 42.6808C67.0896 42.4508 66.5096 37.4208 68.3796 34.6008C71.5396 29.8408 77.7796 28.8408 82.1396 32.6608C83.9196 34.2208 85.3296 36.2308 86.8096 38.1208C88.7996 40.6608 90.6796 43.2808 92.6096 45.8708C93.0796 47.0608 92.7896 48.5408 94.1896 49.9408C94.1796 46.2808 96.5496 45.0808 98.9796 43.8908C99.7996 43.4908 100.61 43.0108 101.28 42.4008C101.87 41.8608 102.38 41.1508 102.69 40.4208C103.24 39.1308 102.71 38.2608 101.3 38.1108C100.59 38.0308 99.7896 38.0608 99.1396 38.3308C97.1396 39.1408 95.4596 40.3508 94.4996 42.4108C93.9296 43.6208 93.1996 44.7608 92.5496 45.9408C92.9896 42.4608 94.4396 39.6308 97.6896 37.9608C98.2596 37.6708 98.8596 37.4008 99.4696 37.2308C101.09 36.7908 102.86 37.2808 103.45 38.3608C104.24 39.8108 103.63 41.1208 102.67 42.1408C101.8 43.0608 100.7 43.7708 99.6296 44.4808C98.7096 45.0908 97.6996 45.5608 96.7396 46.0908C95.5096 46.7708 95.1196 47.7608 95.6796 49.3708C96.7596 49.4408 97.9196 49.4908 99.0896 49.5908C108.06 50.3708 116.81 52.1408 124.75 56.5408C128.26 58.4908 131.43 61.0808 134.64 63.5308C135.51 64.2008 135.95 65.4408 136.59 66.4108C135.96 65.9108 135.26 65.4808 134.7 64.9108C131.18 61.2908 126.97 58.7108 122.51 56.4408C121.2 55.7808 120.17 55.6708 119.1 56.9008C118.6 57.4708 117.72 57.7008 117.01 58.0808C117.76 57.2108 118.5 56.3308 119.37 55.3108C117.05 53.9308 114.64 53.4108 112.23 52.8708C106.82 51.6608 101.43 50.3708 95.8296 50.3408C94.3696 50.3408 93.0796 50.4308 92.8596 52.4408C92.5396 49.4708 91.9196 46.8508 90.1096 44.5608C88.4496 42.4608 87.0496 40.1608 85.4296 38.0308C84.1996 36.4208 82.9696 34.7508 81.4496 33.4408C77.5396 30.0708 71.8996 30.9508 69.1296 35.1908C67.0996 38.3008 68.4696 41.4508 72.1096 41.8808C74.1296 42.1208 76.1596 42.1908 78.1896 42.3508C80.2596 42.5108 82.1496 43.1808 83.8696 44.3608C84.7796 44.9808 85.7496 45.5208 86.7196 46.0608C89.2396 47.4508 90.8096 49.4008 90.6196 52.8708C89.7796 51.2108 88.6696 51.3908 87.3596 51.6208C80.2796 52.8408 74.4096 56.3808 69.1596 61.0708C67.6896 62.3908 66.4396 63.9508 64.6296 65.9008C68.3096 66.4908 71.2796 67.1808 74.2796 67.4008C81.9996 67.9808 89.5496 66.6108 97.0896 65.1008C103.58 63.8108 109.78 61.8708 115.54 58.5708C116 58.3108 116.55 58.2208 117.06 58.0408C116.77 58.3708 116.54 58.8508 116.18 59.0108C112.22 60.7708 108.36 62.8208 104.25 64.1008C99.5096 65.5808 94.5896 66.6208 89.6696 67.3608C84.9396 68.0708 80.1196 68.3908 75.3296 68.3908C72.3296 68.3908 69.3196 67.5608 66.3396 66.9608C64.7896 66.6508 63.6996 66.9008 62.8396 68.3208C60.9296 71.4808 58.8296 74.5308 56.9996 77.7408C56.0696 79.3708 55.0896 81.0808 54.9996 83.0008C55.2796 83.1908 55.4196 83.3608 55.5796 83.3908C67.2496 85.4608 78.9196 86.7708 90.8396 85.3708C106.79 83.5008 121.28 78.0708 134.32 68.7508C135.18 68.1408 135.78 67.1808 136.51 66.3808L136.53 66.3708ZM182.57 137.171C183.84 137.231 185.13 137.461 186.37 137.301C188.45 137.041 189.15 136.011 189.06 133.571C187.32 134.291 185.62 134.901 184.01 135.701C183.44 135.981 183.1 136.721 182.65 137.261C180.67 136.991 176.87 138.681 174.98 140.651C178.53 140.811 184.75 139.491 186.69 138.121C185.16 138.271 183.67 138.511 182.57 137.171V137.171ZM52.7296 116.021C52.7296 116.851 52.7496 117.751 52.7296 118.641C52.6796 120.671 52.6696 122.701 52.5196 124.731C52.1496 129.791 51.4696 134.821 51.7696 139.911C51.9796 143.511 52.2096 147.101 52.2596 150.701C52.3396 155.971 53.7596 160.891 55.6896 165.741C57.7096 170.811 61.0096 174.741 65.6196 177.671C69.8496 180.361 74.0896 183.221 79.1596 183.711C86.0396 184.371 92.9696 184.671 99.8896 184.771C105.24 184.851 110.6 184.551 115.94 184.111C126.29 183.261 135.56 180.051 142.39 171.591C144.35 169.161 145.81 166.621 146.47 163.551C148.12 155.841 148.71 148.001 148.99 140.161C149.3 131.481 149.01 122.801 148 114.171C147.93 113.581 147.62 113.031 147.38 112.371C146.5 112.661 145.8 112.901 145.11 113.121C138.43 115.231 131.81 117.541 125.05 119.381C119.89 120.781 114.53 121.161 109.17 121.551C97.6896 122.391 86.2796 122.351 74.9296 120.351C67.5796 119.061 60.2796 117.511 52.7196 116.021H52.7296ZM130.41 72.7608C129.5 73.2408 128.92 73.5108 128.38 73.8408C122.04 77.7308 115.14 80.3908 108.13 82.7108C100.29 85.3108 92.1496 86.4608 83.9096 86.9708C82.4696 87.0608 81.0496 87.2908 79.6096 87.4508C79.3396 98.3308 79.0896 108.921 78.8196 119.901C99.3396 122.141 119.31 121.501 138.85 114.411C137.11 100.251 134.94 86.3608 130.4 72.7508L130.41 72.7608ZM77.9296 119.591C78.2096 108.751 78.4896 98.1808 78.7596 87.5508C70.4496 86.5408 62.5196 85.5708 54.4496 84.5908C54.4196 84.6408 54.1896 84.8408 54.1196 85.0808C53.8196 86.0508 53.5296 87.0308 53.3096 88.0208C51.4796 96.2408 52.1696 104.571 52.3496 112.871C52.3696 113.931 52.8596 114.641 53.8296 114.971C54.6996 115.271 55.5896 115.551 56.4896 115.731C62.6496 116.951 68.8196 118.151 74.9896 119.321C75.8696 119.491 76.7796 119.491 77.9296 119.591V119.591ZM175.62 98.7408C173.07 99.7108 170.66 100.391 168.46 101.501C162.62 104.461 156.94 107.721 151.07 110.611C148.91 111.671 148.27 113.011 148.68 115.171C148.77 115.621 148.74 116.091 148.83 116.551C149.76 121.471 150.15 126.421 149.96 131.421C149.94 132.021 150.06 132.631 150.16 133.651C151.5 132.961 152.56 132.481 153.56 131.891C160.09 128.051 166.86 124.701 173.85 121.791C175.34 121.171 176.27 120.261 176.84 118.811C179.55 112.001 178.27 105.481 175.63 98.7408H175.62ZM37.6396 156.781C42.4296 160.961 47.5696 163.541 53.4896 164.571C52.7596 160.911 51.8696 157.601 51.4796 154.231C51.0796 150.751 51.0296 147.221 51.0696 143.711C51.1396 136.791 51.3896 129.881 51.5296 122.961C51.5896 120.151 51.5296 117.341 51.5296 114.531C51.2996 114.491 51.0796 114.461 50.8496 114.421C42.1696 127.161 38.9296 141.621 37.6196 156.771L37.6396 156.781ZM147.28 111.381C151.14 96.6908 144.56 75.3708 136.81 68.1008C135.07 69.2908 133.32 70.4808 131.58 71.7008C131.36 71.8508 131.23 72.1108 131.02 72.3608C136.03 85.7408 137.96 99.8008 139.89 114.221C142.65 113.161 145.03 112.241 147.28 111.371V111.381ZM60.7796 196.031C64.1296 197.671 67.4996 198.181 70.8396 198.631C74.0296 199.061 77.2696 199.141 80.4996 199.361C84.2196 199.621 87.8796 199.341 91.4496 198.161C92.5796 197.781 93.4196 197.201 93.5696 195.911C93.8596 193.481 93.5096 191.191 92.3596 188.911C86.8596 191.061 81.6096 189.911 76.5796 187.931C73.0996 186.561 69.8996 186.821 66.6696 188.271C63.3696 189.751 61.9096 192.641 60.7896 196.031H60.7796ZM196 131.101C194.74 129.291 193.68 128.011 192.89 126.581C190.73 122.701 187.96 119.691 183.07 119.211C182.83 117.181 182.65 115.261 182.36 113.361C182.21 112.381 182.1 111.251 181.56 110.491C179.88 108.141 179.57 105.571 179.86 102.841C180.15 100.091 179.3 97.5708 178.43 95.0608C178.18 94.3608 177.59 93.7808 176.86 92.7308C175.9 95.7508 176.24 98.0608 177.28 100.401C179.07 104.441 179.83 108.741 179.33 113.091C179.02 115.731 179.09 118.191 180.04 120.641C180.13 120.871 180.05 121.171 180.05 121.551C177.18 123.441 176.45 126.481 175.96 129.631C175.75 131.001 175.74 132.401 175.48 133.751C175.17 135.431 174.7 137.091 174.16 139.301C181.36 135.801 187.91 131.921 195.99 131.101H196ZM107.08 188.081C112.49 190.271 117.62 189.481 122.69 187.901C124.6 187.301 124.81 186.411 124.13 183.901C123.6 183.951 123.06 184.001 122.52 184.071C120.26 184.371 118 184.791 115.73 184.971C113.25 185.161 110.76 185.131 108.27 185.231C107.94 185.241 107.61 185.471 107.22 185.631C107.18 186.421 107.13 187.141 107.08 188.081V188.081ZM183.52 114.531C185.29 113.531 186.15 112.341 186.34 110.791C186.55 109.051 186.74 107.291 186.7 105.541C186.6 101.101 184.42 98.4308 180.61 97.7808C180.71 100.561 180.72 103.361 180.94 106.141C181.11 108.291 182.08 110.021 184.25 110.861C184.34 110.901 184.35 111.161 184.41 111.351C184.01 111.511 183.61 111.681 183.05 111.911C183.19 112.671 183.33 113.451 183.53 114.541L183.52 114.531ZM92.3896 185.661C87.0496 185.311 81.6896 184.951 76.3296 184.591C76.1596 186.781 76.1096 186.991 77.5396 187.341C80.3796 188.031 83.2596 188.681 86.1596 188.941C87.8296 189.091 89.5996 188.551 91.2896 188.161C92.2396 187.941 92.9296 187.301 92.3896 185.661V185.661ZM190.73 133.171C190.24 134.641 189.84 135.821 189.39 137.151C192.26 135.941 194.66 134.301 196.88 132.401C196.79 132.221 196.7 132.031 196.61 131.851C194.67 132.281 192.72 132.721 190.72 133.161L190.73 133.171Z" fill="black"/> +<path d="M52.7396 116.021C60.2996 117.501 67.5996 119.061 74.9496 120.351C86.2996 122.341 97.7096 122.391 109.19 121.551C114.55 121.161 119.91 120.781 125.07 119.381C131.82 117.541 138.45 115.231 145.13 113.121C145.83 112.901 146.52 112.661 147.4 112.371C147.63 113.031 147.95 113.581 148.02 114.171C149.03 122.811 149.32 131.491 149.01 140.161C148.73 148.001 148.14 155.841 146.49 163.551C145.83 166.621 144.37 169.161 142.41 171.591C135.58 180.051 126.31 183.271 115.96 184.111C110.63 184.551 105.26 184.851 99.9096 184.771C92.9896 184.671 86.0596 184.381 79.1796 183.711C74.1096 183.221 69.8696 180.361 65.6396 177.671C61.0296 174.741 57.7296 170.811 55.7096 165.741C53.7796 160.901 52.3596 155.971 52.2796 150.701C52.2296 147.101 51.9896 143.501 51.7896 139.911C51.4996 134.821 52.1796 129.781 52.5396 124.731C52.6896 122.711 52.6896 120.671 52.7496 118.641C52.7696 117.741 52.7496 116.851 52.7496 116.021H52.7396ZM128.13 128.991C125.25 129.001 123.3 130.841 123.33 133.511C123.37 136.051 125.68 138.001 128.62 137.971C131.16 137.941 133.17 135.751 133.12 133.051C133.08 130.781 130.86 128.981 128.12 128.991H128.13ZM126.74 145.441C128.74 144.971 130.65 144.631 132.47 144.041C132.97 143.881 133.51 142.981 133.53 142.401C133.55 141.511 132.85 140.831 131.87 140.931C129.21 141.211 126.55 141.531 123.9 141.941C123.47 142.011 122.88 142.551 122.8 142.951C122.72 143.381 123.05 144.161 123.42 144.331C124.46 144.821 125.61 145.071 126.73 145.421L126.74 145.441Z" fill="white"/> +<path d="M130.42 72.7608C134.96 86.3708 137.12 100.251 138.87 114.421C119.32 121.511 99.3596 122.151 78.8396 119.911C79.1096 108.931 79.3696 98.3408 79.6296 87.4608C81.0696 87.3008 82.4996 87.0708 83.9296 86.9808C92.1596 86.4608 100.31 85.3208 108.15 82.7208C115.16 80.4008 122.06 77.7408 128.4 73.8508C128.94 73.5208 129.52 73.2508 130.43 72.7708L130.42 72.7608ZM116.14 103.941C116.14 98.1508 111.59 93.7408 105.63 93.7508C100.64 93.7508 96.5296 97.9408 96.5496 103.021C96.5696 108.561 101.34 113.311 106.83 113.261C112.11 113.211 116.14 109.181 116.14 103.951V103.941Z" fill="#C8E2E2"/> +<path d="M136.53 66.3708C135.81 67.1708 135.2 68.1208 134.34 68.7408C121.3 78.0608 106.81 83.4808 90.8596 85.3608C78.9396 86.7608 67.2695 85.4508 55.5995 83.3808C55.4395 83.3508 55.2995 83.1808 55.0195 82.9908C55.1095 81.0708 56.0895 79.3608 57.0195 77.7308C58.8395 74.5208 60.9496 71.4708 62.8596 68.3108C63.7196 66.8908 64.7996 66.6408 66.3596 66.9508C69.3396 67.5508 72.3495 68.3808 75.3495 68.3808C80.1295 68.3808 84.9595 68.0608 89.6895 67.3508C94.6095 66.6208 99.5295 65.5708 104.27 64.0908C108.38 62.8108 112.25 60.7508 116.2 59.0008C116.57 58.8408 116.79 58.3608 117.08 58.0308L117.02 58.0908C117.73 57.7108 118.61 57.4808 119.11 56.9108C120.19 55.6808 121.22 55.7908 122.52 56.4508C126.97 58.7208 131.19 61.3008 134.71 64.9208C135.26 65.4908 135.96 65.9208 136.6 66.4208L136.549 66.3608L136.53 66.3708Z" fill="white"/> +<path d="M77.9496 119.591C76.7996 119.491 75.8896 119.491 75.0096 119.321C68.8396 118.141 62.6796 116.941 56.5096 115.731C55.6096 115.551 54.7196 115.271 53.8496 114.971C52.8796 114.641 52.3896 113.931 52.3696 112.871C52.1896 104.571 51.4996 96.2508 53.3296 88.0208C53.5496 87.0308 53.8396 86.0508 54.1396 85.0808C54.2196 84.8308 54.4396 84.6308 54.4696 84.5908C62.5396 85.5708 70.4696 86.5408 78.7796 87.5508C78.4996 98.1808 78.2296 108.761 77.9496 119.591V119.591Z" fill="white"/> +<path d="M117.07 58.0208C116.56 58.1908 116.01 58.2808 115.55 58.5508C109.8 61.8508 103.59 63.7908 97.0997 65.0808C89.5597 66.5808 82.0097 67.9508 74.2897 67.3808C71.2797 67.1508 68.3096 66.4608 64.6396 65.8808C66.4496 63.9308 67.6997 62.3708 69.1697 61.0508C74.4197 56.3508 80.2796 52.8208 87.3696 51.6008C88.6796 51.3708 89.7896 51.2008 90.6296 52.8508C90.8196 49.3908 89.2497 47.4308 86.7297 46.0408C85.7597 45.5108 84.7896 44.9708 83.8796 44.3408C82.1596 43.1608 80.2696 42.5008 78.1996 42.3308C76.1696 42.1708 74.1396 42.1008 72.1196 41.8608C68.4796 41.4308 67.0996 38.2808 69.1396 35.1708C71.9096 30.9308 77.5497 30.0508 81.4597 33.4208C82.9797 34.7308 84.2096 36.4008 85.4396 38.0108C87.0596 40.1408 88.4596 42.4508 90.1196 44.5408C91.9296 46.8208 92.5496 49.4508 92.8696 52.4208C93.0896 50.4108 94.3797 50.3208 95.8397 50.3208C101.44 50.3508 106.83 51.6408 112.24 52.8508C114.65 53.3908 117.06 53.9108 119.38 55.2908C118.51 56.3108 117.76 57.1908 117.02 58.0608L117.08 58.0008L117.07 58.0208Z" fill="white"/> +<path d="M175.63 98.7408C178.27 105.481 179.55 112.001 176.84 118.811C176.26 120.261 175.34 121.161 173.85 121.791C166.87 124.701 160.09 128.051 153.56 131.891C152.56 132.481 151.5 132.961 150.16 133.651C150.07 132.631 149.94 132.031 149.96 131.421C150.15 126.421 149.76 121.461 148.83 116.551C148.74 116.101 148.77 115.631 148.68 115.171C148.27 113.011 148.92 111.671 151.07 110.611C156.94 107.721 162.62 104.461 168.46 101.501C170.65 100.391 173.07 99.7108 175.62 98.7408H175.63Z" fill="white"/> +<path d="M37.6597 156.771C38.9697 141.621 42.2096 127.161 50.8896 114.421C51.1196 114.461 51.3396 114.491 51.5696 114.531C51.5696 117.341 51.6196 120.151 51.5696 122.961C51.4296 129.881 51.1797 136.791 51.1097 143.711C51.0697 147.221 51.1197 150.751 51.5197 154.231C51.9097 157.601 52.7897 160.911 53.5297 164.571C47.6097 163.541 42.4696 160.961 37.6796 156.781L37.6597 156.771Z" fill="white"/> +<path d="M147.299 111.381C145.039 112.251 142.66 113.161 139.91 114.231C137.98 99.8108 136.05 85.7508 131.04 72.3708C131.25 72.1208 131.39 71.8608 131.6 71.7108C133.34 70.5008 135.09 69.3108 136.83 68.1108C144.58 75.3708 151.159 96.7008 147.299 111.391V111.381Z" fill="white"/> +<path d="M60.7995 196.031C61.9195 192.641 63.3895 189.751 66.6795 188.271C69.9095 186.821 73.0995 186.561 76.5895 187.931C81.6295 189.911 86.8695 191.061 92.3695 188.911C93.5195 191.191 93.8695 193.491 93.5795 195.911C93.4295 197.211 92.5895 197.791 91.4595 198.161C87.8895 199.351 84.2295 199.621 80.5095 199.361C77.2895 199.141 74.0495 199.061 70.8495 198.631C67.5095 198.181 64.1296 197.671 60.7896 196.031H60.7995Z" fill="white"/> +<path d="M196.01 131.101C187.93 131.911 181.38 135.791 174.18 139.301C174.71 137.091 175.18 135.431 175.5 133.751C175.75 132.391 175.77 130.991 175.98 129.631C176.47 126.481 177.2 123.441 180.07 121.551C180.07 121.171 180.15 120.871 180.06 120.641C179.11 118.181 179.04 115.731 179.35 113.091C179.86 108.741 179.1 104.441 177.3 100.401C176.26 98.0608 175.91 95.7508 176.88 92.7308C177.6 93.7808 178.2 94.3608 178.45 95.0608C179.33 97.5708 180.17 100.091 179.88 102.841C179.59 105.561 179.9 108.141 181.58 110.491C182.13 111.251 182.23 112.381 182.38 113.361C182.67 115.261 182.85 117.181 183.09 119.211C187.97 119.691 190.75 122.701 192.91 126.581C193.7 128.011 194.76 129.291 196.02 131.101H196.01Z" fill="white"/> +<path d="M107.1 196.781C106.98 195.411 106.83 194.041 106.76 192.671C106.7 191.591 106.75 190.511 106.75 189.251C107.52 189.381 108.05 189.441 108.56 189.561C114.03 190.881 119.32 190.551 124.44 188.051C125.3 187.631 126.46 187.331 127.36 187.541C129.86 188.111 132.359 188.811 134.739 189.781C137.579 190.941 139.16 193.261 139.73 196.691C137.12 197.221 134.56 197.841 131.95 198.231C129.68 198.571 127.36 198.691 125.06 198.851C119.29 199.251 113.7 198.041 108.12 196.791C107.77 196.711 107.4 196.731 107.04 196.711L107.1 196.771V196.781Z" fill="white"/> +<path d="M56.0296 170.651C54.3096 170.891 52.5896 171.301 50.8696 171.351C44.2596 171.531 39.6196 166.901 39.5496 159.841C44.1496 162.871 48.9896 164.901 54.6196 166.011C55.0396 167.471 55.4996 169.101 55.9696 170.731L56.0396 170.671L56.0296 170.651ZM48.8896 166.961C48.7796 167.131 48.6596 167.301 48.5496 167.461C49.7296 168.241 50.8596 169.121 52.1196 169.741C52.6096 169.981 53.6196 169.821 54.0096 169.441C54.6496 168.821 54.5496 167.961 53.5096 167.301C53.2796 167.851 53.0696 168.341 52.7796 169.041C51.3996 168.301 50.1496 167.631 48.8896 166.961Z" fill="white"/> +<path d="M107.09 188.081C107.14 187.131 107.18 186.411 107.23 185.631C107.63 185.471 107.95 185.251 108.28 185.231C110.76 185.131 113.26 185.161 115.74 184.971C118.01 184.791 120.27 184.381 122.53 184.071C123.07 184.001 123.61 183.951 124.14 183.901C124.82 186.411 124.62 187.301 122.7 187.901C117.63 189.481 112.5 190.271 107.09 188.081V188.081Z" fill="white"/> +<path d="M183.53 114.531C183.33 113.441 183.19 112.671 183.05 111.901C183.61 111.671 184.01 111.511 184.41 111.341C184.35 111.141 184.34 110.881 184.25 110.851C182.08 110.021 181.11 108.281 180.94 106.131C180.72 103.351 180.71 100.551 180.61 97.7708C184.42 98.4208 186.6 101.091 186.7 105.531C186.74 107.281 186.56 109.041 186.34 110.781C186.15 112.331 185.3 113.511 183.52 114.521L183.53 114.531Z" fill="white"/> +<path d="M92.3996 185.661C92.9396 187.301 92.2496 187.941 91.2996 188.161C89.6096 188.551 87.8497 189.101 86.1697 188.941C83.2696 188.671 80.3896 188.031 77.5496 187.341C76.1196 186.991 76.1696 186.781 76.3396 184.591C81.6996 184.951 87.0596 185.301 92.3996 185.661V185.661Z" fill="white"/> +<path d="M92.5596 45.9308C93.2196 44.7608 93.9396 43.6208 94.5096 42.4008C95.4696 40.3408 97.1496 39.1408 99.1496 38.3208C99.7996 38.0608 100.6 38.0308 101.31 38.1008C102.72 38.2508 103.25 39.1208 102.7 40.4108C102.39 41.1408 101.88 41.8608 101.29 42.3908C100.62 43.0008 99.8096 43.4808 98.9896 43.8808C96.5496 45.0608 94.1796 46.2708 94.1996 49.9308C92.7996 48.5308 93.0796 47.0508 92.6196 45.8608L92.5696 45.9308H92.5596Z" fill="white"/> +<path d="M182.58 137.171C183.67 138.501 185.16 138.271 186.7 138.121C184.76 139.491 178.53 140.811 174.99 140.651C176.88 138.671 180.69 136.991 182.66 137.261L182.58 137.181V137.171Z" fill="white"/> +<path d="M190.74 133.171C192.74 132.721 194.68 132.291 196.63 131.861C196.72 132.041 196.81 132.231 196.9 132.411C194.67 134.301 192.28 135.951 189.41 137.161C189.86 135.831 190.25 134.651 190.75 133.181L190.74 133.171Z" fill="white"/> +<path d="M182.58 137.171L182.66 137.251C183.11 136.721 183.45 135.981 184.02 135.691C185.63 134.891 187.32 134.281 189.07 133.561C189.16 136.001 188.46 137.031 186.38 137.291C185.14 137.451 183.85 137.211 182.58 137.161V137.171Z" fill="white"/> +<path d="M128.13 128.991C130.87 128.981 133.09 130.781 133.13 133.051C133.18 135.751 131.17 137.951 128.63 137.971C125.69 138.001 123.37 136.051 123.34 133.511C123.3 130.841 125.25 129.001 128.14 128.991H128.13ZM132.21 132.991C132.16 131.191 130.07 129.571 127.89 129.631C125.92 129.691 124.36 131.431 124.43 133.501C124.5 135.451 126.34 137.011 128.61 137.031C130.66 137.051 132.28 135.231 132.21 132.991V132.991Z" fill="black"/> +<path d="M126.73 145.451C125.6 145.101 124.46 144.841 123.42 144.361C123.05 144.191 122.729 143.401 122.799 142.981C122.869 142.581 123.47 142.041 123.9 141.971C126.54 141.561 129.199 141.231 131.869 140.961C132.849 140.861 133.55 141.531 133.53 142.431C133.52 143.001 132.97 143.901 132.47 144.071C130.65 144.661 128.739 145.001 126.739 145.471L126.73 145.451ZM123.85 142.881C125.6 145.281 127.789 144.061 129.819 143.941C130.859 143.881 132.219 143.851 132.619 141.811C129.519 142.191 126.69 142.531 123.86 142.881H123.85Z" fill="black"/> +<path d="M116.14 103.951C116.14 109.181 112.11 113.211 106.83 113.261C101.34 113.311 96.5696 108.561 96.5496 103.021C96.5396 97.9508 100.64 93.7608 105.63 93.7508C111.59 93.7508 116.14 98.1508 116.14 103.941V103.951ZM111.57 96.8208C108.18 101.141 104.8 105.451 101.42 109.761C104.88 112.931 110.14 112.641 113.1 109.241C116.26 105.611 115.69 100.451 111.57 96.8208ZM100.44 109.161C103.67 104.521 107.47 100.641 110.39 96.0708C106.82 93.8108 102.61 94.4108 99.9296 97.4008C97.0496 100.601 97.1196 104.981 100.44 109.161Z" fill="black"/> +<path d="M48.8996 166.961C50.1496 167.631 51.4096 168.301 52.7896 169.041C53.0796 168.341 53.2896 167.851 53.5196 167.301C54.5596 167.961 54.6596 168.821 54.0196 169.441C53.6296 169.821 52.6196 169.981 52.1296 169.741C50.8696 169.131 49.7396 168.241 48.5596 167.461C48.6696 167.291 48.7896 167.121 48.8996 166.961Z" fill="black"/> +<path d="M132.21 132.991C132.28 135.231 130.66 137.041 128.61 137.031C126.34 137.011 124.49 135.451 124.43 133.501C124.36 131.441 125.92 129.691 127.89 129.631C130.08 129.571 132.16 131.191 132.21 132.991V132.991Z" fill="#F0C3BA"/> +<path d="M123.86 142.881C126.69 142.531 129.52 142.191 132.62 141.811C132.22 143.851 130.86 143.881 129.82 143.941C127.8 144.061 125.6 145.281 123.85 142.881H123.86Z" fill="white"/> +<path d="M111.56 96.8208C115.68 100.441 116.25 105.611 113.09 109.241C110.13 112.641 104.87 112.931 101.41 109.761C104.78 105.461 108.17 101.151 111.56 96.8208Z" fill="white"/> +<path d="M100.44 109.161C97.1196 104.991 97.0496 100.611 99.9296 97.4008C102.61 94.4108 106.82 93.8108 110.39 96.0708C107.47 100.641 103.67 104.511 100.44 109.161Z" fill="white"/> +<path d="M143.778 35.4336L143.833 35.0733L144.178 35.3194L144.188 35.3099L144.97 34.6028L144.557 34.5402C144.664 34.2811 144.77 34.0241 144.874 33.7692C145.282 32.7749 145.677 31.8113 146.146 30.869C147.167 28.8317 148.567 27.4904 150.227 26.6754C151.895 25.8566 153.856 25.5541 156.011 25.6515L156.011 25.6515C158.027 25.742 159.8 26.2371 161.255 27.1767C162.705 28.1136 163.866 29.5102 164.636 31.4536C164.863 32.0316 165.006 32.6524 165.137 33.3495C165.18 33.5813 165.223 33.825 165.267 34.0779C165.354 34.5692 165.446 35.0954 165.562 35.6371L165.702 36.2913L166.29 35.9714C167.705 35.2008 169.086 34.6674 170.376 34.593C171.641 34.5201 172.839 34.8855 173.936 35.9671C175.552 37.5666 177.087 39.3244 177.433 41.9095C162.827 42.0736 148.523 42.2338 134.137 42.3898C133.866 39.6566 134.953 37.5968 136.72 35.8319L136.721 35.8314C137.681 34.8702 138.73 34.6563 139.898 34.7375C140.494 34.7789 141.113 34.8971 141.767 35.0357C141.889 35.0616 142.013 35.0883 142.138 35.1153C142.671 35.2302 143.225 35.3498 143.778 35.4336Z" fill="#EEEFFC" stroke="black"/> +<path d="M171.359 14.2913L171.359 14.2913L171.361 14.2904C173.732 13.2652 175.997 14.2097 178.142 16.1528C178.365 16.3563 178.561 16.7038 178.779 17.1986C178.846 17.3504 178.916 17.5192 178.989 17.6964C179.137 18.051 179.298 18.4393 179.476 18.7933L179.744 19.3275L180.222 18.9694C181.026 18.3675 181.738 18.1552 182.35 18.2552C182.956 18.3542 183.592 18.7813 184.202 19.7405L184.203 19.7415C184.81 20.6921 185.202 21.748 184.665 22.9504C181.379 23.5967 178.064 23.6563 174.703 23.6428C174.245 23.641 173.785 23.6378 173.325 23.6346C170.521 23.6149 167.683 23.5951 164.85 23.8839C164.845 23.4199 164.858 23.046 164.899 22.7374C164.953 22.3217 165.051 22.0615 165.187 21.8751C165.32 21.6917 165.523 21.5337 165.87 21.3832C166.226 21.2294 166.702 21.0965 167.355 20.9483L167.244 20.4607L167.744 20.4598C167.741 18.7632 168.12 17.4524 168.748 16.4578C169.376 15.4646 170.273 14.7552 171.359 14.2913Z" fill="#EEEFFC" stroke="black"/> +</g> +<defs> +<clipPath id="clip0_9_195"> +<rect width="210" height="200" fill="white"/> +</clipPath> +</defs> </svg> diff --git a/frontend/app/svg/ca-no-bookmarked-session.svg b/frontend/app/svg/ca-no-bookmarked-session.svg index f3d84d404..fc69149aa 100644 --- a/frontend/app/svg/ca-no-bookmarked-session.svg +++ b/frontend/app/svg/ca-no-bookmarked-session.svg @@ -1,14 +1,27 @@ -<svg viewBox="0 0 250 100" fill="none" xmlns="http://www.w3.org/2000/svg"> -<rect width="250" height="100" rx="13.1579" fill="#3EAAAF" fill-opacity="0.08"/> -<rect opacity="0.6" x="85.8947" y="28.579" width="138.158" height="14.4737" rx="6.57895" fill="#3EAAAF" fill-opacity="0.5"/> -<rect opacity="0.3" x="85.8947" y="54.8948" width="46.0526" height="14.4737" rx="6.57895" fill="#3EAAAF" fill-opacity="0.5"/> -<g clip-path="url(#clip0_2_27)"> -<path d="M52.5 58C55.8152 58 58.9946 56.683 61.3388 54.3388C63.683 51.9946 65 48.8152 65 45.5C65 42.1848 63.683 39.0054 61.3388 36.6612C58.9946 34.317 55.8152 33 52.5 33C49.1848 33 46.0054 34.317 43.6612 36.6612C41.317 39.0054 40 42.1848 40 45.5C40 48.8152 41.317 51.9946 43.6612 54.3388C46.0054 56.683 49.1848 58 52.5 58V58ZM50.9375 43.1562C50.9375 44.45 50.2375 45.5 49.375 45.5C48.5125 45.5 47.8125 44.45 47.8125 43.1562C47.8125 41.8625 48.5125 40.8125 49.375 40.8125C50.2375 40.8125 50.9375 41.8625 50.9375 43.1562ZM46.6953 52.4266C46.5159 52.323 46.385 52.1523 46.3313 51.9522C46.2777 51.7521 46.3058 51.5388 46.4094 51.3594C47.0264 50.2901 47.9141 49.4022 48.9833 48.785C50.0526 48.1678 51.2655 47.8432 52.5 47.8438C53.7345 47.8435 54.9473 48.1683 56.0164 48.7854C57.0856 49.4025 57.9734 50.2903 58.5906 51.3594C58.6926 51.5387 58.7195 51.7511 58.6654 51.9502C58.6114 52.1493 58.4808 52.3189 58.3021 52.4221C58.1234 52.5252 57.9112 52.5535 57.7118 52.5008C57.5123 52.4481 57.3418 52.3186 57.2375 52.1406C56.7576 51.3088 56.0671 50.6182 55.2354 50.1381C54.4037 49.6581 53.4603 49.4057 52.5 49.4062C51.5397 49.4057 50.5963 49.6581 49.7646 50.1381C48.9329 50.6182 48.2424 51.3088 47.7625 52.1406C47.6589 52.3201 47.4883 52.451 47.2881 52.5046C47.088 52.5582 46.8747 52.5302 46.6953 52.4266ZM55.625 45.5C54.7625 45.5 54.0625 44.45 54.0625 43.1562C54.0625 41.8625 54.7625 40.8125 55.625 40.8125C56.4875 40.8125 57.1875 41.8625 57.1875 43.1562C57.1875 44.45 56.4875 45.5 55.625 45.5Z" fill="#3EAAAF" fill-opacity="0.5"/> -</g> -<path d="M31.875 28.875C31.875 27.0516 32.5993 25.303 33.8886 24.0136C35.178 22.7243 36.9266 22 38.75 22H66.25C68.0734 22 69.822 22.7243 71.1114 24.0136C72.4007 25.303 73.125 27.0516 73.125 28.875V75.2812C73.1248 75.5921 73.0404 75.8972 72.8805 76.1639C72.7207 76.4305 72.4916 76.6489 72.2175 76.7956C71.9434 76.9424 71.6347 77.012 71.3241 76.9971C71.0136 76.9823 70.7129 76.8835 70.4541 76.7113L52.5 67.0347L34.5459 76.7113C34.2871 76.8835 33.9864 76.9823 33.6759 76.9971C33.3653 77.012 33.0566 76.9424 32.7825 76.7956C32.5084 76.6489 32.2793 76.4305 32.1195 76.1639C31.9596 75.8972 31.8752 75.5921 31.875 75.2812V28.875ZM38.75 25.4375C37.8383 25.4375 36.964 25.7997 36.3193 26.4443C35.6747 27.089 35.3125 27.9633 35.3125 28.875V72.0706L51.5478 63.5387C51.8299 63.351 52.1612 63.2509 52.5 63.2509C52.8388 63.2509 53.1701 63.351 53.4522 63.5387L69.6875 72.0706V28.875C69.6875 27.9633 69.3253 27.089 68.6807 26.4443C68.036 25.7997 67.1617 25.4375 66.25 25.4375H38.75Z" fill="#3EAAAF" fill-opacity="0.5"/> -<defs> -<clipPath id="clip0_2_27"> -<rect width="25" height="25" fill="white" transform="translate(40 33)"/> -</clipPath> -</defs> +<svg viewBox="0 0 210 197" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect width="210" height="197" fill="white"/> +<path d="M125.355 144.82V51.0932C125.355 47.8823 127.883 45.2405 131.09 45.0991L176.201 43.1098C179.754 42.9531 182.663 45.904 182.455 49.4544L176.014 159.538C175.666 165.49 167.789 167.346 164.821 162.175L155.192 145.401C153.572 142.579 149.997 141.563 147.135 143.11L134.208 150.098C130.21 152.259 125.355 149.364 125.355 144.82Z" fill="#FDF9F3" stroke="black" stroke-linecap="round"/> +<path d="M11.7727 185.874C15.0263 184.5 15.9446 184.649 20.3026 183.518C29.6116 181.102 30.5103 181.623 41.8592 180.47C67.2667 177.889 103.054 183.704 121.082 178.953C139.11 174.202 154.41 181.603 170.471 183.704C178.025 184.693 186.937 186.093 197.768 187.542" stroke="black" stroke-linecap="round"/> +<path d="M155.814 104.849C154.768 105.367 154.018 105.803 153.22 106.117C152.092 106.558 150.932 106.923 149.776 107.295C148.996 107.545 148.766 107.942 149.024 108.753C149.528 110.337 149.91 111.957 150.378 113.689C148.305 113.777 146.693 113.274 145.43 112.195C145.994 110.909 146.63 109.755 146.996 108.527C147.289 107.546 147.353 106.469 147.308 105.442C147.293 105.119 146.533 104.513 146.214 104.569C144.386 104.887 142.556 105.269 140.79 105.821C139.338 106.275 137.977 106.995 136.576 107.597C136.484 107.49 136.392 107.383 136.298 107.277C137.008 106.37 137.674 105.429 138.436 104.563C139.628 103.209 139.634 103.245 138.544 101.783C137.822 100.815 137.17 99.8019 136.376 98.6473C139.102 98.4913 141.281 99.2713 143.196 100.305C144.817 98.1019 146.36 96.0046 147.901 93.9086C148.04 93.9579 148.177 94.0073 148.314 94.0566C148.349 94.4166 148.422 94.7779 148.412 95.1353C148.353 96.9166 148.264 98.6966 148.201 100.478C148.145 102.097 148.178 102.018 149.758 102.465C151.418 102.934 153.029 103.565 154.653 104.149C154.95 104.255 155.205 104.475 155.814 104.849Z" fill="#EBC885"/> +<path d="M140.433 90.4433C141.155 92.683 141.79 94.6582 142.425 96.6321C141.525 96.5321 139.969 92.1561 140.433 90.4433Z" fill="black"/> +<path d="M154.099 109.964C155.128 109.397 160.3 110.38 160.951 111.221C158.631 110.796 156.511 110.406 154.099 109.964Z" fill="black"/> +<path d="M156.5 95.1787C155.239 96.8133 153.305 97.7947 151.844 99.5C151.775 99.1413 151.648 98.872 151.72 98.808C153.199 97.4787 154.701 96.172 156.199 94.8613C156.3 94.9667 156.4 95.0733 156.5 95.1787Z" fill="black"/> +<path d="M56.3003 170.162C57.4327 171.807 57.8185 172.262 59.0498 172.547C60.9838 172.995 62.8895 173.644 64.9329 173.513C65.8124 173.457 66.7032 173.615 67.5892 173.618C69.1957 173.623 70.8306 173.862 72.5442 173.146C72.4623 172.505 72.3829 171.886 72.2921 171.18C66.8491 170.834 61.5812 170.498 56.3003 170.162ZM94.2303 170.022C92.9536 170.163 91.7321 170.355 90.5041 170.424C86.5258 170.647 82.545 170.839 78.5643 171.014C77.4595 171.061 76.3506 171.001 75.2442 171.006C74.6347 171.008 74.0397 171.082 73.8249 171.795C73.6109 172.507 73.9343 173.26 74.749 173.552C75.7111 173.897 76.7227 174.251 77.7286 174.303C82.2305 174.533 86.7235 174.521 91.1631 173.507C93.5915 172.952 93.7528 172.852 94.2303 170.022ZM64.4563 47.7206C64.4044 48.0878 64.3023 48.3707 64.3355 48.6365C64.5957 50.7067 65.4063 52.4511 67.2147 53.6605C67.8445 54.082 68.6526 54.4005 68.9744 55.3781C67.2139 55.8985 65.5789 55.2929 63.6644 55.2711C64.1256 55.9682 64.3704 56.5072 64.7627 56.8995C65.724 57.86 66.8896 58.489 68.2498 58.7079C70.0168 58.9932 71.3381 59.9578 72.5361 61.2588C73.5209 62.3295 74.0762 63.5867 74.6744 64.8561C74.8657 65.2606 75.2239 65.5864 75.7079 66.2081C75.9235 65.4486 76.0864 65.0482 76.1472 64.634C76.6538 61.1396 76.0183 57.8041 74.274 54.7896C73.0265 52.6335 71.6023 50.4911 69.0903 49.5071C67.5859 48.917 66.0742 48.3439 64.4563 47.7206ZM134.062 112.02C134.923 115.549 135.773 119.045 136.637 122.537C136.721 122.878 136.897 123.195 137.024 123.506C141.098 122.687 143.124 120.503 143.42 116.616C143.499 115.589 143.529 114.536 143.391 113.521C143.255 112.517 142.877 111.545 142.592 110.516C142.818 110.209 143.061 109.931 143.244 109.618C143.445 109.275 143.592 108.899 143.871 108.306C143.042 108.306 142.437 108.17 141.924 108.33C139.17 109.182 136.525 110.288 134.062 112.02ZM56.841 173.057C56.4681 175.558 57.0744 177.805 58.9549 179.32C61.1573 181.096 63.7089 182.343 66.7202 182.124C68.4192 182.001 70.1254 181.98 71.8228 181.84C72.3651 181.796 72.8879 181.519 73.281 181.393C73.161 179.1 73.0549 177.057 72.9487 175.025C70.149 174.904 67.4895 174.951 64.8746 174.633C62.2475 174.312 59.665 173.628 56.841 173.057ZM94.2449 173.765C87.7465 175.714 81.2449 175.897 74.5998 174.938C74.2967 176.761 74.4207 178.381 74.6339 179.977C74.7036 180.501 75.1024 181.148 75.5457 181.423C76.6635 182.115 77.8348 182.883 79.088 183.169C84.9768 184.511 90.5705 183.472 95.7947 180.552C98.5587 179.007 100.866 176.973 101.429 173.412C99.589 172.165 97.6817 171.203 95.2848 171.896C94.9103 172.568 94.5561 173.205 94.2449 173.765ZM109.869 83.1937C107.548 85.4098 104.796 86.7148 102.183 88.2209C102.183 88.6083 102.133 88.9099 102.191 89.1887C103.073 93.4223 103.287 97.7135 103.296 102.02C103.302 105.283 103.269 108.548 103.273 111.811C103.274 112.372 103.374 112.933 103.438 113.612C107.3 112.642 110.758 111.238 113.867 109.22C115.511 101.252 113.348 88.5467 109.869 83.1937ZM53.8013 67.5432C53.3304 68.9244 53.7186 69.859 54.5705 70.5739C55.3025 71.1875 56.0717 71.8416 56.9398 72.1885C58.7896 72.927 60.6815 73.6168 62.6155 74.078C68.2279 75.4171 73.9416 75.4187 79.6432 74.9153C83.4829 74.5765 87.3023 73.9272 90.8575 72.3263C92.1236 71.7557 93.3767 71.0254 94.4232 70.1216C96.3442 68.4623 96.1221 67.1038 93.9036 65.8531C89.3725 63.299 84.4807 61.9469 79.3182 61.5222C78.8999 61.4882 78.4687 61.6106 77.9888 61.6673C77.3817 63.5511 77.9856 65.8061 76.1707 67.1784C74.9103 67.1038 74.2245 66.4991 73.7836 65.4138C73.3694 64.3941 72.806 63.427 72.2378 62.4787C71.8276 61.7946 71.1816 61.5295 70.3419 61.6073C64.3128 62.1658 58.8009 64.1298 53.8013 67.5432ZM132.433 111.51C131.691 111.848 131.275 111.998 130.897 112.215C126.82 114.556 122.398 116.032 117.953 117.476C114.715 118.528 111.499 119.657 108.315 120.863C105.199 122.044 102.134 123.36 99.0516 124.629C96.5834 125.644 94.1192 126.675 91.9631 128.291C91.4338 128.686 90.8275 129.077 90.4927 129.617C89.3166 131.514 88.3496 133.513 88.1526 135.795C87.9102 138.608 88.9672 140.399 91.621 141.261C92.7988 141.643 94.0795 141.811 95.3237 141.869C99.7527 142.075 104.018 141.085 108.215 139.827C117.274 137.111 125.789 133.137 133.995 128.471C134.564 128.147 135.086 127.739 135.76 127.282C135.682 121.861 134.253 116.781 132.433 111.51ZM39.673 84.6317C40.4212 84.8854 40.9513 85.1302 41.5073 85.2428C47.1643 86.389 52.81 87.6056 58.4913 88.6164C63.5955 89.5251 68.7442 90.1192 73.9489 90.1541C78.476 90.1849 82.9755 89.9255 87.4596 89.3078C92.3757 88.6302 97.2748 87.8439 102.073 86.5624C105.026 85.7729 107.355 84.0286 109.196 81.3877C106.188 75.8467 102.219 71.2921 96.9805 67.8277C95.0473 71.3707 93.8931 72.3523 89.9026 73.8089C88.9299 74.1639 87.9402 74.489 86.9359 74.7386C78.6746 76.7885 70.3694 76.881 62.0489 75.1512C59.8725 74.6989 57.7512 74.0067 55.8034 72.8889C54.5705 72.1813 53.439 71.3593 52.7646 70.0446C52.5765 69.6774 52.3293 69.341 52.0091 68.8295C46.4364 73.0477 42.4363 78.2233 39.673 84.6317ZM101.996 114.114C102.891 109.478 101.973 92.0606 100.667 88.55C100.325 88.55 99.9562 88.5111 99.5979 88.5556C93.2762 89.3338 86.9675 90.2424 80.6305 90.8585C77.3282 91.1795 73.9692 91.1462 70.6499 90.9833C67.4011 90.8228 64.1742 90.2651 60.9327 89.9336C54.6638 89.2916 48.4929 88.0977 42.3536 86.7173C41.3558 86.4927 40.3734 86.1993 39.2077 85.8905C36.0546 93.4791 35.5991 101.259 36.4113 109.164C40.7957 111.881 45.45 113.325 50.1635 114.65C55.4768 116.143 61.0041 116.234 66.3887 117.242C66.6051 117.282 66.8329 117.268 67.055 117.272C70.3159 117.327 73.5769 117.427 76.837 117.42C79.3563 117.415 81.8852 117.399 84.3899 117.17C87.4847 116.886 90.5592 116.382 93.6402 115.962C96.5007 115.573 99.3069 114.95 101.996 114.114ZM113.75 117.239C114.05 114.149 113.965 111.907 113.468 110.827C112.813 111.156 112.169 111.499 111.51 111.809C110.438 112.312 109.382 112.863 108.273 113.272C103.327 115.1 98.2799 116.55 93.0168 117.135C90.6613 117.397 88.3123 117.711 85.9535 117.93C83.9652 118.115 81.9679 118.218 79.9731 118.313C79.3133 118.345 78.6478 118.203 77.9848 118.207C75.0278 118.226 72.066 118.376 69.1138 118.269C66.5306 118.176 63.9594 117.769 61.3777 117.559C53.1034 116.881 45.1411 115.063 37.7317 111.179C37.4853 111.05 37.2008 110.992 36.8198 110.863C36.7347 111.246 36.6317 111.512 36.6236 111.781C36.4972 115.78 36.3626 119.781 36.2775 123.781C36.2459 125.262 36.3221 126.745 36.378 128.227C36.438 129.781 36.5815 131.333 36.6026 132.888C36.6512 136.52 36.6698 140.154 36.6601 143.788C36.648 148.427 37.7738 152.753 40.2453 156.681C44.2698 163.076 50.0475 167.091 57.3354 168.922C58.7523 169.278 60.2559 169.337 61.7255 169.42C64.3144 169.565 66.9091 169.631 69.5013 169.713C72.3059 169.803 75.0926 170.133 77.9199 169.903C81.4629 169.615 85.0254 169.506 88.5806 169.467C90.2301 169.449 91.8415 169.333 93.4108 168.874C103.936 165.787 110.99 159.276 113.246 148.279C113.807 145.545 113.763 142.688 113.981 139.887C113.989 139.78 113.825 139.66 113.676 139.457C113.013 139.669 112.314 139.889 111.616 140.113C107.162 141.54 102.663 142.758 97.9654 143.064C95.6496 143.215 93.3589 143.063 91.1598 142.31C87.9702 141.216 86.4342 138.655 87.1686 135.342C87.5633 133.556 88.4258 131.878 89.0296 130.135C89.4884 128.808 90.3776 127.875 91.4922 127.081C93.6264 125.562 96.0103 124.543 98.4169 123.573C103.362 121.579 108.318 119.61 113.267 117.624C113.398 117.571 113.498 117.444 113.75 117.239ZM68.9298 60.4214C68.2222 60.158 67.5438 59.9254 66.8823 59.6538C65.1809 58.9568 63.8419 57.8333 62.9738 56.1976C62.4599 55.2289 62.4469 54.8277 62.7727 53.3557C63.9416 53.6767 64.8486 54.8788 66.4001 54.4265C66.0969 54.0811 65.8626 53.7764 65.5919 53.5097C64.1369 52.0798 63.5874 50.2512 63.3288 48.3107C63.2291 47.5674 63.3328 46.8298 64.0056 46.1465C64.3517 46.2956 64.7376 46.4504 65.1137 46.6255C65.784 46.9376 66.4195 47.3688 67.1215 47.5641C72.7347 49.1245 75.4866 53.3249 77.2552 58.4501C77.4465 59.0046 77.5851 59.5768 77.7732 60.2285C79.691 60.5228 81.5886 60.7635 83.4667 61.1112C89.4884 62.225 94.9185 64.6988 99.6295 68.5961C108.707 76.1069 114.153 85.6619 115.542 97.446C116.043 101.695 115.847 105.88 114.683 110.01C114.543 110.503 114.424 111.043 114.479 111.542C114.672 113.287 114.945 115.023 115.207 116.898C121.324 115.303 127.024 113.077 132.482 110.218C132.934 110.46 133.315 110.662 133.72 110.879C133.912 110.812 134.127 110.759 134.322 110.664C136.383 109.656 138.408 108.571 140.511 107.662C141.361 107.295 142.385 107.14 143.315 107.187C144.758 107.261 145.245 108.22 144.644 109.508C144.458 109.906 144.175 110.259 143.88 110.72C144.041 111.235 144.219 111.791 144.385 112.349C145.138 114.882 144.788 117.337 143.833 119.74C143.198 121.339 141.963 122.388 140.516 123.208C139.359 123.863 138.173 124.469 136.9 125.15C136.856 126.116 136.813 127.064 136.759 128.211C134.189 129.641 131.592 131.218 128.879 132.562C124.962 134.502 120.993 136.345 116.981 138.078C115.614 138.669 114.963 139.429 115.034 140.888C115.073 141.701 115.004 142.519 115 143.335C114.982 147.325 114.084 151.073 112.316 154.683C111.033 157.303 109.557 159.757 107.626 161.914C104.756 165.117 101.251 167.431 97.2391 168.984C96.8606 169.13 96.5323 169.424 96.2121 169.688C96.1408 169.747 96.2016 169.966 96.2016 170.224C98.5206 170.641 100.798 171.241 102.617 173.181C102.287 175.207 101.814 177.221 100.259 178.733C98.8238 180.127 97.214 181.295 95.378 182.18C90.0323 184.757 84.4588 185.383 78.6916 184.206C77.1183 183.884 75.6244 183.169 73.7876 182.527C71.5853 183.008 69.0109 183.188 66.4154 183.231C63.0921 183.287 60.2997 181.887 57.9482 179.805C56.0596 178.132 55.1979 175.791 55.7021 173.153C55.771 172.794 55.8942 172.447 55.9996 172.067C55.0634 170.401 53.8508 169.263 52.0286 168.436C42.2255 163.99 36.8295 156.195 35.535 145.601C35.2149 142.977 35.6347 140.269 35.6015 137.602C35.5302 131.894 35.3308 126.187 35.2919 120.48C35.2708 117.443 35.4872 114.404 35.5667 111.365C35.5861 110.631 35.5739 109.875 35.4305 109.159C33.755 100.809 34.7934 92.7625 38.2261 85.028C41.2909 78.1227 45.7726 72.2842 51.8705 67.7653C56.3408 64.4524 61.2999 62.1934 66.7745 61.1104C67.4279 60.9816 68.0804 60.8494 68.7313 60.7108C68.7718 60.7019 68.7961 60.6208 68.9298 60.4214Z" fill="black"/> +<path d="M113.75 117.239C113.497 117.444 113.398 117.572 113.266 117.624C108.318 119.609 103.362 121.579 98.4168 123.573C96.0102 124.543 93.6263 125.561 91.4921 127.081C90.3775 127.875 89.4883 128.808 89.0295 130.135C88.4257 131.877 87.5632 133.556 87.1685 135.342C86.4349 138.655 87.9701 141.216 91.1597 142.31C93.3588 143.063 95.6503 143.215 97.9653 143.064C102.663 142.758 107.162 141.54 111.617 140.113C112.314 139.889 113.013 139.669 113.676 139.457C113.825 139.66 113.989 139.78 113.981 139.887C113.763 142.688 113.807 145.545 113.246 148.279C110.99 159.276 103.936 165.787 93.4107 168.874C91.8414 169.334 90.23 169.449 88.5805 169.467C85.0253 169.506 81.4628 169.615 77.9198 169.903C75.0925 170.132 72.3058 169.803 69.5012 169.714C66.909 169.631 64.3144 169.565 61.7254 169.42C60.2558 169.337 58.7522 169.278 57.3361 168.922C50.0483 167.092 44.2705 163.076 40.246 156.681C37.7746 152.753 36.6479 148.427 36.66 143.788C36.6698 140.154 36.6511 136.52 36.6025 132.887C36.5814 131.334 36.4379 129.78 36.3788 128.227C36.322 126.745 36.2458 125.262 36.2774 123.781C36.3625 119.78 36.4971 115.78 36.6236 111.781C36.6325 111.512 36.7354 111.246 36.8197 110.863C37.2007 110.992 37.4852 111.05 37.7316 111.179C45.1411 115.063 53.1033 116.881 61.3776 117.559C63.9593 117.769 66.5305 118.176 69.1138 118.27C72.0659 118.377 75.0277 118.226 77.9847 118.207C78.6477 118.203 79.3132 118.345 79.973 118.313C81.9678 118.218 83.9651 118.115 85.9534 117.93C88.3122 117.711 90.6612 117.397 93.0168 117.136C98.2806 116.55 103.326 115.1 108.273 113.272C109.381 112.863 110.438 112.312 111.51 111.808C112.169 111.499 112.813 111.156 113.468 110.827C113.965 111.907 114.05 114.149 113.75 117.239Z" fill="white"/> +<path d="M101.996 114.114C99.3066 114.95 96.5012 115.573 93.6407 115.962C90.5589 116.381 87.4844 116.886 84.3897 117.169C81.885 117.4 79.356 117.416 76.8375 117.421C73.5766 117.426 70.3157 117.327 67.0547 117.272C66.8326 117.268 66.6057 117.282 66.3893 117.242C61.0038 116.235 55.4765 116.143 50.1632 114.65C45.4497 113.325 40.7954 111.882 36.4118 109.164C35.5996 101.259 36.0552 93.4796 39.2075 85.891C40.3731 86.199 41.3555 86.4924 42.3533 86.7169C48.4926 88.0973 54.6635 89.2921 60.9325 89.9333C64.174 90.2656 67.4008 90.8233 70.6496 90.983C73.9689 91.1467 77.3279 91.1791 80.6302 90.859C86.9673 90.2429 93.276 89.3335 99.5976 88.5553C99.9559 88.5115 100.325 88.5496 100.667 88.5496C101.973 92.061 102.891 109.479 101.996 114.114Z" fill="white"/> +<path d="M39.6729 84.632C42.4361 78.2228 46.4355 73.0473 52.0082 68.8299C52.3291 69.3405 52.5764 69.6777 52.7644 70.0441C53.4388 71.3597 54.5704 72.1808 55.8033 72.8892C57.7511 74.007 59.8715 74.6992 62.0479 75.1515C70.3684 76.8813 78.6736 76.7881 86.9358 74.7381C87.9401 74.4893 88.9298 74.1642 89.9024 73.8092C93.8929 72.3518 95.0472 71.3702 96.9804 67.8272C102.218 71.2924 106.187 75.847 109.195 81.3881C107.355 84.0281 105.025 85.7733 102.072 86.562C97.2738 87.8435 92.3755 88.6305 87.4586 89.3082C82.9745 89.9258 78.475 90.1844 73.9479 90.1536C68.7441 90.1188 63.5953 89.5254 58.4911 88.6168C52.809 87.6052 47.1642 86.3893 41.5072 85.2432C40.9503 85.1305 40.421 84.8857 39.6729 84.632Z" fill="white"/> +<path d="M132.432 111.51C134.253 116.781 135.682 121.861 135.759 127.281C135.085 127.739 134.564 128.147 133.994 128.472C125.789 133.137 117.274 137.111 108.215 139.827C104.017 141.085 99.7522 142.075 95.3232 141.869C94.0798 141.811 92.7991 141.643 91.6213 141.261C88.9667 140.399 87.9097 138.608 88.1521 135.795C88.349 133.513 89.3161 131.514 90.493 129.617C90.827 129.077 91.4341 128.686 91.9626 128.291C94.1195 126.675 96.5828 125.644 99.051 124.629C102.134 123.36 105.199 122.044 108.315 120.863C111.498 119.657 114.715 118.528 117.953 117.476C122.397 116.032 126.82 114.556 130.898 112.215C131.275 111.998 131.69 111.848 132.432 111.51Z" fill="white"/> +<path d="M53.8006 67.543C58.8003 64.1296 64.3121 62.1656 70.3412 61.6079C71.1809 61.5301 71.8278 61.7944 72.2379 62.4793C72.8053 63.4269 73.3687 64.3939 73.7829 65.4136C74.2246 66.4989 74.9104 67.1036 76.17 67.179C77.9849 65.8067 77.381 63.5509 77.9889 61.6671C78.468 61.6112 78.8992 61.488 79.3175 61.522C84.48 61.9468 89.3726 63.2996 93.9037 65.8537C96.1215 67.1036 96.3444 68.463 94.4225 70.1222C93.3761 71.0252 92.1237 71.7555 90.8568 72.3262C87.3024 73.9278 83.4822 74.5763 79.6425 74.9151C73.9409 75.4185 68.2272 75.4169 62.6148 74.0778C60.6808 73.6166 58.7897 72.9268 56.94 72.1884C56.0711 71.8422 55.3018 71.1873 54.5707 70.5737C53.718 69.8588 53.3305 68.9242 53.8006 67.543Z" fill="white"/> +<path d="M109.869 83.1941C113.348 88.5463 115.511 101.252 113.867 109.22C110.758 111.238 107.299 112.641 103.438 113.612C103.373 112.933 103.274 112.371 103.273 111.811C103.269 108.547 103.302 105.284 103.296 102.02C103.287 97.7131 103.073 93.4227 102.191 89.1891C102.132 88.9103 102.183 88.6087 102.183 88.2213C104.796 86.7152 107.548 85.4094 109.869 83.1941Z" fill="#CAE4E4"/> +<path d="M94.2448 173.765C94.5561 173.206 94.9103 172.569 95.2848 171.895C97.6816 171.204 99.5889 172.164 101.428 173.412C100.866 176.973 98.5587 179.007 95.7946 180.552C90.5705 183.473 84.9759 184.512 79.0871 183.169C77.8348 182.883 76.6635 182.116 75.5457 181.423C75.1015 181.148 74.7035 180.501 74.633 179.977C74.4206 178.38 74.2966 176.761 74.5998 174.937C81.244 175.896 87.7456 175.714 94.2448 173.765Z" fill="white"/> +<path d="M56.8403 173.057C59.6652 173.627 62.2476 174.312 64.8739 174.633C67.4888 174.952 70.1483 174.904 72.9488 175.024C73.0542 177.057 73.1612 179.1 73.2803 181.392C72.8872 181.519 72.3644 181.796 71.8221 181.841C70.1256 181.98 68.4185 182.001 66.7196 182.124C63.7091 182.343 61.1566 181.096 58.9543 179.32C57.0746 177.805 56.4674 175.558 56.8403 173.057Z" fill="white"/> +<path d="M134.062 112.02C136.525 110.288 139.17 109.182 141.924 108.33C142.437 108.17 143.042 108.306 143.871 108.306C143.592 108.899 143.445 109.275 143.244 109.618C143.061 109.931 142.818 110.209 142.592 110.516C142.876 111.545 143.254 112.517 143.391 113.521C143.528 114.536 143.498 115.589 143.42 116.616C143.124 120.503 141.098 122.687 137.023 123.506C136.897 123.195 136.721 122.878 136.637 122.537C135.773 119.045 134.923 115.549 134.062 112.02Z" fill="white"/> +<path d="M64.4563 47.7209C66.0734 48.3434 67.5851 48.9173 69.0904 49.5074C71.6015 50.4914 73.0257 52.6338 74.2732 54.7899C76.0183 57.8036 76.6538 61.1399 76.1464 64.6335C76.0864 65.0485 75.9235 65.4481 75.7079 66.2084C75.2232 65.5867 74.8649 65.2609 74.6744 64.8564C74.0754 63.587 73.521 62.3298 72.5353 61.2591C71.3381 59.9573 70.0168 58.9935 68.2498 58.7082C66.8888 58.4893 65.7241 57.8603 64.7627 56.899C64.3704 56.5075 64.1248 55.9685 63.6636 55.2706C65.5781 55.2932 67.2131 55.8987 68.9744 55.3784C68.6527 54.4008 67.8445 54.0823 67.2139 53.6599C65.4063 52.4506 64.5949 50.707 64.3355 48.6368C64.3015 48.371 64.4036 48.0881 64.4563 47.7209Z" fill="white"/> +<path d="M94.2306 170.022C93.7524 172.852 93.5919 172.952 91.1634 173.507C86.7239 174.521 82.2309 174.533 77.729 174.303C76.7231 174.252 75.7115 173.897 74.7485 173.552C73.9347 173.26 73.6113 172.507 73.8253 171.795C74.0401 171.082 74.635 171.009 75.2438 171.005C76.351 171.001 77.4599 171.062 78.5647 171.013C82.5454 170.839 86.5253 170.647 90.5036 170.424C91.7325 170.355 92.954 170.163 94.2306 170.022Z" fill="white"/> +<path d="M56.3 170.162C61.5809 170.498 66.8488 170.834 72.2918 171.18C72.3826 171.886 72.4621 172.505 72.5439 173.146C70.8304 173.862 69.1955 173.623 67.5889 173.618C66.7029 173.615 65.8121 173.457 64.9327 173.513C62.8892 173.644 60.9835 172.996 59.0495 172.547C57.8183 172.262 57.4324 171.807 56.3 170.162Z" fill="white"/> +<path d="M21.5141 64.4412C22.6241 64.3912 23.8841 65.5912 23.8541 66.6712C23.8341 67.5112 22.5341 68.6112 21.5141 68.6612C20.4341 68.7112 19.4641 67.7212 19.4641 66.5612C19.4641 65.3812 20.3141 64.5012 21.5141 64.4512V64.4412ZM21.2941 68.0112C21.8641 67.5112 22.5041 67.2112 22.5641 66.8312C22.6241 66.4512 22.0841 65.9912 21.8141 65.5712C21.4241 65.8712 20.8041 66.1112 20.7041 66.4912C20.6041 66.8512 21.0241 67.3712 21.3041 68.0212L21.2941 68.0112Z" fill="black"/> +<path d="M57.8341 13.7912C57.7741 15.0512 56.7141 16.0712 55.5141 16.0112C54.3241 15.9512 53.5641 15.1612 53.6741 14.0912C53.7841 12.9412 54.7941 11.9612 55.8141 12.0012C57.0141 12.0512 57.8841 12.8212 57.8441 13.7912H57.8341ZM55.6741 12.8212C55.2641 13.3912 54.8741 13.7512 54.7741 14.1812C54.7341 14.3612 55.4741 14.9512 55.5941 14.8912C55.9941 14.6612 56.3641 14.2612 56.5541 13.8412C56.6141 13.7012 56.0941 13.3012 55.6741 12.8212Z" fill="black"/> +<path d="M81.2641 35.9812C80.7641 36.8412 80.2641 37.7012 79.7741 38.5612C78.7641 37.8112 77.7641 37.0612 76.7041 36.2712C77.2441 35.2812 77.7641 34.3312 78.3441 33.2612C79.1341 34.4112 79.8841 35.5012 80.6441 36.6012C80.8541 36.3912 81.0641 36.1912 81.2741 35.9812H81.2641Z" fill="white"/> +<path d="M21.294 68.0212C21.014 67.3712 20.594 66.8612 20.694 66.4912C20.804 66.1112 21.414 65.8712 21.804 65.5712C22.084 65.9912 22.614 66.4612 22.554 66.8312C22.494 67.2112 21.864 67.5112 21.284 68.0112L21.294 68.0212Z" fill="white"/> +<path d="M65.674 2.81122C66.094 3.28122 66.614 3.68122 66.554 3.83122C66.374 4.25122 65.994 4.65122 65.594 4.88122C65.474 4.95122 64.734 4.35122 64.774 4.17122C64.874 3.75122 65.264 3.39122 65.674 2.81122Z" fill="white"/> </svg> diff --git a/frontend/app/svg/ca-no-cards.svg b/frontend/app/svg/ca-no-cards.svg new file mode 100644 index 000000000..d5d30a58c --- /dev/null +++ b/frontend/app/svg/ca-no-cards.svg @@ -0,0 +1,58 @@ +<svg width="214" height="197" viewBox="0 0 214 197" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_9_498)"> +<rect width="214" height="197" fill="white"/> +<circle cx="101.718" cy="91.953" r="84.3749" fill="#FDF9F3"/> +<path d="M0.911954 193.085C4.53119 191.711 5.55262 191.86 10.4004 190.729C20.7555 188.313 21.7552 188.834 34.3794 187.681C62.642 185.1 93.3515 184.802 122.505 186.164C141.628 187.058 159.578 188.814 177.444 190.915C185.846 191.904 195.761 193.304 207.808 194.753" stroke="black" stroke-linecap="round"/> +<path d="M189.398 36.3112C188.927 36.6835 188.48 37.1138 187.967 37.4199C182.332 40.7379 178.501 45.5287 176.275 51.6517C175.531 53.6954 175.2 55.7806 175.39 57.9567C175.556 59.8598 175.589 61.7794 175.804 63.6743C176.052 65.8256 175.531 67.7535 174.53 69.6235C172.37 73.6696 169.019 76.5408 165.262 78.9817C161.423 81.4806 158.163 84.5255 155.689 88.3979C152.925 92.7253 152.081 97.4252 152.917 102.456C153.471 105.782 154.1 109.084 155.631 112.137C155.97 112.815 156.301 113.494 156.665 114.222C159.826 113.999 161.498 115.869 162.648 118.376C164.319 122.025 164.89 125.641 162.432 129.273C161.382 130.828 160.025 131.755 158.295 132.334C156.351 132.98 156.367 133.021 156.533 134.866C154.936 136.306 153.447 137.696 151.916 139.028C148.283 142.173 147.059 146.252 147.828 150.811C148.722 156.115 147.34 160.649 143.435 164.116C139.207 167.856 137.072 172.407 136.584 177.893C136.567 178.033 136.493 178.182 136.418 178.315C136.402 178.356 136.302 178.348 136.004 178.431C135.93 177.868 135.748 177.305 135.806 176.767C136.335 171.985 137.982 167.715 141.738 164.488C144.825 161.841 146.802 158.589 147.282 154.518C147.382 153.69 147.398 152.822 147.266 152.011C146.455 147.154 147.257 142.727 150.584 138.772C150.013 139.053 149.442 139.343 148.863 139.616C144.568 141.626 140.299 143.679 135.971 145.598C134.78 146.128 134.06 146.707 134.076 148.072C134.093 149.065 133.928 150.058 133.936 151.043C133.961 155.163 132.951 158.986 131.197 162.742C127.879 169.834 122.691 174.74 115.401 177.52C115.004 177.669 114.715 178.108 114.218 178.546C116.03 179.159 117.577 179.523 118.976 180.193C121.45 181.376 121.4 181.691 120.631 184.281C119.712 187.359 117.371 189.071 114.822 190.453C109.187 193.523 103.155 194.408 96.8338 193.192C96.6849 193.167 96.5277 193.134 96.387 193.084C94.7652 192.48 93.1766 191.554 91.5052 191.363C89.8917 191.173 88.2037 191.794 86.5406 191.976C82.7592 192.389 79.2426 191.711 76.1563 189.32C73.7485 187.45 72.466 185.1 72.9294 181.98C73.2107 180.052 72.1185 178.075 70.2154 177.347C64.9198 175.311 60.6337 171.968 57.3819 167.343C53.8571 162.337 52.045 156.777 52.0864 150.596C52.136 143.182 52.0367 135.768 52.0285 128.354C52.0285 124.995 52.1195 121.627 52.136 118.268C52.136 117.209 52.136 116.133 51.9374 115.099C50.324 106.535 51.5072 98.3022 55.1727 90.483C60.4434 79.2465 68.6598 71.1129 80.7072 67.2736C82.6434 66.6613 84.6458 66.2807 86.946 65.7098C83.446 64.2617 79.946 63.0289 80.0122 58.4035C81.4105 58.9165 82.6351 59.3716 83.868 59.8267C83.9756 59.7109 84.0748 59.595 84.1824 59.4792C81.758 57.1458 80.3349 54.4981 81.1126 51.0559C88.2865 52.8515 93.2841 56.7487 95.2121 63.9721C95.3445 64.4769 95.5017 64.9816 95.6671 65.5443C99.8622 65.8504 103.933 66.5041 107.781 68.0927C121.864 73.9096 130.212 84.6083 133.795 99.1628C135.276 105.17 135.409 111.276 133.448 117.283C133.332 117.639 133.232 118.045 133.29 118.401C133.572 120.188 133.911 121.967 134.258 123.886C140.564 122.223 146.504 119.915 152.545 116.762C152.76 117.25 152.967 117.722 153.19 118.218C154.233 117.06 155.044 116.158 155.987 115.107C155.813 114.785 155.59 114.338 155.35 113.899C153.091 109.721 151.982 105.195 151.593 100.52C151.072 94.2147 153.339 88.8281 157.518 84.178C159.578 81.886 161.986 80.0326 164.485 78.2453C166.834 76.5656 169.16 74.7618 171.154 72.685C174.017 69.7062 175.456 66.1814 174.778 61.8208C173.28 52.1482 177.426 44.7261 184.699 38.7852C186.031 37.693 187.719 37.0393 189.25 36.187C189.307 36.2449 189.357 36.3111 189.415 36.3691L189.398 36.3112ZM132.86 117.259C132.215 117.54 131.859 117.656 131.545 117.838C127.722 120.022 123.552 121.272 119.34 122.389C113.109 124.044 106.705 124.557 100.309 124.987C98.8693 125.086 97.4213 124.921 95.9733 124.929C92.8622 124.954 89.751 125.111 86.6482 125.036C84.29 124.979 81.9401 124.606 79.5819 124.424C70.6622 123.737 62.0569 121.892 54.1053 117.606C53.8654 117.474 53.5427 117.499 53.22 117.449C53.071 122.67 52.798 127.775 52.798 132.872C52.798 138.987 52.98 145.102 53.1703 151.208C53.2282 153.111 53.3193 155.056 53.7661 156.893C56.3063 167.459 63.2401 173.863 73.3513 177.107C74.8655 177.595 76.479 177.893 78.0594 177.992C81.7167 178.215 85.3905 178.29 89.056 178.373C91.4142 178.431 93.7806 178.464 96.147 178.406C100.193 178.315 104.248 178.158 108.294 178.017C108.674 178 109.063 177.984 109.436 177.909C126.083 174.442 133.621 162.37 133.017 146.798C132.256 147.03 131.553 147.261 130.841 147.46C127.896 148.279 124.983 149.206 122.004 149.859C118.099 150.72 114.135 151.109 110.189 150.075C106.3 149.057 104.512 146.119 105.497 142.239C105.902 140.625 106.73 139.119 107.293 137.539C107.83 136.016 108.774 134.833 110.172 134.113C112.745 132.781 115.327 131.457 117.991 130.324C122.898 128.23 127.862 126.269 132.877 124.226V117.234L132.86 117.259ZM119.53 94.2478C108.724 95.8282 98.1577 97.6899 87.4094 96.8459C76.7686 96.0102 66.2189 94.5043 55.6692 91.6662C55.0072 93.9334 54.3122 96.0351 53.7826 98.1698C52.4918 103.341 52.3263 108.604 52.6987 113.899C52.7897 115.182 53.0628 116.233 54.4777 116.762C56.0415 117.358 57.4978 118.243 59.0451 118.889C67.4187 122.347 76.3135 123.431 85.225 124.176C95.0217 124.987 104.777 124.118 114.45 122.48C116.601 122.116 118.711 121.495 120.887 120.974C120.995 111.897 121.226 103.068 119.53 94.2478V94.2478ZM115.137 72.8422C114.723 75.6472 112.936 77.0455 110.809 78.0716C109.369 78.7666 107.888 79.4451 106.349 79.834C103.61 80.5125 100.847 81.1744 98.0501 81.4723C91.3314 82.1756 84.6044 82.0929 78.0346 80.2642C74.4353 79.2713 70.8691 78.0798 69.2142 74.0006C63.2981 78.4522 59.1195 83.8967 56.2484 90.5988C57.0675 90.8636 57.5557 91.0705 58.0687 91.1781C64.1255 92.3944 70.174 93.6934 76.2556 94.7774C83.3384 96.0351 90.504 96.6143 97.7026 96.1592C105.257 95.6875 112.72 94.5126 120.085 92.7253C122.956 92.0303 125.223 90.4913 127.25 88.4475C128.011 87.678 128.127 86.925 127.565 85.9901C124.453 80.8269 120.581 76.3836 115.137 72.8505V72.8422ZM152.023 117.945C147.175 120.858 142.111 122.538 137.063 124.325C130.154 126.766 123.336 129.463 116.51 132.111C114.946 132.715 113.482 133.567 111.968 134.295C109.22 135.611 107.756 137.936 106.829 140.708C105.1 145.879 107.226 149.131 112.654 149.57C113.035 149.603 113.424 149.644 113.796 149.603C116.378 149.355 119.001 149.322 121.524 148.8C133.175 146.376 143.774 141.312 154.084 135.553C155.308 134.866 155.581 133.956 155.49 132.732C155.101 127.75 153.827 122.976 152.032 117.954L152.023 117.945ZM95.9154 66.6282C95.5678 68.2913 95.3858 69.7393 94.9473 71.1129C94.426 72.7512 93.1269 72.9084 92.0761 71.5266C91.803 71.1708 91.6955 70.6991 91.4886 70.2937C91.0832 69.4745 90.686 68.6471 90.2309 67.8611C89.6269 66.8268 88.7333 66.4958 87.517 66.653C81.9897 67.3729 76.7935 69.036 72.0606 72.023C70.265 73.1566 70.0582 74.3812 71.5227 75.8871C72.2012 76.5822 73.0369 77.211 73.914 77.6082C75.4365 78.3032 77.0086 78.9321 78.6304 79.3458C86.2262 81.2654 93.9213 81.3234 101.592 79.9333C104.496 79.4037 107.359 78.4687 110.114 77.3931C111.463 76.8635 112.696 75.7548 113.672 74.6294C114.756 73.38 114.466 72.1637 112.96 71.4521C110.139 70.1282 107.268 68.854 104.322 67.8528C101.666 66.9426 98.861 66.4875 95.9071 66.6199L95.9154 66.6282ZM120.631 94.3967C122.567 102.969 121.64 111.74 121.955 120.577C125.604 119.526 128.855 118.276 131.942 116.572C132.753 116.125 133.183 115.612 133.274 114.652C134.043 106.535 133.117 98.658 130.063 91.0705C129.757 90.3175 129.36 89.6059 129.071 89.0019C126.249 90.8057 123.568 92.5185 120.631 94.3967V94.3967ZM92.2747 183.511C92.2747 185.257 92.1919 186.928 92.3161 188.575C92.3657 189.187 92.7794 189.973 93.2759 190.304C94.4012 191.057 95.6258 191.86 96.9166 192.149C103.164 193.564 109.088 192.431 114.59 189.27C117.354 187.681 119.613 185.546 120.143 182.08C118.744 180.871 117.139 180.259 115.36 180.052C114.367 179.936 113.54 180.152 113.184 181.244C112.936 182.005 112.505 182.51 111.612 182.7C107.268 183.619 102.907 184.429 98.439 184.098C96.4698 183.95 94.5087 183.726 92.2747 183.511V183.511ZM91.0253 183.097C85.0264 184.264 79.6233 182.998 74.2698 181.426C73.5499 184.628 74.6008 187.127 77.0748 188.831C81.4436 191.835 86.16 191.33 90.9508 190.403C91.0666 190.379 91.1411 190.18 91.0335 190.312V183.097H91.0253ZM81.0547 60.0418C81.8408 62.2842 83.5122 63.335 85.5973 63.7322C87.6742 64.1293 89.2959 65.1554 90.504 66.8185C91.2569 67.8528 91.77 69.0525 92.4319 70.1613C92.7049 70.6164 93.069 71.0053 93.5241 71.5928C95.8657 64.8989 91.9106 53.894 81.758 52.5784C81.667 54.2498 82.0559 55.8219 83.1068 57.0548C84.0914 58.2132 85.3656 59.1234 86.5075 60.1411C86.4413 60.2818 86.3668 60.4307 86.3006 60.5714C84.6044 60.4059 82.9082 60.2322 81.0547 60.0501V60.0418ZM156.88 116.2C154.34 117.085 153.463 119.187 154.216 121.694C155.01 124.317 155.614 126.998 156.301 129.654C156.483 130.349 156.682 131.044 156.864 131.714C159.727 131.217 160.513 130.704 161.721 128.719C163.955 125.02 163.252 119.402 160.132 116.415C159.619 115.918 158.924 115.612 158.271 115.19C157.451 116.291 158.163 117.904 156.508 118.508C156.64 117.672 156.756 116.986 156.88 116.216V116.2ZM112.696 178.629C105.745 179.002 98.7783 179.374 91.803 179.746C91.4307 181.79 91.4555 181.939 93.0938 182.46C94.1033 182.775 95.1707 182.998 96.2215 183.048C100.648 183.271 105.058 183.139 109.419 182.204C112.017 181.649 112.315 181.393 112.687 178.621L112.696 178.629ZM73.07 178.505C74.096 179.771 75.0889 180.929 76.7769 181.36C80.6576 182.344 84.6044 182.369 88.5595 182.27C89.0725 182.253 89.5855 182.08 90.2806 181.939C90.2061 181.153 90.1316 180.433 90.0572 179.68C84.3396 179.283 78.8124 178.902 73.07 178.505V178.505Z" fill="black"/> +<path d="M21.7943 171.207C23.3995 171.282 24.5248 171.29 25.6418 171.406C26.883 171.538 27.4787 172.407 27.7518 173.524C28 174.517 28.1903 175.526 28.8274 176.478C28.8605 176.089 28.9598 175.692 28.9019 175.311C28.6868 173.722 28.3889 172.142 28.182 170.553C27.8676 168.154 28.0248 167.988 30.5236 167.856C32.2613 167.757 32.6584 167.98 33.1135 169.709C33.5355 171.331 33.8003 172.994 34.1395 174.641C34.2471 175.154 34.3712 175.659 34.5284 176.337C35.3724 176.379 36.1667 176.42 37.1017 176.461C37.2093 173.185 35.24 170.413 35.8854 167.186C37.4575 166.648 39.071 166.921 40.6183 167.087C43.9611 167.434 47.2957 167.881 50.5971 168.477C51.9706 168.725 53.2614 169.436 54.9577 170.082C52.7484 172.531 50.8371 174.649 48.6526 177.074C49.6704 177.305 50.3737 177.438 51.0605 177.611C53.4186 178.215 55.8513 178.646 58.1267 179.498C61.6847 180.83 62.7024 184.992 60.187 187.896C59.0121 189.253 57.5641 190.503 56.0002 191.38C52.947 193.101 49.7531 194.359 46.0545 194.284C41.0237 194.185 35.9847 194.532 30.9456 194.681C26.5851 194.805 22.4976 193.597 18.4514 192.149C16.4408 191.429 14.3722 190.784 12.8001 189.245C11.8816 188.343 10.9384 187.416 10.2185 186.357C8.90289 184.421 9.44073 182.005 11.4762 180.83C13.0483 179.92 14.7694 179.266 16.598 178.422C16.5401 177.942 16.4904 177.355 16.4077 176.776C16.1926 175.278 16.4077 174.922 17.8391 174.418C19.5767 173.805 20.2387 173.954 20.9337 175.245C21.3144 175.957 21.5874 176.734 21.9515 177.587C23.6146 176.767 22.4893 175.642 22.4479 174.765C22.4066 173.731 22.0839 172.713 21.786 171.224L21.7943 171.207ZM11.1949 185.729C11.2859 185.968 11.3107 186.2 11.4348 186.357C12.9656 188.318 14.8521 189.758 17.1937 190.726C22.5803 192.944 28.1407 194.052 33.9575 193.846C38.2353 193.688 42.5048 193.316 46.7827 193.324C50.1503 193.324 53.0628 192.116 55.843 190.536C57.2745 189.725 58.4411 188.442 59.6492 187.284C60.2449 186.713 60.6173 185.944 60.1622 185.042C46.9233 190.718 25.9397 191.016 11.2032 185.729H11.1949ZM36.5804 188.31C39.2944 188.749 39.5178 188.649 40.4942 186.44C40.9244 185.464 41.2389 184.438 41.6691 183.461C41.9091 182.915 42.1904 182.344 42.5959 181.922C45.6987 178.662 48.8347 175.444 51.9458 172.2C52.2354 171.894 52.4588 171.53 52.856 170.992C47.3701 170.404 42.2318 169.238 36.7707 168.907C36.9693 170.148 37.0521 171.058 37.2589 171.935C37.954 174.815 38.7069 177.686 39.3854 180.565C39.493 181.029 39.4351 181.6 39.253 182.046C38.4008 184.148 37.4823 186.217 36.5804 188.31V188.31ZM41.0734 188.111C41.363 188.178 41.5781 188.277 41.7933 188.269C46.46 188.045 51.0687 187.433 55.5369 186.043C56.9021 185.621 58.2591 184.992 59.4506 184.206C60.9896 183.205 60.8903 182.022 59.3513 180.971C58.979 180.714 58.5487 180.541 58.135 180.342C55.2804 178.968 52.2023 178.439 49.0995 177.992C48.1645 177.86 47.5191 178.182 46.8654 178.819C44.2424 181.401 41.8512 184.132 41.0651 188.103L41.0734 188.111ZM30.4078 185.422C30.2754 182.758 29.4397 178.803 28.604 177.529C28.7529 178.968 28.844 180.003 28.9598 181.037C29.0674 182.022 29.2908 183.015 29.2577 183.991C29.2411 184.479 28.8522 185.249 28.4633 185.373C27.4125 185.712 26.2707 185.778 25.1205 185.968C24.7482 184.479 24.4503 183.155 24.078 181.856C23.7387 180.656 24.0283 179.167 22.2328 177.951C22.3404 179.059 22.3486 179.688 22.4728 180.301C22.7458 181.724 23.1181 183.13 23.3664 184.554C23.5898 185.877 23.3333 186.208 22.0342 186.366C21.0578 186.49 20.0815 186.515 19.0637 186.589C18.3522 184.214 17.6571 181.906 16.9372 179.531C14.9018 179.928 13.0731 180.458 11.5507 181.674C10.2102 182.75 10.1937 183.941 11.6417 184.81C12.9325 185.588 14.3722 186.233 15.8285 186.589C22.2411 188.161 28.7695 188.658 35.5048 188.567C36.2412 186.804 36.8949 185.025 37.7306 183.337C38.7897 181.186 38.3925 179.241 37.1927 177.206C36.3736 177.256 35.571 177.305 34.6443 177.363C34.7601 178.282 34.8511 178.952 34.9339 179.63C35.0828 180.996 35.2814 182.361 35.3558 183.726C35.422 185.042 35.0993 185.356 33.8003 185.406C32.675 185.447 31.5414 185.414 30.4161 185.414L30.4078 185.422ZM34.6277 184.338C34.1147 178.844 33.4941 173.665 31.8393 168.609C31.1773 168.683 30.805 168.717 30.4409 168.774C30.0106 168.849 29.5887 168.956 28.9764 169.089C29.7376 174.26 30.474 179.283 31.2187 184.33H34.636L34.6277 184.338ZM28.604 184.338C28.0827 180.847 27.6525 177.62 27.1064 174.409C26.7837 172.548 26.2458 172.208 24.3014 172.208C23.8628 172.208 23.416 172.266 22.7375 172.316C23.7139 176.602 24.6654 180.739 25.6253 184.959C26.734 184.727 27.5284 184.562 28.604 184.338V184.338ZM17.1524 175.75C17.5495 179.059 18.4845 182.278 19.6429 185.571C20.7186 185.464 21.5957 185.373 22.7458 185.257C22.0425 182.32 21.455 179.581 20.7269 176.883C20.1063 174.591 19.6347 174.467 17.1524 175.758V175.75ZM52.161 170.206C52.1858 170.049 52.2106 169.892 52.2354 169.734C47.5356 168.435 42.6951 168.104 37.8712 167.6C37.8464 167.782 37.8133 167.964 37.7885 168.137C42.5793 168.824 47.3701 169.511 52.161 170.198V170.206Z" fill="black"/> +<path d="M212.045 13.3003C209.207 16.3618 206.344 19.4067 203.531 22.5096C201.438 24.8181 199.427 27.1929 197.309 29.609C198.558 24.2389 199.783 18.9848 201.007 13.7306C200.635 14.6407 200.188 15.5261 199.923 16.4611C198.972 19.8453 198.087 23.246 197.168 26.6302C196.986 27.3004 196.87 28.0451 196.068 28.5829C195.364 25.2318 194.719 22.1042 194.04 18.9848C193.387 15.9812 192.534 11.7282 191.856 8.69152C191.839 8.62533 191.831 8.55913 191.815 8.50121C191.798 8.55085 191.781 8.6005 191.773 8.65842C191.508 9.69271 191.69 11.7447 191.864 12.5804C193.031 18.2566 194.264 23.9328 195.397 29.6255C195.571 30.4778 195.778 31.2473 196.192 31.9506C196.399 32.3312 196.663 32.6787 197.011 33.018C197.996 33.9778 198.74 35.1528 198.293 36.8821C197.185 36.9896 196.101 37.0972 194.719 37.2379C195.439 38.4045 196.018 39.3395 196.672 40.3904C197.317 39.9105 197.78 39.5629 198.492 39.0334C198.144 41.5653 197.077 43.4519 195.704 45.1895C195.563 45.3632 195.199 45.3549 194.917 45.4211C194.909 44.5854 194.735 43.8242 193.627 43.2036C193.122 44.6268 192.675 45.901 192.228 47.1753C192.923 47.407 193.312 47.5311 193.527 47.6056C192.427 49.7817 191.368 51.8503 190.003 54.5312C189.539 53.6044 189.142 52.8101 188.745 52.0158C188.555 52.1399 188.364 52.2557 188.174 52.3798C187.719 54.0347 187.016 55.673 187.901 57.5927C188.828 57.1624 189.614 56.79 190.416 56.4094C191.856 60.1742 192.013 60.4059 193.536 61.1837C192.981 60.0005 192.443 58.8421 191.914 57.6754C191.897 57.6257 192.063 57.4934 192.146 57.4023C192.675 57.7664 193.196 58.1305 193.908 58.6269C194.711 56.608 195.033 54.7546 194.727 52.5619C193.651 53.108 192.783 53.5465 191.666 54.1009C192.195 51.4118 193.296 49.4838 195.116 47.8372C195.414 48.4247 195.67 48.946 195.977 49.5418C197.4 48.6895 197.995 47.4732 198.442 46.05C197.813 45.8762 197.342 45.7521 197.044 45.6694C198.037 43.1374 198.997 40.6965 199.973 38.2059C200.321 38.2308 200.817 38.2639 201.62 38.3052C201.305 37.0062 201.049 35.9305 200.776 34.83C200.188 34.8631 199.766 34.888 199.427 34.9045C198.55 33.6799 197.722 32.5298 196.837 31.2969C201.131 26.4482 205.409 21.6656 209.613 16.8334C210.548 15.7578 211.243 14.4835 212.045 13.3003V13.3003ZM196.928 39.5878C196.548 38.9424 196.291 38.5038 195.91 37.8502C196.73 37.8005 197.284 37.7757 198.07 37.726C197.665 38.3963 197.375 38.8679 196.928 39.5878ZM188.356 56.517C188.439 55.4661 188.488 54.9118 188.555 54.1092C189.159 54.5063 189.564 54.7794 190.16 55.1766C189.622 55.5737 189.2 55.8881 188.356 56.517ZM193.858 54.0678C193.776 55.2924 193.718 56.1612 193.66 57.0383C193.486 57.121 193.312 57.2038 193.139 57.2948C192.675 56.7652 192.212 56.2357 191.591 55.5323C192.394 55.0111 192.99 54.6222 193.858 54.0678ZM193.139 46.5795C193.387 46.0003 193.61 45.4625 193.85 44.9081C194.28 45.1398 194.57 45.297 194.86 45.446C194.678 46.2237 194.313 46.7864 193.139 46.5795V46.5795Z" fill="black"/> +<path d="M212.5 12.8287C212.352 12.9859 212.194 13.1431 212.045 13.3003C209.207 16.3618 206.344 19.4067 203.531 22.5096C201.438 24.8181 199.427 27.1929 197.309 29.609C198.558 24.2389 199.783 18.9848 201.007 13.7306C201.082 13.3582 201.165 12.9859 201.247 12.6053C201.661 11.993 201.984 11.4055 202.985 11.5379C205.534 11.8523 208.099 11.993 210.647 12.266C211.276 12.3322 211.88 12.6301 212.5 12.8287V12.8287Z" fill="#FDF9F3"/> +<path d="M152.205 178.249C152.909 177.636 153.331 176.478 154.605 176.908C155.656 177.264 156.202 178.058 156.31 179.142C156.384 179.895 156.417 180.665 156.409 181.418C156.401 182.328 156.318 183.246 156.252 184.471C157.575 184.554 158.817 184.628 160.314 184.719C160.314 185.679 160.455 186.572 160.281 187.408C159.942 189.063 159.503 189.361 157.807 189.518C157.278 189.568 156.74 189.584 156.202 189.617C155.755 189.642 155.308 189.684 154.779 189.717C154.357 190.751 154.034 191.769 153.546 192.704C152.95 193.846 152.313 194.996 151.51 195.997C150.476 197.279 149.467 197.255 148.135 196.361C147.555 195.972 146.819 195.658 146.124 195.6C140.712 195.194 135.293 194.863 129.881 194.491C127.498 194.326 127.217 194.044 126.787 191.694C126.663 191.032 126.497 190.37 126.34 189.642H121.516C120.689 188.509 121.135 187.342 121.442 186.299C121.566 185.877 122.468 185.538 123.063 185.422C124.321 185.182 125.612 185.116 126.82 184.992C127.523 183.089 128.152 181.384 128.797 179.639C131.553 179.407 134.134 179.109 136.716 178.985C141.3 178.762 145.884 178.637 150.468 178.455C151.064 178.431 151.659 178.265 152.255 178.174C151.32 181.161 150.286 184.115 149.491 187.135C148.954 189.187 148.697 191.33 148.449 193.448C148.341 194.375 148.118 195.658 149.285 196.063C150.609 196.526 151.097 195.227 151.684 194.4C152.652 193.026 153.248 191.487 153.546 189.692C152.694 189.593 151.957 189.51 151.171 189.419C150.501 188.145 151.006 186.961 151.469 185.877C152.164 184.231 154.067 185.48 155.292 184.405C155.424 183.37 155.656 182.171 155.722 180.963C155.879 177.86 155.044 177.239 152.197 178.249H152.205ZM147.481 194.963C147.762 189.46 149.334 184.405 151.337 179.208C150.518 179.208 150.071 179.2 149.624 179.208C146.265 179.308 142.897 179.382 139.538 179.531C136.716 179.655 133.894 179.92 131.065 180.036C129.526 180.102 128.715 180.83 128.334 182.286C127.573 185.182 126.977 188.087 127.258 191.107C127.482 193.498 127.614 193.73 129.939 193.92C133.365 194.201 136.799 194.367 140.233 194.574C142.582 194.714 144.924 194.822 147.464 194.954L147.481 194.963ZM151.585 188.699C153.637 188.79 155.292 188.939 156.938 188.931C159.288 188.914 159.801 188.211 159.545 185.364C157.112 185.505 154.663 185.646 152.106 185.795C151.924 186.804 151.783 187.59 151.577 188.699H151.585ZM121.855 188.823C125.637 189.46 127.407 188.848 126.58 185.695C125.124 185.886 123.643 186.076 122.079 186.275C121.996 187.243 121.922 187.987 121.847 188.823H121.855Z" fill="black"/> +<path d="M145.073 46.894C149.037 45.6197 150.907 46.2403 153.447 49.5831C154.682 51.2104 155.298 52.7439 155.292 54.1836C142.699 56.0288 129.956 55.7144 117.313 56.5501C115.989 50.8243 119.58 47.0595 125.455 48.0276C125.711 47.3904 126.067 46.7368 126.266 46.0335C127.499 41.5322 131.661 39.3892 136.154 39.4306C140.258 39.4719 143.493 42.1362 144.808 46.3479C144.85 46.4885 144.941 46.6127 145.073 46.8857V46.894ZM125.984 48.9047C123.783 48.6895 121.541 47.7628 119.82 49.6576C118.298 51.3373 117.412 53.3148 117.917 55.8964C130.072 55.1517 142.152 54.4153 154.498 53.6706C154.2 51.1387 152.669 49.5583 151.163 48.2096C149.078 46.3396 146.67 47.0595 144.353 48.4827C143.998 47.1588 143.849 46.0086 143.394 44.9992C141.896 41.706 139.099 40.2911 135.649 40.3159C131.983 40.3407 128.922 41.6729 127.333 45.2805C126.82 46.4555 126.431 47.68 125.976 48.9129L125.984 48.9047Z" fill="black"/> +<path d="M47.726 78.1874C51.4825 79.0396 52.8974 81.0255 51.8135 83.9794C49.8773 83.9794 47.8997 83.9546 45.9139 83.9794C40.337 84.0621 34.7519 84.2111 29.175 84.2525C26.1962 84.269 23.2175 84.0704 20.2387 84.0622C19.1382 84.0622 18.5839 83.516 18.1205 82.6555C16.6229 79.8505 17.3096 76.0361 19.8002 73.8682C22.9444 71.1377 26.4941 70.815 30.5485 72.2961C33.4859 70.1779 36.8701 69.8883 40.3784 71.2535C43.7047 72.5443 46.3524 74.6046 47.726 78.1709V78.1874ZM30.4492 73.5703C28.3145 72.0561 26.1135 72.0809 23.9125 72.594C19.6347 73.6034 17.2103 77.815 18.5177 81.704C18.9148 82.8872 19.5271 83.4995 20.8345 83.3919C22.0508 83.2926 23.2837 83.3092 24.5 83.3009C32.9894 83.2595 41.4706 83.2347 49.96 83.1933C50.3986 83.1933 50.8454 83.0609 51.3418 82.9865C51.3667 79.8091 49.1988 79.2217 46.8489 79.0479C45.8891 75.2748 43.2413 73.3552 40.0143 72.0396C36.6053 70.6495 33.428 71.055 30.4409 73.5703H30.4492Z" fill="black"/> +<path d="M152.206 178.249C155.052 177.239 155.888 177.86 155.731 180.963C155.673 182.171 155.433 183.37 155.3 184.405C154.076 185.48 152.181 184.231 151.478 185.877C151.014 186.961 150.509 188.145 151.18 189.419C151.966 189.51 152.702 189.593 153.554 189.692C153.257 191.487 152.661 193.026 151.693 194.4C151.105 195.236 150.609 196.535 149.293 196.063C148.126 195.649 148.35 194.375 148.457 193.448C148.697 191.33 148.962 189.195 149.5 187.135C150.286 184.115 151.329 181.161 152.264 178.174L152.206 178.24V178.249Z" fill="white"/> +<path d="M109.179 38.5866C99.7217 40.3986 90.2558 39.1327 80.9307 39.8525C79.8137 36.5759 80.6577 35.1941 84.2156 34.2674C85.0017 30.3454 87.6246 28.1196 91.4639 27.4824C95.4025 26.8288 99.8209 28.8643 101.997 31.992C106.962 30.635 110.445 33.6965 109.179 38.5866ZM85.1423 35.1279C81.7333 35.7071 81.27 36.1622 81.3941 38.992C90.3386 38.2142 99.3493 39.3395 108.244 37.9743C109.287 36.4932 108.625 35.2024 107.748 34.1681C106.043 32.174 103.884 32.0333 101.633 33.291C100.955 32.2815 100.566 31.1231 99.7547 30.5853C96.8836 28.6409 93.6649 27.5817 90.2393 28.6905C87.2109 29.6669 85.1341 31.6941 85.1423 35.1279V35.1279Z" fill="black"/> +<path d="M197.764 4.98463C198.161 4.77777 198.55 4.56263 198.947 4.35577C199.576 3.8014 200.205 3.24703 200.834 2.69266L200.776 2.7423C201.628 2.3865 202.472 2.0307 203.515 1.59216C203.515 2.63472 203.515 3.50354 203.515 4.37234C203.101 5.91136 202.679 7.45037 202.265 8.98939C201.959 9.49412 201.653 10.0071 201.289 10.6194C198.476 10.0402 195.712 9.52723 193.163 8.35228C194.777 7.17733 196.275 6.07684 197.772 4.98463H197.764Z" fill="#FDF9F3"/> +<path d="M201.247 12.6053C201.165 12.9859 201.082 13.3582 201.007 13.7306C200.635 14.6407 200.188 15.5261 199.924 16.4611C198.972 19.8453 198.087 23.246 197.168 26.6302C196.986 27.3004 196.87 28.0451 196.068 28.5829C195.364 25.2318 194.719 22.1042 194.041 18.9848C193.387 15.9812 192.535 11.7282 191.856 8.69153C192.278 8.86529 192.684 9.04733 193.106 9.16317C195.306 9.80029 197.507 10.4291 199.725 11.0083C200.61 11.24 201.355 11.48 201.247 12.6053V12.6053Z" fill="#FDF9F3"/> +<path d="M212.5 12.8287C211.88 12.6301 211.276 12.3322 210.647 12.266C208.098 11.993 205.533 11.8523 202.985 11.5379C201.984 11.4055 201.661 11.993 201.247 12.6053C201.355 11.48 200.61 11.24 199.725 11.0083C197.507 10.4291 195.306 9.80029 193.105 9.16317C192.683 9.04733 192.278 8.86529 191.856 8.69153C191.831 8.69153 191.798 8.6667 191.773 8.65842C191.724 8.64187 191.674 8.61704 191.624 8.6005C192.394 7.50829 195.687 5.58865 197.764 4.97635C196.266 6.06856 194.769 7.16077 193.155 8.34399C195.704 9.51895 198.467 10.032 201.28 10.6112C201.644 9.9906 201.951 9.48586 202.257 8.98113V10.9504C205.715 11.2483 208.959 11.5213 212.699 11.844C210.928 9.81683 209.546 8.21161 208.14 6.63949C206.775 5.11702 205.873 3.1891 204.267 1.66663C203.953 2.77538 203.73 3.56972 203.506 4.36406V1.5839C202.472 2.01417 201.62 2.36995 200.767 2.73402C201.628 1.52597 202.786 0.756454 204.441 0.193802C204.615 0.45858 204.805 0.789558 205.037 1.07916C207.676 4.31441 210.316 7.54138 212.964 10.7684C213.907 11.9102 213.882 12.0343 212.5 12.8287V12.8287Z" fill="black"/> +<path d="M189.233 36.1291C190.152 35.5334 191.078 34.9376 191.997 34.3501C192.096 34.5073 192.195 34.6646 192.286 34.8218C191.318 35.3182 190.358 35.8147 189.39 36.3112C189.332 36.2532 189.283 36.187 189.225 36.1291H189.233Z" fill="black"/> +<path d="M191.881 34.7721C193.031 34.4494 194.057 33.8206 195.075 33.1917C195.373 33.0097 195.679 32.8194 195.886 32.5463C196.117 32.2402 196.225 31.8596 196.316 31.4872" stroke="black" stroke-width="0.413715" stroke-miterlimit="10"/> +<path d="M200.825 2.68438C200.197 3.23875 199.568 3.79314 198.939 4.34752C199.568 3.79314 200.197 3.23875 200.825 2.68438Z" fill="black"/> +<path d="M132.86 117.259V124.25C127.838 126.294 122.873 128.255 117.975 130.349C115.311 131.482 112.729 132.806 110.156 134.138C108.757 134.858 107.814 136.05 107.276 137.564C106.714 139.144 105.894 140.65 105.481 142.264C104.496 146.153 106.283 149.082 110.172 150.099C114.119 151.134 118.082 150.745 121.988 149.884C124.967 149.231 127.887 148.304 130.825 147.485C131.536 147.286 132.24 147.054 133.001 146.823C133.605 162.395 126.067 174.467 109.419 177.934C109.047 178.009 108.658 178.025 108.277 178.042C104.231 178.174 100.185 178.34 96.1307 178.431C93.7725 178.48 91.4061 178.455 89.0396 178.397C85.3741 178.306 81.7003 178.24 78.0431 178.017C76.4627 177.918 74.8492 177.62 73.335 177.132C63.2238 173.888 56.2899 167.484 53.7497 156.917C53.3112 155.08 53.2119 153.136 53.154 151.233C52.9719 145.118 52.7816 139.012 52.7816 132.897C52.7816 127.792 53.0464 122.687 53.2036 117.474C53.5263 117.523 53.849 117.507 54.089 117.631C62.0488 121.917 70.6458 123.762 79.5655 124.449C81.9237 124.631 84.2736 125.003 86.6318 125.061C89.7347 125.136 92.8458 124.979 95.9569 124.954C97.4049 124.945 98.8529 125.103 100.293 125.012C106.697 124.581 113.093 124.068 119.324 122.414C123.535 121.297 127.705 120.047 131.528 117.863C131.843 117.681 132.198 117.565 132.844 117.283L132.86 117.259Z" fill="white"/> +<path d="M119.531 94.2478C121.227 103.06 120.995 111.897 120.887 120.974C118.711 121.487 116.601 122.116 114.45 122.48C104.769 124.126 95.0137 124.995 85.2252 124.176C76.3221 123.44 67.4272 122.356 59.0453 118.889C57.498 118.252 56.0418 117.358 54.4779 116.762C53.063 116.224 52.79 115.182 52.6989 113.899C52.3266 108.604 52.4921 103.35 53.7829 98.1698C54.3124 96.0351 55.0075 93.9334 55.6694 91.6662C66.2192 94.5043 76.7689 96.0185 87.4096 96.8459C98.1497 97.6899 108.724 95.8282 119.531 94.2478V94.2478Z" fill="white"/> +<path d="M115.137 72.8422C120.573 76.3836 124.453 80.8186 127.565 85.9818C128.127 86.9168 128.011 87.6698 127.25 88.4393C125.223 90.483 122.956 92.022 120.085 92.7171C112.721 94.5043 105.257 95.6793 97.7027 96.1509C90.5123 96.606 83.3468 96.0268 76.2557 94.7691C70.1741 93.6934 64.1255 92.3861 58.0688 91.1698C57.5558 91.0705 57.0593 90.8553 56.2484 90.5906C59.1113 83.8801 63.2981 78.4356 69.2142 73.9923C70.8691 78.0633 74.4353 79.2548 78.0346 80.256C84.6044 82.0763 91.3315 82.159 98.0502 81.464C100.847 81.1744 103.619 80.5125 106.349 79.8257C107.888 79.4451 109.369 78.7583 110.809 78.0633C112.936 77.0373 114.723 75.6472 115.137 72.8339V72.8422Z" fill="white"/> +<path d="M152.024 117.945C153.819 122.968 155.093 127.742 155.482 132.723C155.581 133.956 155.3 134.866 154.076 135.545C143.766 141.304 133.166 146.368 121.516 148.792C118.993 149.322 116.37 149.355 113.788 149.595C113.407 149.628 113.027 149.595 112.646 149.562C107.218 149.123 105.092 145.871 106.821 140.7C107.748 137.928 109.221 135.594 111.959 134.287C113.474 133.559 114.938 132.707 116.502 132.103C123.337 129.455 130.146 126.758 137.055 124.317C142.103 122.538 147.167 120.858 152.015 117.937L152.024 117.945Z" fill="white"/> +<path d="M95.9156 66.6282C98.8778 66.4875 101.674 66.9509 104.331 67.8611C107.276 68.8705 110.147 70.1365 112.969 71.4604C114.475 72.1637 114.756 73.3883 113.68 74.6377C112.704 75.7547 111.471 76.8718 110.123 77.4013C107.367 78.477 104.504 79.412 101.6 79.9415C93.9297 81.3316 86.2429 81.2737 78.6388 79.3541C77.0253 78.9486 75.4449 78.3115 73.9225 77.6165C73.0454 77.2193 72.2097 76.5905 71.5312 75.8954C70.0666 74.3895 70.2735 73.1732 72.069 72.0313C76.8019 69.036 81.9899 67.3729 87.5254 66.6613C88.7417 66.5041 89.6354 66.8351 90.2394 67.8693C90.6945 68.6554 91.0834 69.4911 91.4971 70.302C91.7039 70.7074 91.8115 71.1791 92.0846 71.5348C93.1354 72.9084 94.4345 72.7594 94.9557 71.1211C95.3943 69.7559 95.5763 68.3079 95.9238 66.6365L95.9156 66.6282Z" fill="white"/> +<path d="M120.631 94.3967C123.568 92.5185 126.241 90.8057 129.071 89.0019C129.36 89.6059 129.757 90.3175 130.064 91.0705C133.117 98.658 134.044 106.535 133.274 114.652C133.183 115.612 132.753 116.125 131.942 116.572C128.856 118.276 125.604 119.526 121.955 120.577C121.64 111.74 122.567 102.977 120.631 94.3967V94.3967Z" fill="#C8E2E2"/> +<path d="M92.2749 183.511C94.5007 183.726 96.47 183.95 98.4392 184.098C102.916 184.429 107.268 183.61 111.612 182.7C112.506 182.51 112.936 182.005 113.184 181.244C113.54 180.143 114.376 179.936 115.36 180.052C117.139 180.259 118.744 180.88 120.143 182.08C119.613 185.546 117.354 187.681 114.591 189.27C109.088 192.439 103.172 193.573 96.9168 192.149C95.626 191.86 94.4014 191.057 93.2761 190.304C92.7796 189.973 92.3659 189.187 92.3163 188.575C92.1839 186.928 92.2749 185.257 92.2749 183.511V183.511Z" fill="white"/> +<path d="M91.0171 183.097V190.312C91.1247 190.18 91.0502 190.379 90.9344 190.403C86.1518 191.338 81.4355 191.835 77.0584 188.831C74.5844 187.135 73.5335 184.636 74.2534 181.426C79.6069 182.998 85.01 184.264 91.0088 183.097H91.0171Z" fill="white"/> +<path d="M81.0548 60.0418C82.9083 60.2239 84.6045 60.3976 86.3007 60.5631C86.3669 60.4225 86.4414 60.2735 86.5076 60.1329C85.3657 59.1151 84.0915 58.205 83.1068 57.0466C82.056 55.8137 81.6588 54.2416 81.7581 52.5702C91.9107 53.8858 95.8658 64.8906 93.5242 71.5845C93.0691 70.997 92.705 70.5999 92.432 70.153C91.77 69.0526 91.2487 67.8528 90.5041 66.8102C89.3043 65.1554 87.6825 64.1211 85.5974 63.7239C83.5205 63.3267 81.8409 62.2759 81.0548 60.0336V60.0418Z" fill="white"/> +<path d="M156.88 116.2C156.756 116.977 156.649 117.656 156.508 118.492C158.155 117.887 157.451 116.266 158.271 115.174C158.924 115.596 159.619 115.91 160.132 116.398C163.252 119.385 163.955 125.012 161.721 128.702C160.521 130.696 159.727 131.201 156.864 131.697C156.682 131.027 156.483 130.34 156.301 129.637C155.614 126.981 155.002 124.3 154.216 121.677C153.463 119.178 154.34 117.077 156.88 116.183V116.2Z" fill="white"/> +<path d="M112.696 178.629C112.323 181.401 112.017 181.658 109.427 182.212C105.059 183.139 100.657 183.271 96.2299 183.056C95.1791 183.006 94.1117 182.783 93.1023 182.468C91.4639 181.947 91.4391 181.798 91.8115 179.754C98.7784 179.382 105.754 179.01 112.704 178.637L112.696 178.629Z" fill="white"/> +<path d="M73.0702 178.505C78.8126 178.902 84.3398 179.283 90.0573 179.68C90.1318 180.441 90.2063 181.153 90.2807 181.939C89.5857 182.08 89.0727 182.253 88.5597 182.27C84.6046 182.369 80.6577 182.344 76.7771 181.36C75.0891 180.929 74.1045 179.779 73.0702 178.505V178.505Z" fill="white"/> +<path d="M11.1949 185.729C25.9315 191.008 46.9151 190.718 60.154 185.042C60.6091 185.944 60.245 186.713 59.641 187.284C58.4329 188.442 57.258 189.725 55.8348 190.536C53.0546 192.108 50.1421 193.324 46.7744 193.324C42.4966 193.324 38.2271 193.697 33.9493 193.846C28.1324 194.052 22.5721 192.944 17.1855 190.726C14.8439 189.758 12.9573 188.327 11.4266 186.357C11.3025 186.2 11.2777 185.977 11.1866 185.729H11.1949Z" fill="white"/> +<path d="M36.5804 188.31C37.4823 186.217 38.4008 184.148 39.253 182.046C39.4351 181.6 39.493 181.029 39.3854 180.565C38.7069 177.678 37.9457 174.815 37.2589 171.935C37.0438 171.058 36.9611 170.148 36.7708 168.907C42.2318 169.238 47.3701 170.404 52.856 170.992C52.4588 171.53 52.2354 171.894 51.9458 172.2C48.8347 175.444 45.6905 178.662 42.5959 181.922C42.1904 182.344 41.9091 182.915 41.6691 183.461C41.2389 184.438 40.9245 185.464 40.4942 186.44C39.5178 188.649 39.2944 188.749 36.5804 188.31V188.31Z" fill="white"/> +<path d="M41.0734 188.111C41.8594 184.14 44.2424 181.409 46.8737 178.828C47.519 178.191 48.1644 177.868 49.1077 178C52.2106 178.447 55.2886 178.968 58.1432 180.35C58.557 180.549 58.9872 180.723 59.3596 180.979C60.8986 182.03 60.9979 183.213 59.4589 184.214C58.2591 184.992 56.9104 185.621 55.5451 186.051C51.077 187.441 46.4599 188.054 41.8015 188.277C41.5864 188.285 41.363 188.186 41.0816 188.12L41.0734 188.111Z" fill="white"/> +<path d="M30.3995 185.422C31.5165 185.422 32.6501 185.455 33.7836 185.414C35.091 185.364 35.4137 185.042 35.3392 183.734C35.2647 182.361 35.0662 181.004 34.9172 179.639C34.8428 178.96 34.7517 178.29 34.6276 177.371C35.5543 177.314 36.3487 177.264 37.1761 177.214C38.3759 179.25 38.7731 181.186 37.7139 183.346C36.8782 185.033 36.2328 186.821 35.4882 188.575C28.7529 188.666 22.2244 188.169 15.8119 186.597C14.3556 186.242 12.9159 185.596 11.6251 184.818C10.1688 183.941 10.1936 182.75 11.534 181.682C13.0565 180.466 14.8851 179.945 16.9206 179.539C17.6322 181.914 18.3272 184.223 19.0471 186.597C20.0648 186.523 21.0495 186.49 22.0176 186.374C23.3084 186.217 23.5732 185.886 23.3497 184.562C23.1098 183.13 22.7374 181.732 22.4561 180.309C22.3403 179.705 22.3237 179.076 22.2162 177.959C24.0117 179.175 23.7221 180.656 24.0613 181.864C24.4337 183.172 24.7316 184.496 25.1039 185.977C26.254 185.795 27.3959 185.728 28.4467 185.381C28.8356 185.257 29.2245 184.487 29.241 183.999C29.2741 183.023 29.0507 182.03 28.9432 181.045C28.8273 180.011 28.728 178.977 28.5874 177.537C29.4314 178.811 30.2671 182.766 30.3912 185.431L30.3995 185.422Z" fill="white"/> +<path d="M34.6195 184.338H31.2022C30.4575 179.291 29.7211 174.269 28.9598 169.097C29.5721 168.965 29.9941 168.857 30.4244 168.783C30.7885 168.717 31.1608 168.692 31.8227 168.617C33.4776 173.673 34.0982 178.853 34.6112 184.347L34.6195 184.338Z" fill="#F0C3BA"/> +<path d="M28.5957 184.338C27.5201 184.562 26.7257 184.727 25.617 184.959C24.6489 180.739 23.7056 176.602 22.7292 172.316C23.4077 172.266 23.8546 172.208 24.2931 172.208C26.2376 172.208 26.7754 172.548 27.0981 174.409C27.6525 177.62 28.0744 180.847 28.5957 184.338V184.338Z" fill="#F9E6A6"/> +<path d="M17.1523 175.75C19.6346 174.459 20.1063 174.583 20.7268 176.875C21.455 179.572 22.0507 182.311 22.7458 185.249C21.5956 185.364 20.7186 185.455 19.6429 185.563C18.4762 182.278 17.5495 179.051 17.1523 175.741V175.75Z" fill="#C8E2E2"/> +<path d="M52.161 170.206C47.3702 169.519 42.5794 168.832 37.7886 168.146C37.8134 167.964 37.8465 167.782 37.8713 167.608C42.687 168.113 47.5357 168.435 52.2355 169.743C52.2107 169.9 52.1859 170.057 52.161 170.214V170.206Z" fill="white"/> +<path d="M193.139 57.2948C192.675 56.7652 192.212 56.2439 191.583 55.5323C192.386 55.0111 192.99 54.6222 193.85 54.0595C193.767 55.2924 193.71 56.1612 193.652 57.03C193.478 57.1127 193.304 57.2037 193.13 57.2865L193.139 57.2948Z" fill="white"/> +<path d="M195.911 37.8584C196.73 37.8088 197.284 37.784 198.07 37.7343C197.665 38.4046 197.375 38.8762 196.928 39.5961C196.548 38.9507 196.291 38.5038 195.911 37.8584Z" fill="white"/> +<path d="M188.348 56.5253C188.439 55.4744 188.488 54.9201 188.555 54.1174C189.159 54.5146 189.564 54.7877 190.16 55.1848C189.622 55.5903 189.2 55.9047 188.356 56.5336L188.348 56.5253Z" fill="white"/> +<path d="M194.868 45.4294C194.686 46.2238 194.322 46.7947 193.139 46.5878C193.387 46.0003 193.619 45.4708 193.85 44.9164C194.305 45.1564 194.611 45.3219 194.918 45.4791L194.868 45.4294V45.4294Z" fill="white"/> +<path d="M147.481 194.963C144.941 194.83 142.591 194.714 140.249 194.582C136.815 194.383 133.381 194.21 129.956 193.928C127.631 193.738 127.498 193.506 127.275 191.115C126.994 188.095 127.581 185.191 128.351 182.295C128.731 180.847 129.542 180.11 131.081 180.044C133.903 179.928 136.724 179.663 139.554 179.539C142.913 179.39 146.281 179.316 149.64 179.217C150.087 179.2 150.534 179.217 151.353 179.217C149.351 184.413 147.779 189.477 147.497 194.971L147.481 194.963Z" fill="white"/> +<path d="M151.585 188.699C151.784 187.59 151.932 186.804 152.115 185.795C154.671 185.646 157.112 185.505 159.553 185.364C159.81 188.211 159.297 188.906 156.947 188.931C155.292 188.947 153.645 188.798 151.593 188.699H151.585Z" fill="white"/> +<path d="M121.855 188.823C121.93 187.979 121.996 187.243 122.087 186.275C123.651 186.076 125.124 185.886 126.588 185.695C127.416 188.848 125.645 189.46 121.864 188.823H121.855Z" fill="white"/> +<path d="M125.984 48.9046C126.439 47.6718 126.828 46.4472 127.341 45.2722C128.93 41.6646 131.991 40.3242 135.657 40.3076C139.107 40.2911 141.904 41.6977 143.402 44.9909C143.857 46.0003 144.006 47.1422 144.361 48.4744C146.678 47.0595 149.086 46.3396 151.171 48.2013C152.677 49.55 154.208 51.1304 154.506 53.6624C142.161 54.4153 130.08 55.1517 117.925 55.8881C117.412 53.3065 118.306 51.329 119.828 49.6493C121.541 47.7545 123.783 48.6729 125.993 48.8964L125.984 48.9046Z" fill="#EEEFFC"/> +<path d="M30.4575 73.5703C33.4446 71.0632 36.6302 70.6578 40.0309 72.0396C43.2579 73.3552 45.8974 75.2749 46.8655 79.0479C49.2154 79.2217 51.3832 79.8092 51.3584 82.9865C50.862 83.061 50.4151 83.1933 49.9766 83.1933C41.4872 83.2347 33.006 83.2595 24.5166 83.3009C23.292 83.3009 22.0674 83.2927 20.8511 83.3919C19.5354 83.4995 18.9232 82.8789 18.5343 81.704C17.2269 77.8068 19.6513 73.6034 23.9291 72.594C26.1301 72.0727 28.3228 72.0479 30.4658 73.5703H30.4575Z" fill="#EEEFFC"/> +<path d="M85.1424 35.1279C85.1341 31.6858 87.2027 29.6586 90.2394 28.6905C93.6732 27.5817 96.8919 28.6491 99.7548 30.5853C100.557 31.1314 100.955 32.2815 101.633 33.291C103.884 32.0333 106.043 32.1657 107.748 34.1681C108.633 35.2024 109.295 36.4932 108.244 37.9743C99.3494 39.3395 90.347 38.2142 81.3942 38.992C81.27 36.1622 81.7251 35.7071 85.1424 35.1279V35.1279Z" fill="#EEEFFC"/> +<path d="M203.506 4.36406C203.73 3.56972 203.953 2.78366 204.267 1.67491C205.873 3.19738 206.775 5.1253 208.14 6.64777C209.555 8.21989 210.928 9.82511 212.699 11.8523C208.959 11.5296 205.715 11.2566 202.257 10.9587C202.257 10.1312 202.257 9.56031 202.257 8.98939C202.671 7.45037 203.093 5.91136 203.506 4.37234V4.36406Z" fill="#FDF9F3"/> +</g> +<defs> +<clipPath id="clip0_9_498"> +<rect width="214" height="197" fill="white"/> +</clipPath> +</defs> +</svg> diff --git a/frontend/app/svg/ca-no-dashboards.svg b/frontend/app/svg/ca-no-dashboards.svg new file mode 100644 index 000000000..d4abf5f2a --- /dev/null +++ b/frontend/app/svg/ca-no-dashboards.svg @@ -0,0 +1,53 @@ +<svg width="210" height="197" viewBox="0 0 210 197" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_9_617)"> +<rect width="210" height="197" fill="white"/> +<path d="M11.912 184.085C15.5312 182.711 16.5526 182.86 21.4004 181.729C31.7555 179.313 32.7552 179.834 45.3794 178.681C73.642 176.1 104.352 175.802 133.505 177.164C152.628 178.058 170.578 179.814 188.444 181.915C196.846 182.904 206.761 184.304 218.808 185.753" stroke="black" stroke-linecap="round"/> +<circle cx="101.718" cy="91.953" r="84.3749" fill="#FDF9F3"/> +<path d="M201.525 122.89C201.115 120.37 200.115 117.94 197.895 116.06C190.925 118.47 188.314 124.27 186.344 130.81H181.245C181.165 130.33 181.055 130.06 181.085 129.8C181.155 128.88 181.245 127.96 181.375 127.05C182.535 118.72 181.565 110.64 178.335 102.88C176.305 97.9999 172.905 94.2799 168.045 92.1099C162.215 89.5199 156.215 87.3999 149.955 86.0399C146.345 85.2599 142.755 84.3599 139.135 83.6399C133.065 82.4399 127.015 81.0499 120.895 80.2699C113.935 79.3999 106.895 79.1299 99.9045 78.5599C89.0645 77.6899 78.2945 78.2899 67.5645 79.9299C61.3445 80.8799 55.2345 82.2899 49.4545 84.9099C38.3645 89.9599 31.3945 98.5199 28.4145 110.17C27.6145 113.31 26.6045 115.99 24.6145 118.54C21.5445 122.45 17.9545 124.17 13.0045 122.76C10.7345 122.11 8.40453 122.35 6.27452 123.51C3.68453 124.93 2.81452 127.4 3.95452 130.1C5.29452 133.25 7.78454 134.9 11.1045 134.86C16.1645 134.8 20.5145 133.08 23.3245 128.54C24.0545 127.36 24.7245 126.15 25.4345 124.96C25.7345 124.47 26.0945 124 26.7545 123.03C26.9045 125.01 27.0145 126.38 27.1245 127.75C27.2145 129.04 27.1645 130.36 27.4145 131.61C29.5845 142.12 35.0545 150.5 43.6745 156.88C49.6745 161.32 56.3445 164.38 63.3745 166.83C67.0845 168.12 70.6345 169.85 74.6245 171.54C72.2345 174.05 70.0545 176.3 67.9145 178.58C66.7345 179.85 65.6045 181.17 64.4545 182.46L64.4945 182.49C64.5445 182.52 64.6645 182.6 64.6645 182.6C67.0345 180.43 69.6745 178.48 71.7345 176.04C76.8645 169.97 81.7345 163.69 86.6945 157.48C88.5945 155.1 90.4745 152.7 92.3145 150.27C92.7545 149.69 93.0045 148.96 93.3445 148.3C93.2345 148.25 93.1245 148.21 93.0145 148.16C91.2345 150.34 89.4545 152.51 87.4045 155.02C87.0645 153.87 86.8245 153.28 86.7045 152.66C84.2545 140.33 83.4645 127.88 85.4345 115.45C86.6645 107.72 88.6445 100.09 90.5245 92.4799C91.4045 88.8999 92.7445 85.4299 94.0745 81.9899C95.1145 79.2999 95.3545 79.2899 98.2045 79.4599C100.975 79.6299 103.755 79.6899 106.505 79.9699C113.215 80.6399 119.965 81.0999 126.595 82.2199C133.875 83.4499 141.015 85.4199 148.255 86.8899C154.815 88.2199 161.065 90.4099 167.195 93.0199C169.785 94.1199 171.985 95.7699 173.855 97.8899C180.055 104.91 181.125 113.37 180.755 122.24C180.715 123.35 180.565 124.45 180.415 125.55C179.695 130.75 179.105 135.98 177.365 141.06C174.765 141.43 173.755 143.26 173.725 145.55C173.685 148.3 174.005 151.05 174.165 153.8C174.215 154.8 174.475 155.87 174.225 156.8C172.565 163.1 170.095 169.08 165.985 174.2C164.035 176.62 161.905 179.21 158.705 179.94C155.125 180.76 151.435 181.41 147.765 181.53C138.785 181.82 129.875 180.91 121.025 179.38C115.075 178.34 107.515 177.05 101.385 176.01C101.095 175.96 100.805 175.91 100.515 175.86C100.725 176.12 100.955 176.35 101.225 176.55C102.385 177.41 104.065 177.73 106.205 178.08C110.035 178.71 113.855 179.36 117.675 180.03C125.775 181.44 133.875 182.7 142.145 182.71C147.165 182.72 152.105 182.36 157.015 181.42C159.245 180.99 161.174 180.11 162.904 178.58C166.154 175.72 168.744 172.38 170.594 168.52C171.384 166.89 172.415 166.34 174.075 166.51C176.455 166.76 178.845 167.03 181.245 167.19C182.665 167.28 183.895 167.62 185.085 168.5C186.495 169.55 188.095 170.2 189.975 170.14C191.795 170.07 192.575 168.63 193.815 167.74C196.445 165.86 197.095 163.27 196.585 160.41C196.205 158.27 195.295 156.21 194.515 154.17C194.165 153.27 193.535 152.48 192.955 151.51C193.475 151.51 193.755 151.46 194.005 151.52C199.245 152.73 199.795 152.81 201.495 146.58C201.565 146.31 201.605 146.03 201.625 145.75C201.965 141.89 202.575 138.03 202.575 134.16C202.575 130.29 202.145 126.61 201.525 122.89V122.89ZM26.3445 121.1C25.1345 123.47 23.7845 125.79 22.3545 128.04C19.7045 132.18 15.6045 133.64 10.9545 133.64C8.25452 133.65 6.21453 132.25 5.09453 129.71C4.14453 127.56 4.75452 125.68 6.80452 124.53C8.67452 123.47 10.7445 123.31 12.7245 123.85C18.3345 125.38 22.4645 123.46 25.5445 118.74C25.8945 118.22 26.3345 117.76 26.7345 117.26C26.9445 117.33 27.1545 117.39 27.3645 117.46C27.0345 118.68 26.9045 119.99 26.3445 121.1V121.1ZM34.4745 100.05C31.1245 115.21 31.3945 129.93 34.4445 144.79C25.3445 134.95 26.0745 109.14 34.4745 100.05ZM85.5445 151.28C86.5145 155.09 85.7345 157.65 83.2345 160.4C80.4545 163.47 78.0945 166.92 75.4045 170.39C73.9745 169.91 72.5745 169.51 71.2245 168.98C66.6645 167.22 62.1445 165.34 57.5545 163.67C55.5945 162.96 54.3245 161.89 53.5045 159.91C49.7945 150.94 47.7945 141.57 46.8445 131.97C46.4145 127.57 46.4745 123.11 46.3645 118.68C46.3345 117.69 46.5045 116.7 46.6245 115.2C58.7845 118.98 70.8045 120.64 83.3845 120.59C83.3845 121.6 83.4045 122.38 83.3845 123.16C83.2345 132.6 83.2345 142.08 85.5445 151.28V151.28ZM83.6245 119.45C73.7745 119.58 54.4145 117.24 46.6745 114.05C46.9845 111.79 47.2245 109.42 47.6445 107.08C48.7745 100.9 50.2545 94.8199 52.9345 89.0799C53.2245 88.4499 53.3045 87.7199 53.4745 87.0299C53.4745 87.0299 53.4679 87.0332 53.4545 87.0399C51.9445 90.4399 50.1045 93.7199 49.0045 97.2499C43.3545 115.36 44.1145 133.51 49.3445 151.57C50.2645 154.74 51.6345 157.77 53.0045 161.41C51.2745 160.51 49.8845 159.91 48.6345 159.09C44.2645 156.27 40.3645 152.88 37.0245 148.89C36.4045 148.15 35.9445 147.16 35.7345 146.22C33.6445 136.89 32.8545 127.42 33.1045 117.88C33.2745 111.49 34.2345 105.19 35.9645 99.0299C36.6845 96.4899 37.9745 94.3399 40.0045 92.5599C44.0745 88.9899 48.6445 86.3599 54.0145 84.4999C53.7745 85.5299 53.6045 86.2899 53.4245 87.0399H53.4545C53.4545 87.0399 53.4545 87.0299 53.4645 87.0199C53.4745 86.9999 53.4745 86.9899 53.4845 86.9699C53.4845 86.9899 53.4745 86.9999 53.4745 87.0199C53.5445 86.9899 53.6245 86.9599 53.6945 86.9299C53.7845 86.8899 53.8645 86.8499 53.8845 86.7799C54.8645 83.9099 57.2645 83.1899 59.8745 82.6399C66.0245 81.3299 72.1745 80.1599 78.4645 79.8899C83.5245 79.6699 88.5745 79.4099 93.9545 79.1499C89.0245 92.5099 85.6845 105.84 83.6245 119.45V119.45ZM180.844 132.18H186.165C185.635 137.09 186.394 141.7 188.154 146.91C186.094 145.71 184.595 144.77 183.045 143.94C181.505 143.13 179.915 142.42 178.315 141.64C179.165 138.47 179.974 135.45 180.844 132.18V132.18ZM172.205 165.6C173.185 162.73 174.115 160.01 175.055 157.28C177.275 160.32 179.395 163.23 181.585 166.23C178.535 166.02 175.545 165.82 172.205 165.6ZM185.275 167.62C180.925 164.47 177.455 160.6 175.625 155.43C174.365 151.87 174.125 148.27 175.255 144.55L175.615 146.47C176.245 149.96 177.295 153.3 179.295 156.26C181.555 159.62 183.905 162.93 186.265 166.23C187.305 167.7 188.685 168.73 190.565 168.9C188.425 169.61 186.785 168.72 185.275 167.62V167.62ZM193.715 154.77C194.765 156.76 195.465 159.02 195.885 161.24C196.315 163.43 195.465 165.39 193.535 166.78C191.295 168.4 189.315 168.27 187.375 166.3C184.305 163.18 182.185 159.39 179.955 155.67C178.035 152.45 176.905 148.97 176.365 145.28C176.235 144.44 176.344 143.55 176.344 142.28C178.124 143 179.585 143.49 180.955 144.16C186.075 146.66 190.945 149.53 193.715 154.77ZM188.805 145.75C186.975 139.89 186.535 134.02 188.425 128.07C189.545 124.54 191.555 121.6 194.215 119.11C195.045 118.33 196.164 117.87 197.154 117.26C197.314 117.43 197.475 117.61 197.635 117.78C196.005 128.65 194.895 139.53 196.965 150.45C193.225 151.12 189.865 149.13 188.805 145.75V145.75ZM198.505 150.66C195.835 146.96 196.665 127.95 198.575 118.52C202.605 125.62 202.435 145.87 198.505 150.66Z" fill="black"/> +<path d="M98.0245 187.33C97.6645 187.32 97.4445 187.31 97.2345 187.33C88.5845 187.8 79.9145 188.56 71.4245 185.87C69.0145 185.1 66.4046 184.75 64.6646 182.6C64.6646 182.6 64.5445 182.52 64.4945 182.49C64.4745 182.48 64.4545 182.47 64.4545 182.47C63.7845 182.79 63.6845 183.25 64.2745 183.71C65.1045 184.36 65.9846 184.94 66.8746 185.57C66.5146 186.06 66.3046 186.37 66.0746 186.65C64.3546 188.66 63.9046 190.9 64.9746 193.34C66.0846 195.85 68.3245 196.61 70.8045 196.9C73.9445 197.27 76.8546 196.62 79.5046 194.86C80.1846 194.41 80.9146 194.03 81.7246 193.56C83.3546 195.25 85.1946 194.73 86.8146 193.84C88.2446 193.06 89.6246 191.99 90.6646 190.76C92.0146 189.15 93.4646 188.25 95.6046 188.36C96.3646 188.4 97.2846 188.45 98.0346 187.67C98.0846 187.62 98.1246 187.58 98.1546 187.53C98.2146 187.47 98.2646 187.4 98.3146 187.33H98.0245V187.33ZM83.1445 193.58C82.9845 192.92 82.8945 192.53 82.7745 191.99C81.7145 192.58 80.7145 193.05 79.8045 193.66C77.2545 195.38 74.4946 196.08 71.4146 195.84C69.1646 195.66 67.1346 195.17 66.0746 193.02C64.9646 190.8 65.5845 188.71 67.0845 186.85C67.3145 186.56 67.5645 186.3 67.9545 185.87C75.2845 188.38 82.9146 189.16 90.7846 188.93C88.8146 191.48 86.9545 193.97 83.1445 193.58V193.58Z" fill="black"/> +<path d="M51.6945 191.886C50.5945 193.626 49.2445 193.046 47.6945 192.016C47.7845 193.406 47.8545 194.466 47.9445 195.886C46.1845 194.856 44.6945 193.916 43.1345 193.086C41.5545 192.236 39.9145 191.506 37.8745 190.516C41.3345 189.906 43.2945 192.486 46.0745 192.986C46.2945 192.106 46.5145 191.196 46.8445 189.856C47.9945 190.476 48.8945 190.976 49.8945 191.516C50.4745 191.086 51.1145 190.616 51.9745 189.976C52.8545 190.986 53.6745 191.926 54.5245 192.896C55.6845 192.126 56.7945 191.386 58.0745 190.536C58.4845 191.316 58.7645 191.846 59.0845 192.446C60.0945 192.106 61.1145 191.756 62.1445 191.416C62.2345 191.556 62.3245 191.706 62.4145 191.846C60.9345 192.856 59.4545 193.856 57.7445 195.016C57.5445 194.036 57.4045 193.376 57.2045 192.396C56.4245 193.256 55.8345 193.906 55.0145 194.806C53.9845 193.906 52.9945 193.026 51.7045 191.886H51.6945Z" fill="#A1C7C7"/> +<path d="M29.5846 181.11C32.3846 181.1 34.6546 182.24 36.9146 183.95C37.5846 183.55 38.4246 183.2 39.0646 182.63C40.6846 181.19 41.2046 181.12 42.6546 182.51C43.0446 182.89 43.3446 183.37 43.7746 183.91C44.5146 183.36 45.1846 182.75 45.9646 182.31C46.7246 181.88 47.5946 181.62 48.6446 181.73C47.6146 182.5 46.4946 183.17 45.5946 184.06C44.7046 184.94 44.0546 186.05 43.1246 187.27C42.1546 185.81 42.9046 183.63 40.6446 182.96C40.5446 184.02 40.4446 184.96 40.3646 185.83C39.2646 186.8 38.1046 183.71 37.5546 185.88C34.7446 184.2 32.1646 182.65 29.5846 181.11V181.11Z" fill="#A1C7C7"/> +<path d="M108.345 156.24C105.765 162.75 103.435 169.33 101.385 176.01C101.325 176.19 101.275 176.37 101.225 176.55C100.135 180.12 99.1247 183.71 98.2047 187.33C98.1847 187.4 98.1647 187.46 98.1547 187.53C98.1147 187.67 98.0746 187.8 98.0446 187.94C98.0346 187.85 98.0347 187.76 98.0347 187.67C98.0247 187.56 98.0247 187.45 98.0247 187.33C98.1147 181.17 104.435 161.96 108.345 156.24V156.24Z" fill="black"/> +<path d="M18.162 190.31C20.372 188.44 21.412 191.15 23.252 191.73C24.262 189.25 27.072 191.73 28.422 189.66C27.192 192.23 26.872 192.39 24.722 191.74C24.272 192.27 23.802 192.82 23.302 193.4C22.642 192.9 22.012 192.41 21.292 191.87C21.012 192.44 20.792 192.87 20.392 193.66C18.962 192.7 17.672 191.74 16.292 190.94C14.872 190.11 13.372 189.43 11.912 188.68C11.982 188.51 12.052 188.34 12.122 188.17C14.042 188.85 15.972 189.54 18.162 190.32V190.31Z" fill="#A1C7C7"/> +<path d="M104.655 126.12C103.615 122.78 100.855 121.86 97.9245 123.74C96.1345 124.89 96.1746 126.7 96.1646 128.4C96.1646 131.17 98.2845 133.28 100.805 133.26C103.105 133.24 104.955 131.05 105.015 127.84C104.955 127.55 104.875 126.81 104.655 126.12V126.12ZM102.705 131.49C101.735 132.42 100.555 132.3 99.3645 131.77C97.5545 130.96 97.1645 129.44 97.1545 127.69C97.1745 125.72 98.4945 124.16 100.405 123.86C102.045 123.6 103.415 124.83 103.965 126.96C104.425 128.74 103.975 130.27 102.705 131.49V131.49Z" fill="black"/> +<path d="M119.264 186.39C118.204 187.03 117.364 187.54 116.324 188.17C115.874 187.1 115.534 186.3 115.154 185.4C114.424 185.9 113.724 186.38 112.774 187.03C112.644 185.22 113.504 184.28 114.694 183.78C116.284 183.12 116.504 184.89 117.414 185.66C117.964 185.35 118.484 185 119.054 184.75C120.744 184 121.004 184.19 121.374 186.51C122.994 186.61 124.654 186.72 127.014 186.87C125.434 187.26 124.404 187.57 123.364 187.75C122.324 187.93 121.264 187.99 119.964 188.13C119.774 187.68 119.524 187.07 119.244 186.38L119.264 186.39Z" fill="#A1C7C7"/> +<path d="M174.519 189.808C172.889 189.678 171.259 189.588 169.639 189.388C169.419 189.358 169.269 188.818 169.039 188.448C168.459 188.908 167.919 189.328 167.289 189.818C166.459 189.058 165.729 188.398 164.869 187.608C164.219 189.058 163.659 190.328 163.089 191.608C162.199 189.628 162.919 188.028 163.799 186.498C164.229 185.768 165.009 185.678 165.719 186.208C166.229 186.588 166.689 187.048 167.289 187.578C168.509 186.898 169.659 186.158 171.009 187.798C171.699 188.648 173.299 188.758 174.479 189.198C174.489 189.398 174.499 189.608 174.509 189.808H174.519Z" fill="#A1C7C7"/> +<path d="M203.054 188.779C200.734 189.209 198.624 189.869 196.514 189.859C195.394 189.859 194.284 188.669 192.724 187.759C191.814 188.359 190.474 189.239 188.934 190.259C188.814 188.589 189.984 187.909 190.954 187.279C191.844 186.709 192.924 186.429 193.934 186.089C194.104 186.029 194.484 186.279 194.614 186.479C195.924 188.459 197.704 188.629 199.854 188.129C200.794 187.909 202.014 187.719 203.044 188.789L203.054 188.779Z" fill="#A1C7C7"/> +<path d="M110.175 124.07C110.035 123.63 109.155 123.13 108.675 123.17C108.265 123.21 107.675 123.97 107.605 124.48C107.245 126.93 107.015 129.4 106.785 131.87C106.745 132.32 106.795 132.84 107.005 133.22C107.335 133.8 107.825 134.7 108.255 134.7C108.795 134.71 109.655 134.04 109.825 133.49C110.355 131.75 110.635 129.93 110.985 128.27C110.705 126.73 110.585 125.35 110.175 124.07V124.07ZM108.205 133.8C107.205 130.41 108.475 127.49 108.755 124.48C110.405 125.56 109.915 127.21 109.925 128.62C109.945 130.36 110.015 132.23 108.205 133.8Z" fill="black"/> +<path d="M173.855 97.8899C171.985 95.7699 169.785 94.1199 167.195 93.0199C161.065 90.4099 154.815 88.2199 148.255 86.8899C141.015 85.4199 133.875 83.4499 126.595 82.2199C119.965 81.0999 113.215 80.6399 106.505 79.9699C103.755 79.6899 100.975 79.6299 98.2045 79.4599C95.3545 79.2899 95.1146 79.2999 94.0746 81.9899C92.7446 85.4299 91.4045 88.8999 90.5245 92.4799C88.6445 100.09 86.6645 107.72 85.4345 115.45C83.4645 127.88 84.2545 140.33 86.7045 152.66C86.8245 153.28 87.0645 153.87 87.4045 155.02C89.4545 152.51 91.2345 150.34 93.0145 148.16C93.1245 148.21 93.2345 148.25 93.3445 148.3C93.0045 148.96 92.7545 149.69 92.3145 150.27C90.4745 152.7 88.5945 155.1 86.6945 157.48C81.7345 163.69 76.8645 169.97 71.7345 176.04C69.6745 178.48 67.0346 180.43 64.6646 182.6C66.4046 184.75 69.0145 185.1 71.4245 185.87C79.9145 188.56 88.5845 187.8 97.2345 187.33C97.4445 187.31 97.6645 187.32 98.0245 187.33C98.1145 181.17 104.435 161.96 108.345 156.24C105.765 162.75 103.435 169.33 101.385 176.01C107.515 177.05 115.075 178.34 121.025 179.38C129.875 180.91 138.785 181.82 147.765 181.53C151.435 181.41 155.125 180.76 158.705 179.94C161.905 179.21 164.035 176.62 165.985 174.2C170.095 169.08 172.565 163.1 174.225 156.8C174.475 155.87 174.215 154.8 174.165 153.8C174.005 151.05 173.685 148.3 173.725 145.55C173.755 143.26 174.765 141.43 177.365 141.06C179.105 135.98 179.695 130.75 180.415 125.55C180.565 124.45 180.715 123.35 180.755 122.24C181.125 113.37 180.055 104.91 173.855 97.8899ZM100.805 133.26C98.2845 133.28 96.1646 131.17 96.1646 128.4C96.1646 126.7 96.1345 124.89 97.9245 123.74C100.855 121.86 103.615 122.78 104.655 126.12C104.875 126.81 104.955 127.55 105.015 127.84C104.955 131.05 103.105 133.24 100.805 133.26V133.26ZM109.825 133.49C109.655 134.04 108.795 134.71 108.255 134.7C107.825 134.7 107.335 133.8 107.005 133.22C106.795 132.84 106.745 132.32 106.785 131.87C107.015 129.4 107.245 126.93 107.605 124.48C107.675 123.97 108.265 123.21 108.675 123.17C109.155 123.13 110.035 123.63 110.175 124.07C110.585 125.35 110.705 126.73 110.985 128.27C110.635 129.93 110.355 131.75 109.825 133.49V133.49Z" fill="white"/> +<path d="M46.6247 115.2C58.7847 118.97 70.8047 120.63 83.3847 120.59C83.3847 121.6 83.3947 122.38 83.3847 123.16C83.2347 132.6 83.2346 142.08 85.5446 151.27C86.5046 155.09 85.7346 157.65 83.2346 160.4C80.4546 163.47 78.0947 166.91 75.4047 170.39C73.9747 169.91 72.5747 169.5 71.2247 168.98C66.6647 167.22 62.1447 165.34 57.5547 163.67C55.5947 162.96 54.3346 161.89 53.5146 159.91C49.8046 150.94 47.7947 141.57 46.8447 131.97C46.4147 127.57 46.4746 123.11 46.3646 118.68C46.3346 117.69 46.5147 116.69 46.6347 115.2H46.6247ZM50.1446 120.66C50.1846 121.01 50.1246 121.42 50.2946 121.67C51.7846 123.87 53.2747 126.07 54.8747 128.2C55.3547 128.84 56.0947 129.35 56.8247 129.7C57.8747 130.21 58.9647 130.19 59.8547 129.23C60.7547 128.26 60.8347 127.2 60.0747 125.96C58.5447 123.47 53.4446 120.7 50.1446 120.66V120.66Z" fill="white"/> +<path d="M53.4245 87.0399C53.5845 86.9499 53.8345 86.8999 53.8745 86.7799C54.8545 83.9099 57.2545 83.1899 59.8745 82.6399C66.0145 81.3399 72.1645 80.1699 78.4545 79.8899C83.5145 79.6699 88.5645 79.4099 93.9445 79.1499C89.0145 92.5099 85.6745 105.84 83.6145 119.45C73.7745 119.58 54.4046 117.24 46.6646 114.06C46.9746 111.8 47.2046 109.43 47.6346 107.09C48.7646 100.91 50.2446 94.8199 52.9146 89.0899C53.2146 88.4399 53.2945 87.6899 53.4745 86.9799L53.4146 87.0499L53.4245 87.0399Z" fill="#C8E2E2"/> +<path d="M53.4845 86.9699C51.9745 90.3899 50.1146 93.6999 49.0046 97.2499C43.3546 115.36 44.1146 133.51 49.3446 151.57C50.2646 154.74 51.6346 157.77 53.0046 161.41C51.2846 160.51 49.8946 159.9 48.6346 159.09C44.2646 156.26 40.3645 152.88 37.0245 148.88C36.4045 148.14 35.9445 147.15 35.7345 146.21C33.6545 136.88 32.8546 127.41 33.1046 117.87C33.2746 111.48 34.2246 105.18 35.9646 99.0199C36.6746 96.4899 37.9646 94.3299 40.0046 92.5499C44.0746 88.9799 48.6445 86.3499 54.0145 84.4899C53.7745 85.5199 53.6045 86.2799 53.4245 87.0399L53.4845 86.9699V86.9699Z" fill="white"/> +<path d="M176.345 142.28C178.125 143 179.585 143.49 180.955 144.16C186.065 146.65 190.935 149.53 193.705 154.76C194.755 156.75 195.455 159.01 195.885 161.23C196.305 163.43 195.455 165.38 193.525 166.78C191.285 168.4 189.305 168.27 187.365 166.3C184.295 163.17 182.175 159.39 179.945 155.67C178.015 152.45 176.885 148.97 176.345 145.28C176.225 144.43 176.325 143.55 176.325 142.27L176.345 142.28Z" fill="white"/> +<path d="M197.635 117.77C195.995 128.64 194.895 139.53 196.965 150.45C193.225 151.12 189.865 149.12 188.805 145.75C186.975 139.89 186.525 134.01 188.425 128.06C189.545 124.54 191.545 121.59 194.215 119.1C195.045 118.33 196.165 117.86 197.155 117.26C197.315 117.43 197.475 117.6 197.635 117.77V117.77Z" fill="white"/> +<path d="M27.3546 117.46C27.0246 118.68 26.8946 120 26.3346 121.1C25.1246 123.48 23.7746 125.79 22.3446 128.04C19.6946 132.18 15.5946 133.64 10.9446 133.65C8.25458 133.65 6.21456 132.26 5.08456 129.72C4.13456 127.57 4.74455 125.69 6.79455 124.54C8.66455 123.49 10.7246 123.32 12.7146 123.86C18.3246 125.39 22.4546 123.46 25.5346 118.75C25.8746 118.22 26.3246 117.76 26.7246 117.27C26.9346 117.33 27.1446 117.4 27.3546 117.46V117.46Z" fill="white"/> +<path d="M34.4746 100.05C31.1246 115.22 31.3946 129.93 34.4446 144.79C25.3446 134.96 26.0746 109.14 34.4746 100.05Z" fill="white"/> +<path d="M198.575 118.52C202.605 125.62 202.435 145.87 198.515 150.66C195.845 146.96 196.675 127.95 198.575 118.52V118.52Z" fill="white"/> +<path d="M186.165 132.18C185.635 137.09 186.385 141.7 188.155 146.9C186.095 145.7 184.595 144.77 183.035 143.94C181.505 143.12 179.915 142.41 178.305 141.64C179.155 138.47 179.965 135.45 180.845 132.18H186.165V132.18Z" fill="white"/> +<path d="M190.555 168.9C188.415 169.61 186.785 168.72 185.265 167.63C180.915 164.48 177.445 160.6 175.615 155.44C174.355 151.88 174.115 148.28 175.245 144.56C175.365 145.2 175.485 145.84 175.595 146.48C176.225 149.97 177.285 153.31 179.275 156.27C181.545 159.63 183.895 162.94 186.245 166.24C187.295 167.71 188.675 168.74 190.545 168.91L190.555 168.9Z" fill="white"/> +<path d="M172.205 165.6C173.185 162.73 174.115 160.02 175.055 157.28C177.275 160.32 179.395 163.23 181.575 166.23C178.525 166.03 175.535 165.83 172.195 165.6H172.205Z" fill="white"/> +<path d="M83.1345 193.58C82.9845 192.92 82.8944 192.53 82.7644 191.99C81.7044 192.58 80.7044 193.05 79.7944 193.66C77.2444 195.38 74.4844 196.08 71.3944 195.84C69.1444 195.66 67.1245 195.17 66.0545 193.03C64.9445 190.8 65.5645 188.72 67.0645 186.85C67.2945 186.57 67.5545 186.3 67.9345 185.87C75.2645 188.38 82.8845 189.16 90.7545 188.93C88.7845 191.48 86.9245 193.98 83.1145 193.58H83.1345Z" fill="white"/> +<path d="M97.1545 127.69C97.1745 125.72 98.4945 124.16 100.405 123.86C102.045 123.6 103.415 124.84 103.965 126.96C104.425 128.74 103.975 130.27 102.705 131.49C101.735 132.42 100.555 132.3 99.3645 131.77C97.5645 130.97 97.1645 129.44 97.1545 127.69V127.69Z" fill="#E1B7AE"/> +<path d="M108.204 133.8C107.204 130.41 108.474 127.49 108.754 124.48C110.394 125.56 109.914 127.21 109.924 128.62C109.934 130.36 110.004 132.23 108.204 133.81V133.8Z" fill="white"/> +<path d="M59.4879 128.89L59.488 128.89C59.8796 128.468 60.0604 128.063 60.0882 127.662C60.1165 127.254 59.9923 126.782 59.6483 126.221L59.4879 128.89ZM59.4879 128.89C59.1159 129.291 58.7268 129.474 58.3405 129.52C57.9452 129.567 57.5073 129.476 57.043 129.25L57.0407 129.249M59.4879 128.89L57.0407 129.249M57.0407 129.249C56.3562 128.921 55.69 128.445 55.2729 127.898C53.7419 125.869 52.3112 123.756 50.876 121.637C50.8201 121.554 50.7643 121.472 50.7085 121.389L50.7085 121.389L50.7083 121.389C50.7076 121.388 50.701 121.376 50.6938 121.344C50.6854 121.308 50.6791 121.259 50.6748 121.192M57.0407 129.249L50.6748 121.192M50.6748 121.192C52.1284 121.337 53.9018 121.959 55.537 122.836C57.3857 123.827 58.9514 125.087 59.6483 126.221L50.6748 121.192Z" stroke="black"/> +<path d="M55.6013 60.8644C53.0233 50.4257 49.5426 40.5197 47.0856 30.3385C47.2691 30.2892 47.4377 30.2231 47.6212 30.1738C48.6754 33.2869 49.8069 36.3792 50.7742 39.5156C51.8082 42.8619 52.6904 46.2593 53.6208 49.6438C54.5253 52.9318 55.3911 56.2301 56.3515 59.8032C57.2641 59.6926 58.0771 59.7122 58.8225 59.4808C65.2647 57.5214 71.6791 55.497 78.1213 53.5376C82.8891 52.0904 87.6776 50.7204 92.461 49.3311C94.0466 48.8739 95.6478 48.4745 97.4924 47.9788C96.9395 49.5046 95.7221 49.5212 94.759 49.7904C91.2084 50.7965 87.6067 51.6507 84.0814 52.7122C79.3979 54.1264 74.7883 55.7382 70.1101 57.1717C66.0543 58.4171 61.9726 59.566 57.8858 60.6956C57.1325 60.8981 56.3013 60.8108 55.6083 60.8521L55.6013 60.8644Z" fill="black"/> +<path d="M58.7964 55.3769C59.1289 52.4917 59.2062 49.5819 60.1838 46.8236C61.0276 44.8779 63.1119 44.7732 64.4334 46.2612C64.8162 46.645 65.2237 47.1982 65.6059 47.2715C65.7195 47.3859 66.3474 46.6787 66.725 46.0802C67.2954 45.2745 67.6683 44.2734 67.9498 43.3175C69.0032 44.0388 70.1756 45.0491 71.2602 45.6171C71.6659 45.2388 72.2192 44.562 72.599 44.0871C74.4026 41.8213 75.7225 39.2195 76.811 36.566C77.4748 34.4512 79.1189 32.9013 81.2255 34.6131C81.9777 35.1772 83.9094 36.7393 84.6816 37.3394L83.0622 37.6712C83.6814 36.4692 84.2736 35.2435 84.9296 34.0628L85.5105 33.0265L86.4142 33.6534C86.8341 33.9443 87.3105 34.1373 87.4728 34.1247C87.5456 34.1259 87.5263 34.1311 87.5746 34.1181C87.6036 34.1103 87.6838 33.9852 87.7214 33.7783C87.9643 30.2131 89.1632 26.3908 91.4109 23.5811L91.5825 23.6799C90.4271 26.3928 89.728 29.2627 89.6366 32.197C89.6081 32.7845 89.6581 33.2785 89.5797 34.0658C89.3024 36.5013 86.8475 36.4984 85.3052 35.4218L86.7924 35.0221C86.0226 36.6269 85.1247 38.2559 84.3207 39.8492C83.1405 38.8099 81.2285 37.1286 80.0509 36.099C79.7256 35.8136 79.4665 35.6969 79.3989 35.715C79.3209 35.6946 79.1188 35.7903 78.8914 36.1C78.1838 37.2428 77.4471 38.6627 76.6422 39.7903C75.2639 41.8279 73.7088 43.7474 72.1434 45.6282C71.9312 45.8405 71.855 46.0578 71.5307 46.2381C71.0486 46.4091 70.7408 46.1502 70.5403 46.0591C69.6948 45.5718 68.8211 45.1336 67.9571 44.6928L68.7687 44.3607C68.5561 45.6501 68.1025 46.8904 67.3626 47.9901C67.0999 48.361 66.6603 48.883 65.9011 49.025C64.4132 49.3835 63.4996 47.6409 62.5309 47.0418C62.3793 46.979 62.2653 46.9786 62.1379 47.0439C61.3619 47.4699 61.0339 49.3702 60.5628 50.8534C60.0879 52.3996 59.6007 53.9388 59.004 55.4557L58.8068 55.4155L58.7964 55.3769Z" fill="black"/> +<path d="M160.255 39.2181C160.053 38.857 159.822 38.5031 159.67 38.1204C159.425 37.5066 159.266 36.8566 159.02 36.2428C158.515 34.9718 157.742 34.0619 156.189 34.2352C155.951 34.2641 155.698 34.1197 155.222 33.9969C155.568 33.6142 155.756 33.2387 156.045 33.1304C158.125 32.3504 159.461 31.0867 159.244 28.6747C159.237 28.5592 159.338 28.4292 159.396 28.3209C159.425 28.2703 159.49 28.2342 159.75 27.9959C160.01 28.4075 160.392 28.7686 160.479 29.1874C160.898 31.1228 162.169 32.0977 163.989 32.5743C164.602 32.7332 165.18 33.0148 165.787 33.6286C165.137 33.802 164.494 33.9897 163.844 34.1558C161.562 34.748 160.984 35.4268 160.826 37.716C160.789 38.1998 160.696 38.6764 160.623 39.1603C160.501 39.1819 160.385 39.1964 160.262 39.2181H160.255ZM159.923 35.629C161.028 34.7624 162.053 33.9464 162.941 33.2531C161.952 32.5021 160.927 31.7294 159.937 30.9711C159.251 31.7511 158.529 32.5526 157.901 33.2676C158.645 34.1269 159.316 34.9141 159.93 35.629H159.923Z" fill="black"/> +<path d="M171.961 50.1369C170.194 51.5042 169.055 53.2927 168.545 55.5025C167.368 54.8381 167.7 53.4588 167.058 52.6139C166.4 51.7473 165.729 50.9313 164.306 50.693C165.072 49.9997 166.09 49.4364 166.53 48.5771C166.964 47.7322 166.812 46.5912 167.086 45.4935C168.35 47.3711 169.477 49.3137 171.961 50.1369V50.1369ZM166.249 50.4041C166.971 51.2129 167.577 51.8989 168.199 52.585C168.841 51.8412 169.469 51.119 170.184 50.2958C169.39 49.6603 168.625 49.0609 167.809 48.4038C167.274 49.097 166.798 49.7036 166.249 50.4041V50.4041Z" fill="black"/> +<path d="M152.037 47.0461C149.885 45.2359 148.253 44.2225 147.141 44.0059C147.87 43.2621 148.657 42.6844 149.141 41.9117C149.618 41.1534 149.784 40.2002 150.167 39.1169C151.373 40.7923 152.369 42.4677 154.536 42.9804C152.644 43.7242 152.355 45.3707 152.044 47.0389L152.037 47.0461ZM152.499 43.161C152.348 43.3126 152.196 43.4571 152.044 43.6087C151.503 42.8143 150.954 42.0272 150.383 41.1967C149.964 41.9694 149.589 42.6555 149.199 43.3704C149.972 43.9481 150.694 44.4825 151.416 45.0241C151.777 44.4031 152.131 43.782 152.492 43.161H152.499Z" fill="black"/> +<path d="M174.344 20.3845C175.146 20.3484 176.056 21.2149 176.034 21.9949C176.02 22.6015 175.081 23.3958 174.344 23.4319C173.564 23.468 172.864 22.7531 172.864 21.9154C172.864 21.0633 173.478 20.4278 174.344 20.3917V20.3845ZM174.185 22.9625C174.597 22.6015 175.059 22.3848 175.102 22.1104C175.146 21.836 174.756 21.5038 174.561 21.2005C174.279 21.4171 173.831 21.5905 173.759 21.8649C173.687 22.1248 173.99 22.5004 174.193 22.9698L174.185 22.9625Z" fill="black"/> +<path d="M185.574 45.7174C185.552 46.9234 184.888 47.5155 183.978 47.4794C183.082 47.4505 182.613 46.9522 182.678 46.0929C182.736 45.2913 183.53 44.5547 184.332 44.7786C184.859 44.923 185.27 45.4791 185.566 45.7174H185.574ZM184.05 45.3202C183.797 45.7101 183.516 45.999 183.566 46.0857C183.703 46.3167 183.978 46.4828 184.23 46.6056C184.281 46.6345 184.606 46.3312 184.577 46.2518C184.49 45.9846 184.303 45.7463 184.043 45.3202H184.05Z" fill="black"/> +<path d="M142.801 19.9151C142.757 20.825 141.992 21.5616 141.125 21.5182C140.266 21.4749 139.717 20.9044 139.797 20.1317C139.876 19.3012 140.605 18.5935 141.342 18.6224C142.209 18.6585 142.837 19.2146 142.808 19.9151H142.801ZM141.241 19.2146C140.945 19.6262 140.663 19.8862 140.591 20.1967C140.562 20.3267 141.097 20.7528 141.183 20.7094C141.472 20.5433 141.739 20.2545 141.876 19.9512C141.92 19.8501 141.544 19.5612 141.241 19.2146Z" fill="black"/> +<path d="M159.923 35.629C159.309 34.9141 158.63 34.1269 157.894 33.2676C158.529 32.5526 159.244 31.7511 159.93 30.9711C160.927 31.7222 161.945 32.5021 162.934 33.2531C162.053 33.9464 161.021 34.7624 159.916 35.629H159.923Z" fill="white"/> +<path d="M166.256 50.4041C166.798 49.7036 167.274 49.097 167.816 48.4038C168.639 49.0537 169.397 49.6603 170.192 50.2958C169.477 51.119 168.849 51.8484 168.206 52.585C167.592 51.8989 166.978 51.2129 166.256 50.4041V50.4041Z" fill="white"/> +<path d="M152.499 43.161C152.138 43.782 151.777 44.4031 151.423 45.0241C150.694 44.4825 149.972 43.9409 149.206 43.3704C149.596 42.6555 149.972 41.9694 150.391 41.1967C150.961 42.0272 151.503 42.8143 152.052 43.6087C152.203 43.457 152.355 43.3126 152.507 43.161H152.499Z" fill="white"/> +<path d="M174.185 22.9698C173.983 22.5004 173.68 22.1321 173.752 21.8649C173.831 21.5905 174.272 21.4171 174.553 21.2005C174.756 21.5038 175.138 21.8432 175.095 22.1104C175.052 22.3848 174.597 22.6015 174.178 22.9625L174.185 22.9698Z" fill="white"/> +<path d="M184.05 45.3202C184.302 45.7463 184.49 45.9773 184.584 46.2518C184.613 46.3312 184.288 46.6345 184.237 46.6056C183.985 46.4828 183.718 46.3095 183.573 46.0857C183.523 45.999 183.804 45.7101 184.057 45.3202H184.05Z" fill="white"/> +<path d="M141.241 19.2074C141.544 19.5468 141.92 19.8356 141.876 19.944C141.746 20.2473 141.472 20.5361 141.183 20.7022C141.096 20.7528 140.562 20.3195 140.591 20.1895C140.663 19.8862 140.945 19.6262 141.241 19.2074Z" fill="white"/> +</g> +<defs> +<clipPath id="clip0_9_617"> +<rect width="210" height="197" fill="white"/> +</clipPath> +</defs> +</svg> diff --git a/frontend/app/svg/ca-no-live-sessions.svg b/frontend/app/svg/ca-no-live-sessions.svg index 979060448..927e914f7 100644 --- a/frontend/app/svg/ca-no-live-sessions.svg +++ b/frontend/app/svg/ca-no-live-sessions.svg @@ -1,15 +1,69 @@ -<svg viewBox="0 0 250 100" fill="none" xmlns="http://www.w3.org/2000/svg"> -<rect width="250" height="100" rx="13.1579" fill="#3EAAAF" fill-opacity="0.08"/> -<rect opacity="0.6" x="86.8421" y="28.579" width="138.158" height="14.4737" rx="6.57895" fill="#3EAAAF" fill-opacity="0.5"/> -<rect opacity="0.3" x="86.8421" y="54.8948" width="46.0526" height="14.4737" rx="6.57895" fill="#3EAAAF" fill-opacity="0.5"/> -<g clip-path="url(#clip0_2_44)"> -<path fill-rule="evenodd" clip-rule="evenodd" d="M71.047 68.047C65.9884 73.1055 59.1276 75.9474 51.9737 75.9474C44.8198 75.9474 37.959 73.1055 32.9004 68.047C27.8419 62.9884 25 56.1276 25 48.9737C25 41.8198 27.8419 34.959 32.9004 29.9004C37.959 24.8419 44.8198 22 51.9737 22C57.1003 22 62.0765 23.4594 66.3467 26.1483C65.4733 27.1904 64.9474 28.5338 64.9474 30C64.9474 33.3137 67.6336 36 70.9474 36C72.4136 36 73.7569 35.4741 74.7991 34.6006C77.4879 38.8709 78.9474 43.847 78.9474 48.9737C78.9474 56.1276 76.1055 62.9884 71.047 68.047ZM45.2303 48.9737C47.0914 48.9737 48.602 46.7079 48.602 43.9161C48.602 41.1243 47.0914 38.8586 45.2303 38.8586C43.3691 38.8586 41.8586 41.1243 41.8586 43.9161C41.8586 46.7079 43.3691 48.9737 45.2303 48.9737ZM38.6623 62.8968C38.7781 63.3287 39.0606 63.6969 39.4478 63.9205C39.835 64.144 40.2951 64.2046 40.727 64.0889C41.1589 63.9732 41.5271 63.6907 41.7507 63.3035C42.7861 61.5086 44.2762 60.0182 46.0709 58.9823C47.8656 57.9465 49.9015 57.4017 51.9737 57.403C54.0458 57.4017 56.0818 57.9465 57.8764 58.9823C59.6711 60.0182 61.1612 61.5086 62.1967 63.3035C62.4219 63.6875 62.7898 63.9669 63.2202 64.0807C63.6506 64.1945 64.1085 64.1334 64.494 63.9108C64.8796 63.6882 65.1614 63.3222 65.278 62.8926C65.3947 62.4629 65.3366 62.0046 65.1166 61.6176C63.7847 59.3106 61.8688 57.395 59.5617 56.0633C57.2546 54.7315 54.6376 54.0307 51.9737 54.0312C49.3097 54.03 46.6924 54.7306 44.3851 56.0624C42.0779 57.3941 40.1622 59.3102 38.8308 61.6176C38.6072 62.0048 38.5466 62.465 38.6623 62.8968ZM55.3454 43.9161C55.3454 46.7079 56.8559 48.9737 58.7171 48.9737C60.5783 48.9737 62.0888 46.7079 62.0888 43.9161C62.0888 41.1243 60.5783 38.8586 58.7171 38.8586C56.8559 38.8586 55.3454 41.1243 55.3454 43.9161Z" fill="#3EAAAF" fill-opacity="0.5"/> -</g> -<circle opacity="0.7" cx="70.9474" cy="30" r="5" fill="#3EAAAF" fill-opacity="0.5"/> -<path fill-rule="evenodd" clip-rule="evenodd" d="M145.872 57.1005C145.617 56.9984 145.337 56.9734 145.068 57.0286C144.799 57.0838 144.552 57.2167 144.358 57.4109C144.164 57.6051 144.031 57.852 143.976 58.121C143.921 58.39 143.946 58.6693 144.048 58.9242L149.659 72.9529C149.76 73.204 149.931 73.4207 150.152 73.5769C150.372 73.733 150.634 73.822 150.904 73.8331C151.174 73.8443 151.442 73.777 151.675 73.6395C151.908 73.502 152.096 73.3001 152.217 73.0582L154.153 69.1876L158.387 73.4243C158.65 73.6874 159.007 73.8351 159.379 73.8349C159.751 73.8348 160.108 73.6869 160.371 73.4236C160.634 73.1604 160.782 72.8034 160.782 72.4313C160.781 72.0591 160.633 71.7023 160.37 71.4392L156.135 67.2026L160.007 65.268C160.248 65.1469 160.45 64.9586 160.587 64.7258C160.724 64.4929 160.791 64.2255 160.78 63.9555C160.768 63.6856 160.679 63.4246 160.523 63.204C160.367 62.9834 160.151 62.8126 159.9 62.712L145.872 57.1005Z" fill="#3EAAAF" fill-opacity="0.5"/> -<defs> -<clipPath id="clip0_2_44"> -<rect width="53.9474" height="53.9474" fill="white" transform="translate(25 22)"/> -</clipPath> -</defs> +<svg width="210" height="197" viewBox="0 0 210 197" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect width="210" height="197" fill="white"/> +<path d="M11.9121 184.085C15.1657 182.711 16.0839 182.86 20.442 181.729C29.751 179.313 30.6497 179.834 41.9986 178.681C67.4061 176.1 95.0132 175.802 121.221 177.164C138.412 178.058 154.55 179.814 170.61 181.915C178.164 182.904 187.077 184.304 197.907 185.753" stroke="black" stroke-linecap="round"/> +<rect x="109.664" y="64.8859" width="88.2438" height="80.702" rx="4" fill="#F4F5FF"/> +<rect x="7.80273" y="8.6078" width="88.2438" height="80.702" rx="4" fill="#FDF9F3"/> +<path d="M173.29 99.9065C173.245 99.9603 173.194 100.014 173.149 100.068C171.433 101.406 169.968 103.029 169.01 105.237L169.049 105.183C168.547 105.652 168.001 106.075 167.558 106.614C166.169 108.267 164.82 109.967 163.457 111.644C162.602 112.706 161.754 113.175 160.475 112.375C159.447 111.736 158.232 111.352 157.069 111.229C153.348 110.821 149.614 110.567 145.88 110.29C145.025 110.229 144.157 110.283 143.296 110.283C143.45 109.837 143.63 109.675 143.83 109.583C146.49 108.306 148.913 106.537 151.227 104.522C154.98 101.26 157.892 97.0528 160.61 92.6454C161.06 91.9147 161.503 91.3993 162.249 91.1917C167.198 89.7841 171.504 86.8766 175.411 83.023C177.159 81.3 178.368 79.1848 179.132 76.6465C179.884 74.139 180.096 71.5391 180.231 68.9085C180.488 63.9473 178.927 59.9014 175.996 56.5324C175.405 55.8556 174.711 55.3171 174.055 54.7095C175.148 46.0177 173.927 38.349 167.378 32.8186C170.334 31.8187 172.628 32.6956 174.569 35.0415C175.36 36.003 176.073 37.0491 176.832 38.0567C177.577 39.0413 178.477 39.4874 179.55 38.9721C180.733 38.4029 181.504 37.4029 181.607 35.7953C181.78 33.0417 181.08 30.7803 179.139 29.1266C175.469 25.996 171.394 25.3268 167.095 26.4114C165.964 26.696 164.935 27.5267 163.804 28.1343C162.911 27.4344 161.992 26.7036 160.809 25.7729C161.548 25.4499 161.96 25.2345 162.39 25.0807C165.167 24.0654 168.007 24.0961 170.867 24.3576C179.332 25.1345 187.237 28.3035 194.955 32.2879C195.604 32.6263 196.247 33.0263 196.838 33.4955C199.017 35.2108 200.437 37.4568 200.791 40.7027C201.421 46.4869 200.547 51.925 198.304 57.0016C196.337 61.4629 194.107 65.7472 192.025 70.1315C191.652 70.9084 191.414 71.7776 191.118 72.6006C190.771 73.1467 190.424 73.7005 190.071 74.2466C189.582 75.1927 189.1 76.1388 188.612 77.0772L188.651 77.0234C187.532 77.8233 186.877 79.0463 186.472 80.4924L186.504 80.4385C186.356 80.5385 186.163 80.5924 186.067 80.7308C184.055 83.6076 182.057 86.492 180.064 89.3918C179.891 89.6456 179.814 89.9994 179.692 90.3071L179.73 90.2533C179.12 91.1224 178.515 91.9916 177.905 92.8608L177.943 92.8069L176.118 95.4222L176.157 95.3683C175.61 96.0913 175.058 96.8221 174.511 97.5451C174.46 97.5989 174.415 97.6528 174.364 97.7066C173.997 98.4373 173.631 99.1681 173.271 99.8988L173.29 99.9065Z" fill="white"/> +<path d="M191.119 72.5929C191.421 71.7699 191.658 70.9007 192.025 70.1238C194.114 65.7395 196.344 61.4475 198.304 56.9939C200.54 51.9174 201.421 46.4793 200.791 40.695C200.438 37.4414 199.017 35.2031 196.839 33.4878C196.247 33.0186 195.605 32.6186 194.955 32.2802C187.237 28.2882 179.325 25.1191 170.867 24.35C168.008 24.0884 165.173 24.0577 162.39 25.073C161.96 25.2268 161.549 25.4422 160.809 25.7652C161.986 26.696 162.911 27.419 163.804 28.1266C164.936 27.519 165.964 26.6883 167.095 26.4037C171.395 25.3191 175.469 25.9883 179.139 29.1189C181.08 30.7726 181.787 33.034 181.607 35.7877C181.504 37.3952 180.726 38.3952 179.55 38.9644C178.483 39.4797 177.577 39.0336 176.832 38.049C176.08 37.0491 175.366 35.9953 174.569 35.0339C172.628 32.6802 170.334 31.811 167.378 32.8109C173.927 38.349 175.141 46.01 174.055 54.7018C174.711 55.3094 175.405 55.8555 175.996 56.5247C178.927 59.8937 180.489 63.9396 180.231 68.9008C180.096 71.5391 179.884 74.1313 179.132 76.6388C178.368 79.1771 177.159 81.2923 175.411 83.0153C171.504 86.8612 167.204 89.7687 162.249 91.184C161.504 91.3993 161.06 91.907 160.61 92.6377C157.885 97.0528 154.98 101.253 151.227 104.514C148.907 106.529 146.49 108.298 143.83 109.575C143.63 109.675 143.45 109.837 143.296 110.275C144.157 110.275 145.019 110.221 145.88 110.283C149.614 110.567 153.348 110.813 157.069 111.221C158.232 111.352 159.447 111.729 160.475 112.367C161.754 113.167 162.603 112.698 163.457 111.636C164.82 109.952 166.169 108.26 167.558 106.606C168.008 106.068 168.547 105.652 169.049 105.175C168.837 105.583 168.676 106.052 168.406 106.398C166.491 108.821 164.556 111.221 162.532 113.744C163.657 117.851 163.04 122.251 162.48 126.666C162.288 128.205 161.388 129.005 160.366 129.589C159.299 130.189 158.168 130.658 157.037 131.058C152.808 132.535 148.476 133.35 144.093 133.85C140.256 134.289 136.432 134.85 132.332 135.396C131.831 143.719 131.451 152.195 131.014 160.856C131.522 160.933 132.036 160.964 132.525 161.087C133.81 161.418 134.337 162.325 134.054 163.848C133.637 166.125 133.155 168.387 132.66 170.848C134.838 171.148 136.959 171.356 139.061 171.748C144.222 172.709 149.37 173.709 154.511 174.786C155.726 175.04 156.908 175.578 158.072 176.109C158.541 176.324 159.164 176.794 159.261 177.271C159.511 178.586 160.315 179.04 161.15 179.586C161.934 180.093 162.397 180.847 162.166 182.039C162.082 182.478 162.14 182.993 162.256 183.432C163.342 187.524 160.803 191.277 157.191 190.839C153.393 190.377 152.191 186.631 153.226 183.593C153.823 181.839 154.183 180.163 156.163 179.363C147.461 177.578 139.151 175.871 130.706 174.14C130.243 177.463 129.761 180.863 129.266 184.432C129.813 184.678 130.41 184.939 131.002 185.224C132.293 185.832 133.02 186.908 133.026 188.624C133.026 189.262 133.103 189.893 133.148 190.531C133.386 193.954 131.252 196.639 128.264 196.669C125.262 196.7 123.244 194.116 123.174 190.7C123.116 188.062 124.266 186.662 125.905 185.478C126.355 185.155 126.85 184.901 127.403 184.562C127.158 182.747 126.876 180.947 126.689 179.14C126.496 177.324 126.394 175.501 126.239 173.486C122.505 174.863 119.028 176.178 115.532 177.44C112.081 178.678 108.553 179.647 105.288 181.578C106.412 182.324 107.537 183.001 108.591 183.801C109.266 184.316 109.735 185.055 109.427 186.147C109.375 186.339 109.42 186.57 109.427 186.778C109.619 192.062 105.705 195.6 101.477 193.954C98.3595 192.739 97.7168 189.085 98.9315 186.831C99.0922 186.531 99.33 186.208 99.33 185.893C99.3364 183.662 100.596 182.663 102.151 181.77C101.952 180.078 102.903 179.393 104.067 178.778C109.459 175.948 115.031 173.755 120.667 171.763C121.914 171.325 123.148 170.848 124.504 170.348C124.183 168.579 123.887 166.94 123.585 165.294C123.084 162.548 123.251 162.264 125.59 161.949C125.822 161.918 126.046 161.833 126.522 161.71C126.708 153.103 126.901 144.465 127.094 135.581C126.284 135.504 125.719 135.381 125.16 135.404C120.275 135.619 115.442 134.781 110.59 134.473C105.725 134.166 100.892 133.497 96.0459 133.143C91.5728 132.82 87.2218 131.797 82.8193 131.081C79.5288 130.543 78.1534 128.72 77.6007 124.659C77.1059 120.997 76.8424 117.328 78.1085 113.752C78.5648 112.467 79.2267 111.567 80.3322 111.221C81.881 110.729 83.4428 110.337 84.9981 109.867C85.7886 109.629 86.5598 109.29 87.3375 109.006C88.9313 109.198 90.4995 109.006 91.9584 108.652C92.0933 108.352 92.1898 108.237 92.2026 108.106C93.0317 100.13 91.4314 93.0223 86.894 86.9919C86.3284 86.2382 85.5315 85.7382 84.8953 85.169C83.3785 86.6074 83.0572 87.3227 82.8386 89.6995C82.4723 93.6453 83.0572 97.4836 83.8412 101.291C84.2461 103.245 85.0238 104.983 86.0906 106.56C86.5984 107.314 86.9454 108.221 87.3696 109.06C84.9531 106.552 83.3849 103.422 82.8772 99.668C82.4145 96.2529 81.7782 92.8454 82.2602 89.3072C82.5237 87.3765 83.1857 85.8151 84.2461 84.4306C84.4646 84.1537 84.8374 83.9152 85.1459 83.8998C85.8528 83.8614 86.5919 83.8383 87.2668 84.046C93.1923 85.9074 97.4726 89.9148 99.1501 97.2605C99.1822 97.3989 99.2336 97.522 99.3557 97.9066C99.8634 97.9451 100.468 97.9912 101.271 98.0604C100.532 95.876 99.857 93.9915 99.2464 92.0762C98.186 88.738 97.3119 85.3459 97.524 81.7077C97.7554 77.8233 98.623 74.1082 100.21 70.7546C101.522 67.9932 103.231 65.5011 104.767 62.8935C104.999 62.5013 105.262 62.1321 105.577 61.6475C102.46 59.9784 99.4392 58.4015 96.4443 56.7555C93.5008 55.1402 90.2488 54.2403 87.6138 51.3866C88.2308 49.9559 88.8478 48.5253 89.529 46.9561C88.9313 46.5331 88.2951 46.0793 87.6138 45.6024C84.2526 48.6022 80.5057 47.7946 76.8617 46.9254C73.976 46.2331 72.4399 43.5794 72.0929 40.2335C71.6944 36.3722 71.1224 32.5109 71.3024 28.5727C71.3731 26.9652 71.7973 26.0191 73.0569 25.3037C75.1264 24.1192 77.318 23.95 79.4967 24.0038C82.8194 24.0807 86.1421 24.35 89.3812 25.3883C89.7797 25.5191 90.1653 25.6883 90.5638 25.8345C91.5728 26.2037 92.0162 26.9729 91.8105 28.2497C91.7527 28.5881 91.8041 28.9573 91.8041 29.4342C92.6396 30.0188 93.5587 30.3342 94.5934 30.3419C96.8685 30.3496 97.7233 31.2341 98.0125 33.8878C98.0832 34.5647 98.2374 34.9723 98.7259 35.4261C101.387 37.926 103.867 40.7335 106.637 43.0333C109.157 45.1255 111.972 46.7023 114.658 48.5099C115.622 49.156 116.348 48.9099 117.171 47.9561C122.351 41.9719 127.84 36.4415 134.087 32.0571C137.326 29.7881 140.687 27.819 144.222 26.3421C149.19 24.2653 154.267 23.6192 159.453 25.173C159.871 25.296 160.398 25.1961 160.803 24.9961C164.196 23.3039 167.725 23.1808 171.311 23.5115C178.246 24.15 184.852 26.3729 191.279 29.4419C192.989 30.2573 194.711 31.0572 196.344 32.0571C199.454 33.957 201.344 36.8568 201.678 41.2104C202.076 46.4485 201.402 51.4328 199.563 56.1632C197.43 61.6552 194.782 66.801 191.858 71.7391C191.658 72.0775 191.369 72.3391 191.119 72.6391V72.5929ZM125.873 186.708C128.225 185.993 130.295 186.624 132.068 188.716C132.377 187.662 131.959 187.07 131.432 186.578C129.838 185.085 127.666 185.162 125.905 186.739C125.34 187.116 124.774 187.501 124.208 187.878C124.305 188.031 124.395 188.178 124.491 188.331C124.954 187.785 125.417 187.239 125.879 186.701L125.873 186.708ZM106.11 104.929C104.979 103.406 103.739 101.976 102.756 100.33C101.894 98.8835 100.962 98.4681 99.5549 99.0911C99.9534 102.176 99.8056 105.122 98.186 107.791C100.461 107.791 102.524 107.791 104.594 107.791C105.815 107.791 106.38 106.937 106.592 105.614C107.261 105.206 107.987 104.898 108.591 104.383C110.834 102.452 113.386 101.76 116.104 101.553C111.438 103.191 106.843 104.922 105.166 111.413C107.839 111.806 110.352 112.206 112.871 112.544C115.429 112.883 117.756 114.067 120.07 115.436C120.558 114.444 120.924 113.552 121.413 112.759C122.165 111.536 122.981 110.506 124.51 111.136C125.147 111.398 125.924 111.198 126.638 111.144C130.43 110.852 134.228 110.629 138.013 110.19C140.616 109.89 143.174 109.267 145.539 107.775C149.357 105.368 152.821 102.399 155.707 98.545C157.204 96.5452 158.586 94.4222 160.263 92.007C157.133 92.607 154.646 94.3761 151.825 94.2992C155.09 92.9762 158.361 91.707 161.69 90.6994C165.842 89.4379 169.634 87.315 173.008 84.1998C174.409 82.9076 175.983 81.5846 176.845 79.8232C179.814 73.7697 180.803 67.3702 177.937 60.7783C177.057 58.7554 175.784 57.1247 173.94 55.9171C173.747 56.6786 173.567 57.1939 173.49 57.7323C173.04 60.9168 172.146 63.9012 170.945 66.7856C170.257 68.4316 169.756 70.1931 169.248 71.9391C169.049 72.6237 169.074 73.4082 168.997 74.1851C165.681 77.3695 155.552 79.7617 150.276 78.3848C152.249 77.5772 154.036 78.3694 155.944 77.5772C155.25 77.3234 154.858 77.1849 154.479 77.0388C140.089 71.5853 127.37 63.1243 118.013 48.2791C117.608 48.7945 117.132 49.4175 116.644 50.0175C112.679 54.8172 108.604 59.4938 104.78 64.4473C101.586 68.5855 99.5035 73.5544 98.5781 79.1155C98.3338 80.5693 98.1217 82.1153 98.2888 83.5537C99.0022 89.8302 100.705 95.6837 103.861 100.868C104.664 102.183 105.371 103.575 106.123 104.937L106.11 104.929ZM105.236 108.421C104.844 108.352 104.626 108.267 104.407 108.275C97.042 108.606 89.7283 109.429 82.4787 111.152C79.7537 111.798 78.462 113.567 78.1085 116.621C77.9671 117.813 77.8964 119.028 77.9092 120.228C77.9221 121.574 78.102 122.92 78.1406 124.274C78.2434 127.743 80.4993 130.004 82.9608 130.358C85.3773 130.704 87.7681 131.304 90.1846 131.643C92.8389 132.012 95.5253 132.035 98.1603 132.512C101.875 133.181 105.59 133.55 109.337 133.52C109.69 133.52 110.05 133.589 110.404 133.635C115.905 134.35 121.406 134.82 126.946 134.52C128.964 134.412 130.989 134.389 133 134.227C141.182 133.581 149.357 132.774 157.288 130.074C158.515 129.658 159.678 128.851 160.764 128.028C161.253 127.658 161.722 126.835 161.786 126.159C162.069 123.128 162.281 120.074 162.358 117.028C162.41 114.967 161.516 113.506 159.794 112.944C158.766 112.606 157.737 112.244 156.69 112.052C155.167 111.767 153.631 111.521 152.088 111.436C147.879 111.19 143.656 110.798 139.447 110.898C134.292 111.021 129.138 111.59 123.983 111.96C122.415 112.075 120.532 114.813 120.95 116.613C121.548 119.205 120.95 121.682 120.687 124.197C120.558 125.466 119.896 126.128 118.829 126.435C114.138 127.774 109.414 127.843 104.677 126.943C102.743 126.582 100.815 126.151 98.8994 125.682C97.2927 125.289 95.7052 124.782 94.0086 124.297C94.3106 121.997 95.1332 120.182 95.9238 118.382C97.6719 114.413 100.076 111.467 104.247 111.452C104.555 110.49 104.87 109.529 105.223 108.437L105.236 108.421ZM160.482 53.5634C149.183 51.1251 141.034 43.4948 134.491 32.9263C128.585 36.8953 123.611 41.9642 118.842 47.3946C120.597 51.0328 122.807 53.8941 125.25 56.517C131.811 63.5627 139.215 69.0162 147.287 73.2006C147.705 73.4159 148.161 73.5467 148.573 73.7082C152.185 66.8087 155.61 59.9091 160.488 53.5711L160.482 53.5634ZM135.025 32.5186C135.301 33.0263 135.462 33.4032 135.68 33.7109C138.47 37.6798 141.368 41.4719 144.974 44.4871C147.711 46.7792 150.424 49.1098 153.618 50.2944C157.288 51.6635 161.022 52.8404 164.781 53.8095C166.549 54.2633 168.445 54.2403 170.263 54.1095C173.695 53.8634 173.657 54.2787 173.676 49.5944C173.689 47.1869 173.599 44.7794 172.673 42.4872C171.883 42.3872 171.112 42.3641 170.366 42.1718C168.342 41.6642 166.259 41.3334 164.331 40.5027C157.429 37.5337 151.58 32.5186 146.085 26.496C142.339 27.9959 138.598 29.8573 135.025 32.511V32.5186ZM149.113 74.2159C151.304 75.0466 153.123 75.685 154.903 76.4311C159.068 78.1694 162.982 76.8926 166.831 75.0466C167.41 74.7697 167.995 74.0236 168.245 73.3236C169.332 70.2546 170.263 67.1163 171.317 64.0319C172.352 61.0014 172.821 57.8477 173.245 54.4556C169.344 55.2633 165.694 55.1479 162.088 54.0787C161.169 53.8095 160.604 54.3403 160.128 55.1171C158.014 58.5477 155.867 61.9475 153.836 65.4395C152.255 68.1624 150.822 71.0238 149.113 74.2082V74.2159ZM88.4622 51.4558C89.9018 52.325 91.245 53.2096 92.6332 53.9634C95.8145 55.6863 99.0215 57.3555 102.222 59.04C103.141 59.5245 104.099 59.9245 105.011 60.4245C105.905 60.9168 106.528 60.6398 107.196 59.8014C109.619 56.7786 112.1 53.8326 114.555 50.8482C114.825 50.5174 115.044 50.1329 115.416 49.579C108.61 46.8331 103.713 41.0335 98.4559 35.9723C96.2901 38.126 89.5097 48.5714 88.4557 51.4635L88.4622 51.4558ZM94.7284 123.682C95.1333 123.82 95.3453 123.905 95.5638 123.959C98.7066 124.666 101.836 125.474 104.992 126.051C109.472 126.874 113.958 126.866 118.431 125.774C119.549 125.497 120.095 124.866 120.211 123.566C120.359 121.874 120.552 120.19 120.661 118.498C120.731 117.421 120.359 116.528 119.491 116.098C117.692 115.213 115.924 114.159 114.054 113.575C110.596 112.49 107.036 112.298 103.469 112.444C102.723 112.475 101.914 112.683 101.264 113.106C97.7426 115.39 96.2451 119.459 94.7219 123.682H94.7284ZM71.7909 27.5651C72.0608 31.4802 72.2921 35.08 72.5749 38.6721C72.6456 39.5797 72.877 40.4797 73.0891 41.3642C73.7896 44.2794 75.4927 45.887 77.9285 46.31C79.5095 46.5869 81.1162 46.6254 82.7165 46.7638C85.4737 46.9946 87.3053 45.2486 88.7192 42.6641C89.7218 40.8335 90.1203 38.7951 90.3002 36.6338C90.5252 33.9647 90.9236 31.3187 91.3093 28.2189C89.3683 29.8034 87.4789 29.965 85.5765 29.965C81.534 29.965 77.5043 29.7881 73.6289 28.1497C73.1341 27.942 72.6135 27.8266 71.7909 27.5728V27.5651ZM163.682 31.0034C165.308 32.2494 167.031 31.9033 168.715 31.7264C171.639 31.4264 173.901 32.7725 175.707 35.4569C176.266 36.2876 176.902 37.0337 177.506 37.8106C178.194 38.6875 178.972 38.6413 179.762 38.0644C180.566 37.4722 181.073 36.6414 181.086 35.4569C181.118 33.0109 180.373 31.0726 178.644 29.6573C175.482 27.0728 171.992 26.4652 168.284 26.9575C166.324 27.219 164.736 28.4497 163.175 29.7419C163.02 29.2035 163.007 28.5497 162.738 28.3112C161.054 26.8267 159.248 25.6883 157.101 25.396C154.119 24.9884 151.182 25.2268 148.258 25.8883C147.924 25.9652 147.602 26.1114 146.966 26.3421C147.622 27.1036 148.059 27.742 148.592 28.2189C150.739 30.1034 152.911 31.9648 155.096 33.7955C159.068 37.1183 163.38 39.6105 168.104 40.9565C169.441 41.3334 170.842 41.4104 172.449 41.6565C171.716 38.8028 170.315 36.9876 168.92 35.1569C167.481 33.2801 165.366 32.6417 163.695 31.011L163.682 31.0034ZM92.7103 108.544C93.7258 108.406 94.5356 108.198 95.3389 108.214C96.8621 108.244 97.8454 107.298 98.3724 105.76C98.7451 104.668 99.0151 103.429 98.9958 102.26C98.8544 93.4992 94.4456 87.3304 87.5624 84.8382C87.1575 84.6921 86.6883 84.8229 86.0007 84.8229C91.6949 91.4224 93.8157 99.2834 92.7039 108.552L92.7103 108.544ZM130.263 161.172C130.693 152.411 131.111 143.888 131.541 135.15C130.14 135.266 128.984 135.358 127.807 135.458C127.64 144.242 127.48 152.834 127.313 161.641C128.45 161.464 129.363 161.318 130.256 161.179L130.263 161.172ZM130.995 172.786C131.946 169.902 132.775 167.048 133.187 164.033C133.405 162.464 133.09 161.833 131.766 161.941C129.189 162.156 126.625 162.564 124.08 162.895C123.964 165.594 125.269 170.456 126.644 172.509C128.084 171.209 129.498 171.825 130.989 172.786H130.995ZM131.786 173.402C134.922 174.094 137.949 174.809 140.989 175.425C146.079 176.455 151.182 177.424 156.285 178.394C157.069 178.54 157.892 178.863 158.888 178.047C158.47 177.586 158.2 177.109 157.834 176.924C156.908 176.448 155.964 175.948 154.987 175.732C150.199 174.686 145.411 173.671 140.603 172.763C137.859 172.248 135.089 171.971 132.261 171.571C132.081 172.248 131.953 172.763 131.786 173.402V173.402ZM91.3414 26.9729C90.2745 26.5037 89.4648 26.0652 88.6228 25.8037C87.7681 25.5422 86.8811 25.4114 86.0007 25.3114C82.7615 24.9499 79.5288 24.5499 76.264 25.0268C74.8886 25.2268 73.6739 25.7191 72.215 26.8959C74.3616 27.5959 76.129 28.4804 77.9606 28.7035C81.0712 29.0881 84.2204 29.1189 87.3503 29.1804C88.7964 29.2112 90.1074 28.6112 91.3414 26.9729V26.9729ZM103.398 193.408C104.921 193.431 106.509 192.662 107.569 191.393C108.308 190.5 108.752 189.47 108.758 188.185C108.765 187.178 108.578 186.416 107.64 186.085C107.158 185.908 106.747 185.432 106.31 185.078C104.247 183.355 100.622 184.47 99.5356 187.17C98.321 190.185 100.288 193.369 103.398 193.423V193.408ZM157.506 190.008C158.779 189.908 159.98 189.401 160.944 188.239C162.011 186.955 162.442 184.724 161.587 183.609C160.829 182.616 159.788 181.847 158.766 181.247C158.264 180.955 157.506 181.216 156.876 181.332C156.613 181.378 156.388 181.709 156.144 181.901C155.707 182.255 155.269 182.632 154.82 182.962C153.579 183.87 153.129 185.27 153.573 186.916C154.068 188.77 155.623 190.024 157.506 190.016V190.008ZM128.039 195.723C129.864 195.739 131.284 194.831 132.011 192.823C132.287 192.062 132.441 190.816 132.107 190.224C130.385 187.147 127.242 186.093 125.172 188.608C123.804 190.277 123.463 191.623 124.568 193.554C125.391 194.992 126.567 195.715 128.032 195.731L128.039 195.723ZM95.5767 35.5261C95.0176 36.0492 94.4327 36.3107 94.3235 36.7491C93.8607 38.5875 92.7489 39.4951 91.3671 40.1412C90.9944 40.3181 90.5252 40.5104 90.3324 40.8719C89.6061 42.2103 88.9763 43.6256 88.2565 45.1332C89.2012 45.487 89.9532 46.4331 90.7758 45.2639C92.9224 42.2257 95.069 39.2028 97.1577 36.1107C97.9739 34.9031 97.6911 33.5263 97.087 32.311C96.5022 31.1418 95.4867 30.7957 94.4456 31.0957C93.8864 31.2572 93.1088 31.7956 92.9995 32.334C92.8838 32.8802 93.3595 33.7647 93.7772 34.257C94.2592 34.8185 94.9597 35.1031 95.5831 35.5184L95.5767 35.5261ZM102.91 180.847L103.07 181.263C103.597 181.132 104.144 181.055 104.658 180.863C109.176 179.178 113.694 177.501 118.193 175.763C120.5 174.871 122.775 173.871 125.063 172.902C125.205 172.84 125.301 172.609 125.449 172.409C125.25 171.979 125.057 171.556 124.838 171.079C124.498 171.194 124.221 171.271 123.945 171.379C118.488 173.517 113.026 175.625 107.595 177.824C106.2 178.386 104.883 179.224 103.552 179.993C103.289 180.147 103.122 180.555 102.916 180.847H102.91ZM128.155 183.447C128.264 183.432 128.367 183.424 128.476 183.409C128.559 183.101 128.682 182.809 128.726 182.493C129.093 179.778 129.472 177.071 129.787 174.348C129.935 173.055 129.658 172.717 128.598 172.625C127.39 172.517 126.811 173.125 126.933 174.632C127.113 176.878 127.39 179.109 127.673 181.34C127.762 182.055 127.987 182.747 128.155 183.447V183.447ZM93.8607 35.5569C92.1062 33.6186 91.9969 33.1263 92.7617 30.6418C92.4083 30.6188 92.0934 30.6034 91.5278 30.5726C91.6113 31.711 91.4442 32.8494 91.7977 33.6724C92.5754 35.4877 92.6782 35.8723 91.5278 37.3414C91.0715 37.926 90.6345 38.4413 90.9622 39.4105C92.6075 38.8798 93.6486 37.7644 93.8607 35.5569V35.5569ZM100.101 184.878C100.57 184.724 100.744 184.716 100.872 184.616C103.064 182.986 105.166 182.863 107.196 185.024C107.621 185.478 108.161 185.816 108.81 185.47C107.421 182.032 102.338 181.516 100.108 184.878H100.101ZM153.984 182.439C155.449 181.601 156.542 180.77 157.731 180.34C159.113 179.84 160.077 181.24 161.163 182.07C161.754 181.109 161.259 180.701 160.758 180.293C158.894 178.794 155.655 179.686 153.984 182.439V182.439ZM91.8041 34.757C91.6435 34.7262 91.4828 34.6954 91.3221 34.657C91.2836 35.18 91.2514 35.7107 91.2128 36.2338C91.3414 36.2492 91.4635 36.2645 91.592 36.2799C91.6627 35.7723 91.7334 35.2646 91.8041 34.757V34.757Z" fill="black"/> +<path d="M77.8064 99.2219C78.7319 98.2989 79.7795 98.3296 80.7949 98.8604C81.1548 99.0527 81.5147 99.5988 81.5983 100.06C81.6433 100.306 81.1548 100.906 80.827 101.006C78.8283 101.645 76.8295 102.452 74.7922 102.699C69.9528 103.268 65.0876 103.575 60.2289 103.945C58.2302 104.098 56.225 104.137 54.0848 104.237C54.6311 131.635 55.1645 158.756 55.7044 185.985C57.896 185.985 60.0104 185.939 62.1249 185.993C66.8165 186.116 71.5145 186.231 76.1161 187.531C77.3758 187.885 78.6419 188.37 79.8116 189.031C81.2512 189.847 81.3733 191.293 80.0237 192.277C78.7255 193.223 77.273 193.969 75.8077 194.469C72.0094 195.769 68.0761 195.931 64.1622 196.139C58.3459 196.454 52.5295 196.731 46.7132 196.892C41.7259 197.031 36.7387 196.985 31.745 197C30.164 197 28.6794 196.492 27.3748 195.415C26.1408 194.4 26.1151 193.231 27.2783 192.139C27.7411 191.708 28.2873 191.393 28.8272 191.108C34.7206 188.039 40.9162 186.531 47.3045 185.993C47.8957 185.947 48.4934 186.047 49.0847 186.055C49.6631 186.07 50.2416 186.055 50.9357 186.055V104.437C50.3058 104.368 49.7917 104.268 49.2839 104.252C46.4947 104.16 43.7055 103.983 40.9162 104.037C37.7734 104.091 34.6307 104.352 31.4879 104.522C28.0496 104.706 24.6112 104.914 21.1664 105.06C20.6715 105.083 20.1767 104.868 19.6625 104.522C21.8027 102.691 24.3284 102.329 26.9506 101.491C26.5971 101.106 26.4043 100.776 26.1344 100.614C23.1202 98.8219 21.5199 95.4991 20.0738 92.1839C16.3848 83.746 12.8757 75.2004 9.32166 66.6779C8.89749 65.6703 8.54402 64.5857 8.33194 63.4935C7.89491 61.2859 8.65327 59.8399 10.5363 59.5015C12.8822 59.0861 15.2537 58.7554 17.6188 58.6554C23.4351 58.4169 29.2578 58.3092 35.0741 58.2015C38.0434 58.1477 41.019 58.2323 43.9882 58.2169C46.2569 58.2015 47.8829 59.163 49.014 61.7244C52.7159 70.0777 56.2121 78.5463 59.2777 87.2611C60.6531 91.1609 60.6981 91.207 64.1172 91.3378C68.6288 91.5147 73.1405 91.5609 77.6522 91.6685C77.89 91.6685 78.1278 91.6685 78.3655 91.6916C79.5931 91.8608 79.9337 92.4993 79.3681 93.8607C78.8411 95.1299 78.1663 96.3067 77.5622 97.5297C77.3308 97.9989 77.1187 98.4758 76.7974 99.168C77.2858 99.168 77.5686 99.168 77.8514 99.168C76.6303 99.4219 75.4092 99.8526 74.1816 99.8834C68.963 100.022 63.7315 99.9449 58.5129 100.137C51.5719 100.399 44.6309 99.7757 37.6963 100.491C33.0176 100.976 28.3709 101.576 23.8271 103.037C23.0623 103.283 22.3232 103.645 21.5713 103.952C23.4865 104.183 25.331 104.314 27.1562 104.16C32.8376 103.691 38.4868 103.014 44.2131 103.268C52.6966 103.637 61.1865 103.106 69.6571 102.514C72.5492 102.314 75.4285 101.737 78.3013 101.214C79.1882 101.053 80.0173 100.491 81.0327 100.037C80.3215 99.5142 79.2439 99.2296 77.8 99.1834L77.8064 99.2219ZM79.1625 92.6685C78.6033 92.5685 78.2692 92.4608 77.9414 92.4531C73.2498 92.307 68.5581 91.9608 63.873 92.1147C61.0965 92.207 59.5284 90.9455 58.7315 87.938C58.6608 87.6688 58.5322 87.4227 58.4422 87.1612C55.5566 78.7386 52.0411 70.6699 48.7312 62.4782C47.7801 60.1245 46.3662 59.1015 44.2453 59.1246C40.4406 59.1553 36.6423 59.0861 32.8376 59.0938C26.6035 59.1092 20.363 58.8861 14.1418 59.663C13.0814 59.7937 12.0081 59.9091 10.9605 60.1553C9.1417 60.5706 8.56974 61.6859 9.10317 63.8319C9.3731 64.9165 9.77796 65.9549 10.2021 66.9702C12.7343 73.0852 15.2151 79.2232 17.8501 85.2767C19.5468 89.1764 21.2885 93.0838 23.3387 96.7221C25.1125 99.8757 27.5933 101.499 31.1152 100.783C34.8428 100.022 38.6732 99.7372 42.465 99.6834C51.6105 99.5373 60.7624 99.6603 69.9142 99.6296C71.5145 99.6296 73.1148 99.4142 74.7087 99.2142C75.2164 99.1527 75.8848 98.9988 76.174 98.5835C77.3823 96.8605 78.3334 94.9299 79.1689 92.6685H79.1625ZM27.2334 194.031C27.7218 194.423 28.1974 195.016 28.7694 195.215C29.8941 195.615 31.0638 195.992 32.2206 196.023C35.0034 196.092 37.7927 195.931 40.582 195.908C42.6579 195.892 44.7337 196.023 46.8096 195.977C50.4279 195.9 54.0527 195.831 57.6646 195.592C62.7547 195.254 67.8448 194.816 72.9284 194.3C74.9979 194.092 77.0159 193.416 78.8733 192.223C79.4838 191.831 80.2101 191.516 80.1651 190.27C78.8347 188.97 77.138 188.524 75.4799 188.131C69.2715 186.662 62.9925 186.493 56.6941 186.739C56.4178 186.747 56.1414 186.885 55.8651 186.97C55.7044 188.262 55.6337 189.47 55.3959 190.623C55.1067 192.054 54.2519 192.639 52.973 192.477C51.8611 192.339 51.3663 191.731 51.2377 190.239C51.1413 189.131 51.1028 188.024 51.0321 186.855C47.8765 186.539 44.888 186.97 41.9123 187.616C37.5871 188.547 33.3067 189.693 29.2835 191.908C28.4994 192.339 27.574 192.608 27.2269 194.039L27.2334 194.031ZM51.7776 104.429C51.7776 105.537 51.7776 106.452 51.7776 107.36C51.7776 132.874 51.7711 158.387 51.7647 183.893C51.7647 185.739 51.6876 187.585 51.7583 189.431C51.8162 191.131 52.3174 191.747 53.3329 191.723C54.3098 191.7 54.7854 191.154 54.8882 189.593C54.9846 188.047 54.946 186.485 54.9139 184.939C54.554 167.04 54.1748 149.134 53.8213 131.235C53.6606 122.997 53.545 114.752 53.41 106.514C53.3971 105.837 53.3393 105.16 53.3007 104.429H51.7904H51.7776Z" fill="black"/> +<path d="M95.0303 0.0900269C95.7051 2.62832 95.1653 3.60518 93.2244 3.49749C92.8709 3.48211 92.511 3.41288 92.164 3.47442C91.0136 3.68979 90.4737 4.48204 90.6601 5.84349C90.7757 6.66651 91.0714 7.46646 91.3284 8.25102C92.0932 10.5893 91.0521 12.62 88.9634 12.7123C88.1407 12.743 87.3117 12.5584 86.4826 12.4661C86.0134 12.4123 85.5378 12.2661 85.0751 12.2815C83.0249 12.3584 81.746 14.1891 82.2023 16.5735C82.4594 17.9119 82.9735 19.1811 83.4812 20.8194C81.9645 19.158 81.3861 17.3196 81.3604 15.3044C81.3347 13.0046 83.0699 11.297 85.2551 11.3585C86.0199 11.3816 86.7782 11.6123 87.5495 11.7046C88.0765 11.7739 88.6227 11.8508 89.1433 11.7739C90.5315 11.5816 91.1678 10.297 90.7372 8.70484C90.4865 7.75875 90.1523 6.83573 90.0174 5.86657C89.7667 4.03592 90.5894 2.88215 92.1447 2.68216C92.2025 2.68216 92.2604 2.67447 92.3246 2.66678C93.0123 2.59755 93.8735 2.78985 94.3491 2.3668C94.7861 1.98221 94.8054 0.928433 95.0239 0.0977188L95.0303 0.0900269Z" fill="black"/> +<path d="M96.2258 14.1199C94.2978 13.7122 93.8093 13.9506 92.736 15.989C91.5663 18.2042 90.5573 18.5734 88.5971 17.5043C87.1896 16.7351 86.1228 17.4273 85.9557 19.2811C85.885 20.0887 85.9428 20.9117 85.9428 21.7886C84.8117 20.1272 84.8888 17.9042 86.0842 16.6735C86.8426 15.889 87.7231 15.8044 88.7321 16.412C90.6794 17.5735 91.0586 17.4735 92.3183 15.2583C93.0445 13.9814 93.9443 13.1661 95.2875 13.4584C95.6217 13.5276 95.9109 13.8968 96.2258 14.1276V14.1199Z" fill="black"/> +<path d="M179.704 90.2994C179.826 89.9917 179.897 89.6379 180.077 89.3841C182.069 86.4843 184.068 83.5999 186.08 80.7231C186.183 80.577 186.369 80.5231 186.517 80.4308C186.395 80.8154 186.35 81.2769 186.144 81.5769C184.216 84.369 182.262 87.1381 180.302 89.9071C180.167 90.0994 179.91 90.1763 179.704 90.2994Z" fill="black"/> +<path d="M169.004 105.237C169.968 103.037 171.427 101.406 173.143 100.068C171.761 101.791 170.379 103.514 169.004 105.237Z" fill="black"/> +<path d="M186.478 80.4847C186.883 79.0386 187.539 77.8156 188.657 77.0157C187.931 78.1694 187.205 79.3232 186.478 80.4847Z" fill="black"/> +<path d="M177.918 92.8608C178.529 91.9916 179.133 91.1224 179.743 90.2533C179.133 91.1224 178.529 91.9916 177.918 92.8608Z" fill="black"/> +<path d="M176.131 95.4222L177.956 92.8069L176.131 95.4222Z" fill="black"/> +<path d="M188.618 77.0772C189.107 76.1311 189.589 75.185 190.077 74.2466C189.949 75.4542 189.525 76.4388 188.618 77.0772Z" fill="black"/> +<path d="M174.531 97.5451C175.077 96.8221 175.63 96.0913 176.176 95.3683C175.893 96.3759 175.418 97.1836 174.531 97.5451Z" fill="black"/> +<path d="M173.291 99.9065C173.657 99.1757 174.023 98.445 174.383 97.7143C174.017 98.445 173.65 99.1757 173.291 99.9065Z" fill="black"/> +<path d="M106.11 104.929C105.358 103.568 104.645 102.176 103.848 100.86C100.692 95.6837 98.9893 89.8302 98.276 83.546C98.1089 82.1076 98.3209 80.5539 98.5651 79.1079C99.497 73.5467 101.579 68.5778 104.767 64.4396C108.591 59.4861 112.666 54.8095 116.631 50.0098C117.126 49.4098 117.595 48.7868 118 48.2714C127.351 63.1166 140.076 71.5776 154.466 77.0311C154.852 77.1772 155.237 77.3157 155.931 77.5695C154.023 78.3617 152.236 77.5695 150.263 78.3771C155.539 79.754 165.668 77.3618 168.984 74.1774C169.062 73.4005 169.036 72.6237 169.235 71.9314C169.736 70.1854 170.244 68.4239 170.932 66.7779C172.134 63.8858 173.033 60.9014 173.477 57.7247C173.554 57.1862 173.734 56.6709 173.927 55.9094C175.771 57.117 177.044 58.7477 177.924 60.7706C180.784 67.3625 179.801 73.7621 176.832 79.8155C175.97 81.5692 174.389 82.8999 172.995 84.1921C169.621 87.3073 165.829 89.4302 161.677 90.6917C158.348 91.707 155.077 92.9685 151.812 94.2915C154.633 94.3684 157.127 92.5993 160.25 91.9993C158.579 94.4145 157.191 96.5298 155.694 98.5373C152.808 102.399 149.35 105.368 145.526 107.767C143.161 109.26 140.597 109.883 138 110.183C134.221 110.613 130.423 110.844 126.625 111.136C125.912 111.19 125.134 111.39 124.498 111.129C122.968 110.506 122.152 111.536 121.4 112.752C120.911 113.544 120.545 114.436 120.057 115.429C117.749 114.059 115.423 112.883 112.859 112.536C110.339 112.198 107.826 111.806 105.153 111.406C106.83 104.906 111.425 103.183 116.091 101.545C113.373 101.753 110.821 102.445 108.578 104.375C107.974 104.891 107.248 105.206 106.579 105.606C106.419 105.375 106.258 105.145 106.097 104.914L106.11 104.929ZM133.739 86.0382L133.701 85.9843C133.874 86.4151 133.99 86.8919 134.228 87.2612C134.652 87.915 135.083 88.5995 135.629 89.0841C136.349 89.7148 137.107 89.3764 137.686 88.6841C138.341 87.8996 137.801 87.2612 137.364 86.715C136.644 85.8074 135.88 84.9536 135.14 84.0691C134.909 83.946 134.684 83.8152 134.453 83.6922C133.624 82.2846 133.264 82.3692 132.3 84.4075C132.756 84.9228 133.251 85.4843 133.739 86.0458V86.0382ZM139.183 77.0003C137.281 76.9772 135.757 78.7386 135.764 80.977C135.764 82.9614 136.991 84.4383 138.675 84.4998C140.513 84.569 142.152 82.7538 142.223 80.5924C142.287 78.5771 140.97 77.0311 139.177 77.0003H139.183Z" fill="white"/> +<path d="M105.236 108.421C104.883 109.514 104.568 110.475 104.259 111.436C100.088 111.452 97.6912 114.398 95.9367 118.367C95.1398 120.167 94.3171 121.982 94.0215 124.282C95.7181 124.766 97.3056 125.266 98.9123 125.666C100.828 126.135 102.756 126.566 104.69 126.928C109.433 127.82 114.15 127.758 118.842 126.42C119.909 126.112 120.564 125.459 120.699 124.182C120.956 121.667 121.561 119.19 120.963 116.598C120.545 114.79 122.428 112.052 123.996 111.944C129.151 111.575 134.299 111.006 139.459 110.883C143.669 110.783 147.885 111.175 152.101 111.421C153.637 111.513 155.18 111.752 156.703 112.036C157.75 112.229 158.779 112.59 159.807 112.929C161.536 113.49 162.429 114.952 162.371 117.013C162.294 120.059 162.082 123.113 161.799 126.143C161.735 126.82 161.266 127.643 160.777 128.012C159.691 128.835 158.528 129.643 157.3 130.058C149.37 132.766 141.201 133.566 133.013 134.212C130.995 134.373 128.977 134.396 126.959 134.504C121.426 134.804 115.918 134.343 110.416 133.62C110.063 133.573 109.709 133.504 109.35 133.504C105.603 133.535 101.882 133.166 98.1732 132.497C95.5382 132.02 92.8454 131.997 90.1975 131.627C87.781 131.289 85.3902 130.689 82.9737 130.343C80.5122 129.989 78.2563 127.728 78.1535 124.259C78.1149 122.913 77.935 121.567 77.9221 120.213C77.9093 119.013 77.98 117.798 78.1214 116.605C78.4749 113.552 79.7666 111.783 82.4916 111.136C89.7412 109.414 97.055 108.591 104.42 108.26C104.639 108.252 104.857 108.337 105.249 108.406L105.236 108.421Z" fill="white"/> +<path d="M160.475 53.5634C155.597 59.9014 152.172 66.7933 148.56 73.7005C148.142 73.539 147.692 73.4159 147.274 73.1929C139.202 69.0085 131.799 63.555 125.237 56.5094C122.794 53.8865 120.59 51.0328 118.829 47.3869C123.604 41.9642 128.572 36.8876 134.479 32.9186C141.021 43.4948 149.17 51.1174 160.469 53.5557L160.475 53.5634Z" fill="#C8E2E2"/> +<path d="M135.019 32.5186C138.598 29.865 142.332 28.0036 146.079 26.5037C151.574 32.5186 157.416 37.5414 164.325 40.5104C166.253 41.3411 168.335 41.6719 170.36 42.1795C171.105 42.3641 171.877 42.3872 172.667 42.4949C173.586 44.7871 173.676 47.1946 173.67 49.6021C173.65 54.2864 173.689 53.8711 170.257 54.1172C168.438 54.248 166.542 54.271 164.775 53.8172C161.015 52.8404 157.288 51.6635 153.611 50.3021C150.417 49.1175 147.705 46.7792 144.967 44.4948C141.362 41.4873 138.47 37.6875 135.674 33.7186C135.456 33.4032 135.295 33.034 135.019 32.5263V32.5186Z" fill="white"/> +<path d="M149.113 74.2159C150.822 71.0238 152.249 68.1701 153.836 65.4472C155.867 61.9475 158.02 58.5554 160.128 55.1248C160.604 54.348 161.169 53.8172 162.088 54.0864C165.7 55.1479 169.351 55.271 173.245 54.4633C172.821 57.8554 172.352 61.0091 171.317 64.0396C170.263 67.124 169.332 70.27 168.245 73.3313C167.995 74.0313 167.416 74.7697 166.831 75.0543C162.982 76.9003 159.061 78.1771 154.903 76.4388C153.116 75.6927 151.304 75.062 149.113 74.2236V74.2159Z" fill="white"/> +<path d="M88.4624 51.4558C89.51 48.5637 96.2968 38.1183 98.4626 35.9646C103.72 41.0335 108.617 46.8254 115.423 49.5714C115.05 50.1252 114.832 50.5098 114.562 50.8405C112.107 53.8249 109.626 56.7786 107.203 59.7937C106.535 60.6322 105.911 60.9091 105.018 60.4168C104.105 59.9091 103.154 59.5168 102.229 59.0323C99.0282 57.3555 95.8212 55.6863 92.6399 53.9557C91.2517 53.2019 89.9085 52.3173 88.4688 51.4482L88.4624 51.4558Z" fill="white"/> +<path d="M94.7283 123.682C96.2514 119.459 97.7489 115.39 101.271 113.106C101.92 112.683 102.73 112.475 103.475 112.444C107.036 112.29 110.603 112.49 114.06 113.575C115.931 114.159 117.698 115.213 119.497 116.098C120.365 116.528 120.738 117.421 120.667 118.498C120.558 120.19 120.359 121.874 120.217 123.566C120.108 124.866 119.562 125.497 118.437 125.774C113.964 126.866 109.478 126.874 104.998 126.051C101.836 125.474 98.7065 124.666 95.5702 123.959C95.3517 123.913 95.1332 123.82 94.7347 123.682H94.7283Z" fill="white"/> +<path d="M71.7908 27.5651C72.607 27.819 73.134 27.9343 73.6288 28.142C77.5043 29.7804 81.5339 29.9496 85.5764 29.9573C87.4723 29.9573 89.3683 29.7957 91.3092 28.2112C90.93 31.311 90.5251 33.957 90.3002 36.6261C90.1202 38.7951 89.7217 40.8335 88.7191 42.6564C87.3052 45.2409 85.4736 46.9869 82.7165 46.7562C81.1226 46.6254 79.5094 46.5792 77.9284 46.3023C75.4926 45.8716 73.7895 44.2717 73.089 41.3565C72.8769 40.472 72.652 39.572 72.5748 38.6644C72.2856 35.0723 72.0607 31.4802 71.7908 27.5574V27.5651Z" fill="white"/> +<path d="M163.682 31.0034C165.36 32.634 167.468 33.2724 168.907 35.1492C170.308 36.9722 171.703 38.7951 172.436 41.6488C170.829 41.4027 169.434 41.3257 168.091 40.9488C163.361 39.6028 159.055 37.1106 155.083 33.7878C152.898 31.9571 150.732 30.1034 148.579 28.2112C148.039 27.742 147.609 27.1036 146.953 26.3344C147.589 26.1114 147.911 25.9575 148.245 25.8806C151.176 25.2191 154.106 24.9807 157.088 25.3883C159.241 25.6806 161.047 26.8113 162.725 28.3035C162.995 28.542 163.007 29.1958 163.162 29.7342C164.723 28.4497 166.317 27.2113 168.271 26.9498C171.979 26.4575 175.476 27.0728 178.631 29.6496C180.366 31.0649 181.112 33.0032 181.073 35.4492C181.054 36.6261 180.553 37.4645 179.749 38.0567C178.959 38.6336 178.181 38.6798 177.494 37.8029C176.889 37.026 176.253 36.2722 175.694 35.4492C173.888 32.7648 171.626 31.4187 168.702 31.7187C167.018 31.8956 165.295 32.2417 163.669 30.9957L163.682 31.0034Z" fill="white"/> +<path d="M87.3697 109.06C86.9519 108.221 86.5984 107.314 86.0907 106.56C85.0239 104.983 84.2462 103.245 83.8413 101.291C83.0572 97.4912 82.4724 93.653 82.8387 89.6995C83.0572 87.3227 83.3786 86.615 84.8953 85.169C85.5316 85.7382 86.3285 86.2382 86.8941 86.9919C91.4314 93.0223 93.0317 100.137 92.2027 108.106C92.1898 108.237 92.0934 108.352 91.9585 108.652C90.4996 109.006 88.9314 109.198 87.3375 109.006L87.3825 109.06H87.3697Z" fill="white"/> +<path d="M92.7169 108.544C93.8287 99.2757 91.7079 91.4147 86.0137 84.8152C86.7013 84.8152 87.1705 84.6844 87.5754 84.8305C94.4586 87.3227 98.8674 93.4992 99.0088 102.252C99.0281 103.422 98.7582 104.66 98.3854 105.752C97.8584 107.291 96.8751 108.229 95.3519 108.206C94.5485 108.191 93.7388 108.406 92.7233 108.537L92.7169 108.544Z" fill="white"/> +<path d="M130.269 161.172C129.376 161.31 128.463 161.456 127.325 161.633C127.493 152.826 127.653 144.234 127.82 135.45C128.996 135.35 130.153 135.258 131.554 135.143C131.124 143.88 130.706 152.403 130.275 161.164L130.269 161.172Z" fill="white"/> +<path d="M131.002 172.779C129.51 171.817 128.09 171.202 126.657 172.502C125.282 170.448 123.971 165.587 124.093 162.887C126.638 162.564 129.202 162.156 131.779 161.933C133.103 161.818 133.418 162.448 133.199 164.025C132.788 167.04 131.959 169.894 131.008 172.779H131.002Z" fill="white"/> +<path d="M131.792 173.394C131.959 172.755 132.094 172.24 132.268 171.563C135.096 171.956 137.866 172.24 140.61 172.755C145.417 173.663 150.205 174.678 154.993 175.725C155.97 175.94 156.915 176.44 157.84 176.917C158.207 177.109 158.47 177.578 158.894 178.04C157.898 178.855 157.069 178.532 156.291 178.386C151.189 177.417 146.092 176.44 140.996 175.417C137.956 174.801 134.929 174.086 131.792 173.394V173.394Z" fill="white"/> +<path d="M91.3478 26.9652C90.1139 28.6035 88.8028 29.2035 87.3567 29.1727C84.2204 29.1035 81.0777 29.0804 77.9671 28.6958C76.1418 28.4728 74.368 27.5882 72.2214 26.8882C73.6868 25.7114 74.9014 25.2191 76.2704 25.0191C79.5352 24.5422 82.7679 24.9422 86.0071 25.3037C86.8876 25.4037 87.7745 25.5345 88.6293 25.796C89.4776 26.0575 90.281 26.496 91.3478 26.9652V26.9652Z" fill="white"/> +<path d="M103.405 193.4C100.294 193.346 98.3275 190.162 99.5422 187.147C100.628 184.455 104.26 183.339 106.316 185.055C106.747 185.416 107.158 185.893 107.646 186.062C108.585 186.401 108.771 187.155 108.765 188.162C108.752 189.447 108.308 190.485 107.576 191.37C106.522 192.646 104.928 193.416 103.405 193.385V193.4ZM105.114 188.431C105.153 187.608 104.536 186.708 103.874 186.631C103 186.531 101.972 187.485 101.972 188.393C101.972 189.385 102.569 190.262 103.302 190.339C104.189 190.431 105.056 189.516 105.108 188.431H105.114Z" fill="white"/> +<path d="M157.513 190C155.63 190 154.081 188.762 153.579 186.901C153.142 185.255 153.592 183.855 154.826 182.947C155.276 182.616 155.713 182.247 156.15 181.886C156.394 181.686 156.619 181.363 156.883 181.316C157.513 181.201 158.271 180.94 158.772 181.232C159.794 181.832 160.835 182.601 161.594 183.593C162.448 184.709 162.018 186.939 160.951 188.224C159.987 189.385 158.779 189.893 157.513 189.993V190ZM157.872 183.547C157.211 183.493 156.291 184.585 156.24 185.485C156.195 186.331 156.786 187.085 157.551 187.155C158.348 187.231 159.145 186.462 159.164 185.593C159.184 184.724 158.464 183.593 157.872 183.539V183.547Z" fill="white"/> +<path d="M128.045 195.715C126.58 195.7 125.404 194.985 124.581 193.539C123.476 191.6 123.816 190.262 125.185 188.593C127.248 186.078 130.397 187.139 132.12 190.208C132.454 190.8 132.3 192.047 132.023 192.808C131.297 194.816 129.877 195.723 128.052 195.708L128.045 195.715ZM128.013 189.485C127.216 189.439 126.67 190.024 126.606 190.985C126.541 191.939 127.088 192.716 127.807 192.7C128.502 192.685 129.221 191.939 129.286 191.154C129.356 190.27 128.791 189.524 128.013 189.485V189.485Z" fill="white"/> +<path d="M106.11 104.929C106.271 105.16 106.432 105.391 106.592 105.621C106.374 106.937 105.815 107.798 104.594 107.798C102.531 107.798 100.461 107.798 98.186 107.798C99.8056 105.129 99.9534 102.176 99.555 99.0988C100.962 98.4758 101.894 98.8835 102.756 100.337C103.739 101.983 104.979 103.414 106.11 104.937V104.929Z" fill="white"/> +<path d="M95.5896 35.5184C94.9661 35.1031 94.2656 34.8185 93.7836 34.257C93.3659 33.7647 92.8903 32.8802 93.006 32.334C93.1217 31.7956 93.8929 31.2572 94.452 31.0957C95.4932 30.7957 96.5086 31.1418 97.0935 32.311C97.6976 33.5186 97.9739 34.9031 97.1641 36.1107C95.0754 39.2028 92.9288 42.2334 90.7823 45.2639C89.9596 46.4331 89.2013 45.4793 88.2629 45.1332C88.9892 43.6256 89.619 42.2103 90.3388 40.8719C90.538 40.5104 91.0008 40.3104 91.3735 40.1412C92.7553 39.4951 93.8672 38.5875 94.3299 36.7491C94.4392 36.3107 95.024 36.0492 95.5832 35.5261L95.5896 35.5184Z" fill="white"/> +<path d="M102.923 180.84C103.135 180.547 103.295 180.14 103.559 179.986C104.889 179.217 106.207 178.378 107.601 177.817C113.039 175.617 118.501 173.509 123.951 171.371C124.228 171.263 124.504 171.186 124.845 171.071C125.063 171.556 125.263 171.971 125.455 172.402C125.301 172.602 125.211 172.832 125.07 172.894C122.788 173.863 120.507 174.863 118.199 175.755C113.694 177.494 109.176 179.17 104.664 180.855C104.15 181.047 103.61 181.124 103.077 181.255L102.916 180.84H102.923Z" fill="white"/> +<path d="M128.167 183.432C128 182.732 127.775 182.039 127.685 181.324C127.403 179.094 127.126 176.855 126.946 174.617C126.824 173.109 127.409 172.502 128.611 172.609C129.665 172.709 129.948 173.04 129.8 174.332C129.485 177.055 129.106 179.77 128.739 182.478C128.694 182.793 128.579 183.086 128.489 183.393C128.379 183.409 128.277 183.416 128.167 183.432V183.432Z" fill="white"/> +<path d="M93.8735 35.5415C93.6614 37.7491 92.6203 38.8644 90.975 39.3951C90.6408 38.4259 91.0843 37.9106 91.5406 37.326C92.691 35.8569 92.5881 35.4723 91.8105 33.657C91.457 32.834 91.6177 31.6956 91.5406 30.5572C92.1061 30.588 92.421 30.6111 92.7745 30.6265C92.0097 33.1109 92.119 33.6109 93.8735 35.5415V35.5415Z" fill="white"/> +<path d="M100.12 184.87C102.351 181.516 107.441 182.032 108.822 185.462C108.18 185.808 107.64 185.462 107.209 185.016C105.178 182.855 103.077 182.978 100.885 184.609C100.757 184.701 100.583 184.716 100.114 184.87H100.12Z" fill="white"/> +<path d="M153.997 182.424C155.668 179.67 158.907 178.778 160.771 180.278C161.272 180.678 161.767 181.086 161.176 182.055C160.09 181.232 159.132 179.824 157.744 180.324C156.555 180.755 155.462 181.578 153.997 182.424V182.424Z" fill="white"/> +<path d="M125.905 186.739C127.666 185.162 129.838 185.085 131.432 186.578C131.959 187.07 132.377 187.654 132.068 188.716C130.295 186.624 128.225 185.993 125.873 186.708L125.899 186.747L125.905 186.739Z" fill="white"/> +<path d="M91.8171 34.7416C91.7464 35.2492 91.6757 35.7569 91.605 36.2645C91.4765 36.2492 91.3544 36.2338 91.2258 36.2184C91.2644 35.6954 91.2965 35.1646 91.3351 34.6416C91.4957 34.6723 91.6564 34.7031 91.8171 34.7416V34.7416Z" fill="white"/> +<path d="M125.873 186.708C125.41 187.255 124.947 187.801 124.485 188.339C124.388 188.185 124.298 188.039 124.202 187.885C124.767 187.508 125.333 187.124 125.899 186.747L125.873 186.708Z" fill="white"/> +<path d="M79.1626 92.6685C78.3271 94.9299 77.376 96.8605 76.1677 98.5835C75.8785 98.9988 75.2165 99.145 74.7024 99.2142C73.1085 99.4142 71.5082 99.6219 69.9079 99.6296C60.7561 99.6603 51.6042 99.5373 42.4588 99.6834C38.6669 99.7449 34.8365 100.03 31.1089 100.783C27.587 101.499 25.1062 99.8757 23.3324 96.7221C21.2822 93.0838 19.5405 89.1764 17.8438 85.2767C15.2153 79.2232 12.728 73.0852 10.1959 66.9702C9.77811 65.9549 9.36681 64.9088 9.09688 63.8319C8.56345 61.6859 9.13541 60.5706 10.9542 60.1553C12.0018 59.9168 13.0687 59.8014 14.1355 59.663C20.3567 58.8861 26.5908 59.1092 32.8313 59.0938C36.636 59.0861 40.4343 59.1553 44.239 59.1246C46.3663 59.1092 47.7738 60.1245 48.725 62.4782C52.0348 70.6623 55.5503 78.7309 58.436 87.1612C58.5259 87.4227 58.6481 87.6688 58.7252 87.938C59.5285 90.9455 61.0903 92.207 63.8667 92.1147C68.5519 91.9608 73.2435 92.307 77.9351 92.4531C78.2693 92.4608 78.5971 92.5685 79.1562 92.6685H79.1626ZM30.3698 74.1082L30.4019 74.1466C28.5896 72.5621 26.3787 72.816 25.6461 74.7235C26.6551 75.9388 28.0047 75.185 29.2001 75.5312C29.1487 75.7619 29.1294 75.9773 29.0523 76.1542C27.5613 79.7924 28.3261 85.0075 30.7297 87.7688C32.0408 89.2687 33.6089 89.4072 35.2157 88.8457C36.7838 88.2995 37.5743 86.8304 37.87 84.9305C38.5512 80.6078 35.055 73.2313 30.3698 74.1082Z" fill="white"/> +<path d="M27.2397 194.039C27.5868 192.616 28.5123 192.346 29.2964 191.908C33.3196 189.693 37.5998 188.547 41.9251 187.616C44.9008 186.978 47.8893 186.539 51.0449 186.855C51.1156 188.024 51.1541 189.131 51.2505 190.239C51.3791 191.731 51.8739 192.339 52.9858 192.477C54.2647 192.639 55.1195 192.054 55.4087 190.624C55.6465 189.462 55.7172 188.262 55.8779 186.97C56.1542 186.893 56.4306 186.755 56.7069 186.739C63.0053 186.485 69.2908 186.662 75.4927 188.131C77.1573 188.524 78.854 188.962 80.1779 190.27C80.2229 191.516 79.4966 191.831 78.8861 192.223C77.0287 193.416 75.0171 194.093 72.9412 194.3C67.8576 194.816 62.7675 195.254 57.6774 195.592C54.0655 195.831 50.4407 195.9 46.8224 195.977C44.7465 196.023 42.6707 195.892 40.5948 195.908C37.8055 195.923 35.0163 196.092 32.2334 196.023C31.0766 195.992 29.9005 195.615 28.7822 195.216C28.2102 195.016 27.7282 194.423 27.2462 194.031L27.2397 194.039Z" fill="white"/> +<path d="M51.7774 104.437H53.2877C53.3263 105.168 53.3905 105.845 53.397 106.521C53.5384 114.759 53.6476 123.005 53.8083 131.243C54.1618 149.142 54.5409 167.048 54.9008 184.947C54.933 186.501 54.9715 188.062 54.8751 189.6C54.7787 191.162 54.2967 191.7 53.3198 191.731C52.3044 191.754 51.8095 191.139 51.7452 189.439C51.681 187.601 51.7517 185.747 51.7517 183.901C51.7517 158.387 51.7645 132.874 51.7645 107.368C51.7645 106.452 51.7645 105.545 51.7645 104.437H51.7774Z" fill="white"/> +<path d="M77.8066 99.2219C79.2505 99.2681 80.3281 99.5526 81.0393 100.076C80.0239 100.522 79.1884 101.091 78.3079 101.253C75.4351 101.776 72.5558 102.352 69.6637 102.552C61.1931 103.145 52.7032 103.675 44.2197 103.306C38.4934 103.06 32.8442 103.729 27.1628 104.198C25.3376 104.352 23.4931 104.222 21.5779 103.991C22.3298 103.683 23.0689 103.322 23.8337 103.075C28.3775 101.614 33.0242 101.014 37.7029 100.53C44.6375 99.8142 51.5849 100.437 58.5195 100.176C63.7381 99.9757 68.9632 100.06 74.1882 99.9219C75.4158 99.8911 76.6305 99.4526 77.858 99.2065L77.813 99.2373L77.8066 99.2219Z" fill="white"/> +<path d="M139.183 77.0003C140.976 77.0234 142.294 78.5771 142.229 80.5924C142.159 82.7538 140.52 84.569 138.682 84.4998C136.998 84.4383 135.777 82.9614 135.77 80.977C135.764 78.7463 137.281 76.9772 139.189 77.0003H139.183ZM141.49 80.6154C141.535 79.1078 140.565 77.8618 139.324 77.8387C137.949 77.8079 136.683 79.2232 136.619 80.8616C136.561 82.3999 137.486 83.5768 138.778 83.6075C140.192 83.646 141.439 82.2692 141.484 80.6154H141.49Z" fill="black"/> +<path d="M134.453 83.6845C134.684 83.8075 134.909 83.9383 135.141 84.0614C135.533 84.8152 135.899 85.592 136.323 86.3228C136.67 86.9304 137.088 87.4765 137.519 88.1073C136.445 88.9918 135.777 88.6149 135.16 87.7919C134.691 87.1689 134.196 86.5766 133.714 85.9766L133.752 86.0305C133.54 85.3921 133.322 84.7536 133.084 84.0537C133.258 83.8845 133.418 83.6076 133.617 83.5691C133.887 83.5153 134.177 83.6383 134.459 83.6845H134.453Z" fill="white"/> +<path d="M133.701 85.9766C134.183 86.5843 134.678 87.1765 135.147 87.7919C135.77 88.6149 136.439 88.9918 137.506 88.1072C137.081 87.4765 136.664 86.9227 136.31 86.3227C135.886 85.592 135.52 84.8152 135.128 84.0614C135.873 84.9382 136.638 85.7997 137.351 86.7073C137.788 87.2611 138.335 87.8919 137.673 88.6764C137.094 89.3687 136.336 89.7071 135.616 89.0764C135.07 88.5995 134.639 87.9073 134.215 87.2535C133.977 86.8843 133.862 86.4074 133.688 85.9766H133.701Z" fill="black"/> +<path d="M134.453 83.6845C134.17 83.6383 133.875 83.5153 133.611 83.5691C133.418 83.6076 133.258 83.8845 133.078 84.0537C133.315 84.7536 133.527 85.392 133.746 86.0305C133.251 85.469 132.763 84.9152 132.306 84.3921C133.27 82.3538 133.63 82.2692 134.459 83.6768L134.453 83.6845Z" fill="black"/> +<path d="M105.114 188.431C105.063 189.516 104.195 190.431 103.308 190.339C102.576 190.262 101.978 189.385 101.978 188.393C101.978 187.478 103 186.524 103.88 186.631C104.542 186.708 105.159 187.608 105.121 188.431H105.114ZM103.803 189.631C103.977 189.301 104.375 188.854 104.44 188.339C104.536 187.624 104.105 187.07 103.482 187.285C103.128 187.408 102.685 187.939 102.64 188.347C102.569 188.939 102.801 189.608 103.803 189.624V189.631Z" fill="black"/> +<path d="M157.872 183.547C158.464 183.593 159.183 184.732 159.164 185.601C159.145 186.462 158.354 187.239 157.551 187.162C156.786 187.093 156.188 186.339 156.24 185.493C156.291 184.593 157.21 183.501 157.872 183.555V183.547ZM158.836 185.393C158.297 184.901 157.956 184.585 157.615 184.278C157.377 184.701 157.037 185.101 156.953 185.562C156.921 185.739 157.48 186.262 157.77 186.27C158.059 186.27 158.348 185.816 158.836 185.393V185.393Z" fill="black"/> +<path d="M128.02 189.493C128.797 189.539 129.363 190.277 129.292 191.162C129.228 191.947 128.514 192.693 127.814 192.708C127.094 192.723 126.548 191.939 126.612 190.993C126.676 190.031 127.223 189.454 128.02 189.493V189.493ZM127.878 192.108C128.155 191.631 128.514 191.277 128.476 191.023C128.431 190.739 128.007 190.347 127.762 190.354C127.544 190.362 127.191 190.862 127.184 191.154C127.184 191.423 127.563 191.693 127.885 192.108H127.878Z" fill="black"/> +<path d="M30.3761 74.1159C35.0613 73.2467 38.5575 80.6231 37.8762 84.9382C37.5742 86.8381 36.7901 88.2995 35.2219 88.8534C33.6216 89.4149 32.0471 89.2764 30.736 87.7765C28.3259 85.0151 27.5675 79.8001 29.0586 76.1619C29.1293 75.985 29.1485 75.7696 29.2064 75.5389C28.011 75.1927 26.6678 75.9465 25.6523 74.7312C26.385 72.8236 28.5958 72.5775 30.4018 74.1543C29.1614 73.9543 27.921 73.7313 26.7256 74.3389C27.9917 75.2389 29.1742 74.5774 30.3696 74.1159H30.3761ZM29.8426 85.0921C30.3118 85.8382 30.6717 86.5843 31.1666 87.1612C32.9404 89.2302 36.1281 88.4765 36.9443 85.7689C37.5549 83.7537 37.3428 81.7769 36.4174 79.9232C33.0497 81.6615 31.3851 82.9537 29.8426 85.0921V85.0921ZM28.9943 79.5078C30.646 78.3617 32.227 77.2695 33.8402 76.1542C32.754 75.0312 31.1087 74.4697 30.4403 75.1543C29.3992 76.2388 28.7244 77.6233 28.9943 79.5078ZM34.38 80.1078C34.1165 79.1848 33.8787 78.3541 33.551 77.2234C32.3556 78.0002 31.3594 78.6463 30.2475 79.3617C30.5946 80.4155 30.8838 81.2923 31.2116 82.2846C32.3041 81.5308 33.2167 80.9 34.38 80.1001V80.1078ZM29.4506 83.9383C30.3761 83.1127 30.4125 81.8513 29.5599 80.1539C28.4287 81.3539 29.2 82.5384 29.4506 83.9383ZM35.9482 79.0925C35.4726 77.9131 34.877 77.3644 34.1615 77.4464C34.3672 78.1771 34.56 78.8771 34.7978 79.7232C35.2541 79.4694 35.6011 79.2848 35.9546 79.0925H35.9482Z" fill="black"/> +<path d="M141.49 80.6077C141.445 82.2615 140.198 83.6383 138.785 83.5999C137.493 83.5614 136.567 82.3922 136.625 80.8539C136.683 79.2155 137.949 77.7926 139.331 77.831C140.571 77.8618 141.542 79.1078 141.497 80.6077H141.49Z" fill="white"/> +<path d="M103.797 189.639C102.794 189.624 102.569 188.954 102.633 188.362C102.678 187.954 103.122 187.424 103.475 187.301C104.099 187.085 104.529 187.631 104.433 188.354C104.362 188.87 103.964 189.316 103.797 189.647V189.639Z" fill="white"/> +<path d="M158.837 185.393C158.355 185.816 158.059 186.27 157.77 186.27C157.48 186.27 156.921 185.739 156.953 185.562C157.037 185.101 157.378 184.701 157.615 184.278C157.956 184.593 158.297 184.901 158.837 185.393V185.393Z" fill="white"/> +<path d="M127.878 192.108C127.557 191.7 127.178 191.423 127.178 191.154C127.178 190.862 127.538 190.362 127.756 190.354C128 190.339 128.425 190.731 128.47 191.023C128.515 191.277 128.155 191.631 127.872 192.108H127.878Z" fill="white"/> +<path d="M29.8364 85.0921C31.3724 82.9614 33.0435 81.6692 36.4111 79.9232C37.3366 81.7769 37.5487 83.7537 36.9382 85.7689C36.1219 88.4765 32.9342 89.2226 31.1604 87.1611C30.6655 86.5843 30.3056 85.8305 29.8364 85.0921V85.0921Z" fill="white"/> +<path d="M28.9945 79.5078C28.7245 77.6233 29.3994 76.2388 30.4405 75.1543C31.1025 74.462 32.7542 75.0312 33.8404 76.1542C32.2272 77.2695 30.6462 78.3694 28.9945 79.5078Z" fill="white"/> +<path d="M34.3737 80.1001C33.204 80.9077 32.2978 81.5308 31.2052 82.2846C30.8775 81.2923 30.5883 80.4155 30.2412 79.3617C31.3466 78.6463 32.3492 77.9925 33.5446 77.2234C33.8724 78.3541 34.1102 79.1771 34.3737 80.1078V80.1001Z" fill="white"/> +<path d="M29.4507 83.9306C29.2001 82.5307 28.4289 81.3462 29.56 80.1462C30.4126 81.8436 30.3762 83.105 29.4507 83.9306Z" fill="white"/> +<path d="M35.9481 79.0848C35.6011 79.2771 35.254 79.4617 34.7913 79.7155C34.5599 78.8771 34.3607 78.1695 34.155 77.4387C34.8706 77.3516 35.4661 77.9002 35.9417 79.0848H35.9481Z" fill="white"/> +<path d="M30.376 74.1159C29.1806 74.5774 27.998 75.2389 26.7319 74.3389C27.9273 73.7313 29.1677 73.9543 30.4081 74.1543L30.376 74.1159V74.1159Z" fill="white"/> </svg> diff --git a/frontend/app/svg/ca-no-notes.svg b/frontend/app/svg/ca-no-notes.svg new file mode 100644 index 000000000..03df7c155 --- /dev/null +++ b/frontend/app/svg/ca-no-notes.svg @@ -0,0 +1,37 @@ +<svg width="210" height="200" viewBox="0 0 210 200" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_8_72)"> +<rect width="210" height="200" fill="white"/> +<path d="M35.7859 176.05C38.1902 175.584 40.2359 175.172 43.4563 174.788C50.3353 173.969 58.5513 173.468 66.9377 173.077C85.7128 172.202 106.113 172.101 125.48 172.563C138.184 172.866 150.109 173.461 161.977 174.173C167.559 174.508 174.145 174.983 182.148 175.474" stroke="black" stroke-linecap="round"/> +<path d="M42.3224 35.8988C42.3224 38.6195 42.3224 41.3401 42.3224 44.0607C42.3224 45.0296 41.8508 46.2914 43.031 46.2914" stroke="black" stroke-linecap="round"/> +<rect x="7.80286" y="8.6078" width="88.2438" height="80.702" rx="4" fill="#FDF9F3"/> +<rect x="112.794" y="71.2889" width="88.2438" height="80.702" rx="4" fill="#F4F5FF"/> +<path d="M99.8902 173.216C42.4475 170.95 27.0186 177.736 19.0232 181.68L92.9268 179.573L99.8902 173.216Z" fill="white"/> +<path d="M171.235 22.4191C171.692 25.3007 171.277 25.8686 168.862 26.0635C168.099 26.1228 167.336 26.1906 166.234 26.2754C166.234 29.479 166.226 32.5894 166.234 35.6999C166.26 42.2767 166.353 48.8619 166.294 55.4387C166.277 56.8372 166.683 57.7017 167.7 58.6255C175.235 65.482 180.1 73.8979 182.092 83.9072C182.981 88.3991 183.236 92.9418 182.38 97.4592C181.82 100.409 181.99 103.358 181.964 106.307C181.914 111.545 181.93 116.783 181.914 122.029C181.905 124.538 181.769 127.046 181.863 129.547C182.117 136.92 181.549 144.243 180.49 151.531C179.786 156.362 178.227 160.956 176.574 165.524C175.616 168.177 173.82 170.27 171.726 172.058C169.709 173.779 167.522 175.288 165.471 176.839C165.98 178.957 166.472 181.152 167.031 183.322C167.319 184.432 167.006 185.271 166.234 186.094C165.141 187.263 163.954 188.17 162.403 188.695C159.954 189.517 157.589 190.577 155.149 191.399C152.106 192.416 149.072 193.552 145.944 194.153C143.224 194.679 140.376 194.526 137.588 194.671C136.986 186.407 140.224 183.051 149.707 181.966C149.682 181.678 149.707 181.364 149.631 181.085C149.538 180.712 149.368 180.364 149.174 179.856C143.402 180.051 137.639 180.246 131.681 180.449C131.172 181.695 130.587 182.958 130.147 184.271C128.799 188.289 126.121 189.865 122.002 189.043C119.629 188.568 117.213 188.272 114.874 187.661C110.153 186.424 105.645 184.687 101.932 181.364C100.559 180.135 100.551 179.678 101.754 178.288C103.204 176.627 105.017 175.644 107.145 175.177C107.797 175.033 108.45 174.855 109.348 174.626C108.831 174.143 108.577 173.855 108.272 173.626C102.755 169.465 99.8983 163.854 99.1779 157.057C98.7033 152.548 97.9151 148.048 97.7456 143.522C97.5168 137.344 97.7456 131.157 97.7795 124.97C97.788 122.097 97.7795 119.224 97.7795 116.24C96.0506 116.156 94.347 116.02 92.635 115.986C84.1767 115.817 75.7353 115.927 67.277 116.613C60.1154 117.198 52.8945 117.173 45.699 116.444C42.0461 116.071 38.3594 116.071 34.6811 115.918C34.0624 115.893 33.4437 115.918 32.6047 115.918C32.5369 114.596 32.4606 113.376 32.4013 112.147C32.2487 109.028 32.0792 105.901 31.9521 102.773C31.8843 101.129 31.825 99.4848 31.8589 97.849C31.9351 93.5436 32.1301 89.2381 32.164 84.9412C32.1979 79.8475 31.9267 74.7539 32.1216 69.6688C32.342 63.9564 33.1047 58.2695 33.3081 52.5656C33.5709 45.2091 33.5031 37.8441 33.5793 30.4875C33.5963 29.0976 33.5793 27.7076 33.5793 26.1312C32.3674 26.0634 31.4436 26.0211 30.5282 25.9702C29.9858 25.9363 29.4265 25.9533 28.8925 25.8516C27.2144 25.5295 26.9008 25.0803 27.189 23.4446C27.2822 22.9191 27.4687 22.4021 27.6805 21.6394C28.6298 21.6394 29.5536 21.5377 30.452 21.6563C34.2913 22.1818 38.0882 21.8173 41.9275 21.5292C46.6058 21.1732 51.3266 21.08 56.0134 21.2495C58.717 21.3428 61.107 20.9359 63.4123 19.5968C70.4552 15.5033 77.4897 11.4012 84.575 7.38396C87.3634 5.80756 90.2789 4.46848 93.1096 2.95141C93.7622 2.60392 94.3555 2.07 94.8301 1.49369C97.1354 -1.30315 101.898 -0.998067 103.899 2.05303C104.255 2.59545 104.856 3.17176 105.45 3.31584C109.958 4.40067 113.696 7.0619 117.696 9.18919C121.154 11.0283 124.604 12.8759 128.011 14.8083C130.596 16.2745 133.028 18.0204 135.664 19.385C139.393 21.3173 143.114 22.97 147.64 22.6225C153.793 22.1479 160.005 22.3937 166.183 22.3428C167.878 22.3344 169.574 22.3852 171.209 22.4106L171.235 22.4191ZM117.9 132.75C117.832 133.284 117.62 133.852 117.722 134.36C118.552 138.53 119.773 142.573 122.265 146.09C122.815 146.87 123.697 147.429 124.341 148.014C124.833 147.794 125.061 147.726 125.256 147.607C131.443 143.938 137.656 140.319 143.792 136.556C146.614 134.827 148.733 132.386 149.216 128.962C150.004 123.419 148.724 118.224 146.029 113.41C145.258 112.02 143.987 110.825 142.732 109.816C140.834 108.291 138.622 108.502 136.723 110.036C135.071 111.376 133.952 113.096 133.588 115.139C133.206 117.274 133.104 119.469 133.054 121.648C132.96 125.428 131.613 128.326 127.858 129.708C125.214 130.674 122.587 131.665 119.925 132.572C119.281 132.793 118.552 132.733 117.857 132.801C119.061 132.293 120.264 131.784 121.468 131.275C123.909 130.241 126.4 129.301 128.774 128.123C130.443 127.301 131.613 125.945 131.664 123.894C131.698 122.648 131.892 121.41 131.943 120.164C132.096 116.596 132.511 113.121 135.105 110.333C135.283 110.138 135.325 109.825 135.359 109.74C129.138 108.977 122.976 108.452 116.908 107.401C110.831 106.358 104.975 104.417 99.339 101.265C99.2627 101.943 99.1949 102.307 99.1864 102.671C98.9661 111.435 98.6779 120.207 98.5762 128.97C98.5169 134.521 98.5762 140.09 98.8982 145.633C99.1525 150.074 99.6949 154.515 100.483 158.888C100.949 161.456 101.695 164.193 103.102 166.329C106.162 170.957 110.264 174.559 115.73 176.296C121.578 178.161 127.528 179.5 133.698 179.322C139.486 179.161 145.266 178.856 151.047 178.601C152.996 178.517 154.988 178.652 156.878 178.279C163.539 176.949 169.158 173.694 173.608 168.533C174.26 167.77 174.947 166.922 175.252 165.99C176.549 162.058 177.913 158.134 178.888 154.116C180.566 147.167 180.752 140.039 180.888 132.92C180.93 130.411 180.761 127.894 180.973 125.402C181.617 117.732 180.761 110.062 180.956 102.4C180.956 102.121 180.829 101.841 180.719 101.366C180.176 101.612 179.752 101.773 179.362 101.993C175.438 104.197 171.175 105.502 166.845 106.595C159.903 108.341 152.826 109.011 145.699 109.316C145.292 109.333 144.894 109.494 144.419 109.604C149.021 115.757 150.792 121.41 150.496 128.318C150.411 130.208 149.809 131.843 148.597 133.25C147.775 134.191 146.961 135.2 145.953 135.903C143.393 137.683 140.766 139.369 138.096 140.971C134.011 143.429 129.875 145.802 125.748 148.192C125.282 148.463 124.587 148.938 124.29 148.777C123.087 148.124 121.993 148.76 120.866 148.853C118.713 149.04 116.629 148.735 114.679 147.845C112.137 146.692 109.865 145.192 108.704 142.488C108.204 141.319 107.848 140.107 108.441 138.861C109.391 136.878 110.789 135.335 112.984 134.733C113.883 134.488 114.883 134.522 115.739 134.182C116.535 133.869 117.205 133.233 117.925 132.742L117.9 132.75ZM139.435 48.1585C139.435 47.2008 139.385 46.4296 139.444 45.6668C139.605 43.5649 139.571 41.4122 140.037 39.3781C141.919 31.2079 147.368 26.5381 155.25 24.4532C157.225 23.9277 159.217 24.1226 160.573 25.9363C162.005 27.8517 161.768 29.9028 160.708 31.9199C160.42 32.4708 160.09 33.0216 159.691 33.4963C156.928 36.7847 153.513 39.0052 149.233 39.7425C144.834 40.4968 142.326 43.6412 142.58 48.1585C145.266 48.6077 148.012 48.8535 150.64 49.5484C153.276 50.2434 155.869 51.2096 158.344 52.3537C160.776 53.4809 163.022 54.9896 165.658 56.4982C165.658 47.4889 165.675 38.8188 165.641 30.1486C165.641 28.3518 165.438 26.555 165.31 24.7583C165.277 24.3091 165.183 23.8599 165.132 23.487C164.853 23.3514 164.717 23.2242 164.582 23.2327C158.166 23.3514 151.75 23.4616 145.334 23.6311C143.86 23.6735 142.385 24.0972 140.919 24.0802C137.562 24.0548 134.206 23.8514 130.85 23.7752C129.519 23.7412 128.189 23.8429 126.867 23.9107C125.155 23.9955 123.434 24.1481 121.722 24.1904C118.747 24.2667 115.773 24.2837 112.798 24.343C111.704 24.3599 110.611 24.4786 109.518 24.4701C105.916 24.4447 102.314 24.3854 98.7118 24.3176C97.1439 24.2837 95.5844 24.2328 94.0249 24.1057C90.9823 23.8684 87.9482 23.4701 84.9056 23.3175C78.1762 22.9785 71.4468 22.7751 64.7175 22.4615C55.0811 22.0208 45.4617 21.8936 35.8592 23.0717C35.3422 23.1395 34.8252 23.2242 34.4523 23.2836C34.4523 25.5719 34.3591 27.6738 34.4692 29.7587C34.9693 39.5561 34.6303 49.345 33.8844 59.1086C33.2657 67.1685 33.2064 75.22 33.3081 83.2885C33.3675 88.2211 33.1641 93.1537 33.0708 98.0778C33.0454 99.3322 32.9861 100.587 33.02 101.832C33.1047 105.35 33.2319 108.867 33.3336 112.384C33.359 113.206 33.3336 114.037 33.3336 114.859C34.054 114.969 34.4353 115.054 34.8167 115.079C39.3425 115.35 43.8768 115.8 48.4026 115.834C55.2167 115.876 62.0224 115.672 68.8365 115.494C73.6843 115.367 78.5322 115.02 83.3885 114.961C86.5921 114.918 89.8043 115.223 93.0079 115.325C94.6267 115.376 96.237 115.333 97.8049 115.333C97.9405 114.816 98.0507 114.588 98.0422 114.376C97.7371 108.536 98.5084 102.705 97.6778 96.8235C96.1607 86.043 98.9576 76.1184 105.034 67.177C112.645 55.9642 123.053 49.3281 136.8 48.4128C137.639 48.3534 138.469 48.2433 139.427 48.1415L139.435 48.1585ZM114.001 105.595C114.62 105.79 114.976 105.951 115.349 106.011C120.527 106.799 125.689 107.714 130.892 108.307C135.774 108.858 140.707 107.799 145.605 108.519C145.834 108.553 146.072 108.494 146.309 108.477C153.25 107.918 160.183 107.282 166.955 105.536C171.514 104.366 175.921 102.799 180.066 100.536C180.829 100.12 181.21 99.6034 181.286 98.722C181.981 91.2213 181.363 83.8648 178.828 76.7286C178.625 76.1693 178.312 75.6438 178.057 75.1353C175.794 75.7285 173.709 76.4235 171.565 76.7964C165.87 77.7796 160.158 78.7034 154.437 79.4916C150.648 80.017 146.817 80.4154 142.995 80.5933C135.774 80.9154 128.629 79.9492 121.502 78.9491C119.451 78.661 117.383 78.4576 115.162 78.1949C114.145 87.5261 113.323 96.5184 114.001 105.595ZM177.701 74.0843C177.311 73.1351 177.066 72.4232 176.735 71.7452C174.354 66.9058 171.124 62.719 167.124 59.1001C166.09 58.1678 165.124 57.9136 163.734 58.2271C157.547 59.6171 151.275 60.5578 144.927 60.846C135.257 61.2867 125.697 60.3459 116.264 58.1932C114.95 57.8966 114.044 58.1932 113.086 59.0492C108.882 62.8122 105.543 67.2024 103.144 72.3046C102.899 72.83 102.771 73.4148 102.56 74.042C103.11 74.2284 103.458 74.3895 103.831 74.4657C110.043 75.7201 116.239 77.0592 122.476 78.1949C134.147 80.3222 145.851 80.3137 157.53 78.2203C162.378 77.3473 167.209 76.3557 172.04 75.3896C173.854 75.0251 175.65 74.559 177.693 74.0928L177.701 74.0843ZM62.5139 20.9105C62.5648 21.0546 62.6156 21.1987 62.658 21.3428C63.175 21.3936 63.6835 21.4699 64.2005 21.4868C70.4552 21.7411 76.71 21.9275 82.9647 22.2411C91.2366 22.6479 99.483 23.5802 107.789 23.3938C113.806 23.2581 119.824 23.2497 125.85 23.1734C126.163 23.1734 126.468 23.0717 126.782 23.0548C128.418 22.987 130.053 22.8937 131.689 22.8768C134.562 22.8513 137.427 22.8768 140.639 22.8768C140.003 22.5717 139.639 22.3259 139.241 22.2072C137.113 21.5716 135.223 20.5037 133.477 19.1392C132.808 18.6137 132.053 18.173 131.299 17.7577C124.375 13.9523 117.451 10.1554 110.518 6.35848C108.662 5.34145 106.704 4.65491 104.619 4.35828C103.433 8.02807 102.085 9.4604 99.5169 9.45192C96.0251 9.43497 94.0843 7.50262 93.669 3.68875C82.7274 8.90952 73.3538 16.2322 62.5139 20.9105ZM102.093 75.2709C102.093 75.2709 101.941 75.3387 101.89 75.4743C98.7796 83.0427 98.0761 90.9162 98.9915 98.9762C99.0508 99.5356 99.6441 100.197 100.17 100.519C103.746 102.68 107.721 103.9 111.662 105.18C111.925 105.265 112.238 105.189 112.416 105.189C112.95 96.0523 113.484 87.0261 114.018 77.8728C110.094 77.0168 106.145 76.1523 102.093 75.2709ZM115.883 56.9134C132.172 61.5325 148.114 61.4901 164.149 56.9643C160.768 53.9132 154.818 51.0147 149.292 50.1417C146.072 49.6332 142.758 49.7688 139.495 49.5061C135.206 49.1586 131.062 49.7942 127.002 51.0824C123.036 52.3453 119.349 54.1674 115.883 56.9134ZM165.302 179.517C164.115 180.042 163.183 180.415 162.285 180.856C159.352 182.28 156.216 182.915 152.996 183.127C151.284 183.237 149.546 183.051 147.843 183.22C143.122 183.703 137.876 186.246 138.571 193.755C141.181 193.577 143.86 193.662 146.428 193.17C152.301 192.035 157.666 189.34 163.149 187.077C165.209 186.229 166.183 184.89 165.717 182.644C165.531 181.737 165.472 180.796 165.302 179.517ZM140.173 44.2683C140.173 45.3362 140.088 46.1245 140.207 46.8872C140.275 47.3364 140.597 48.0229 140.927 48.1077C141.698 48.3026 141.664 47.5737 141.741 47.0313C142.495 41.7597 144.165 40.0392 149.326 39.0222C153.403 38.217 156.717 36.0897 159.208 32.7929C159.937 31.8267 160.488 30.6062 160.717 29.4281C161.225 26.8008 159.649 24.9702 156.979 25.0126C155.674 25.0295 154.267 25.1651 153.089 25.6651C147.063 28.2586 142.47 32.2335 141.02 38.9883C140.63 40.8105 140.419 42.675 140.164 44.2599L140.173 44.2683ZM101.424 179.754C103.865 181.347 105.755 183.161 108.238 184.254C113.01 186.356 118.171 186.873 123.138 188.195C124.934 188.678 126.434 187.89 127.519 186.619C128.401 185.585 128.926 184.246 129.621 183.017C127.655 182.237 125.799 181.771 124.239 180.813C121.129 178.915 117.696 177.906 114.298 176.83C109.67 175.355 105.373 175.923 101.432 179.762L101.424 179.754ZM116.73 134.801C115.315 135.123 114.069 135.31 112.891 135.683C109.221 136.852 108.052 140.429 110.331 143.48C112.154 145.912 114.654 147.353 117.595 147.802C118.925 148.006 120.459 148.429 121.875 147.421C118.756 143.743 117.586 139.386 116.722 134.801H116.73ZM99.0847 0.502041C96.6862 0.46814 94.9403 2.01068 94.881 4.24815C94.8132 6.6975 96.576 8.51964 99.0847 8.59591C101.415 8.67219 103.339 6.73139 103.365 4.29051C103.39 1.81573 101.958 0.552901 99.0847 0.510525V0.502041ZM151.08 182.102C155.844 182.441 159.818 180.89 163.675 178.923C164.031 178.745 164.2 178.22 164.454 177.847C164.336 177.61 164.226 177.364 164.107 177.127C159.835 179.517 155.089 179.161 150.318 179.856C150.623 180.754 150.835 181.39 151.08 182.102ZM33.0115 25.021C33.3336 23.631 33.1301 22.8768 31.7995 22.8174C31.0198 22.7835 30.2486 22.5971 29.4688 22.5293C28.6976 22.4615 28.0789 22.6479 28.0111 23.6141C27.9518 24.504 28.384 24.9702 29.2061 25.0126C30.4435 25.0719 31.6893 25.0295 33.0115 25.0295V25.021ZM166.073 23.4276C166.158 24.1396 166.217 24.7074 166.277 25.2329C169.904 25.5888 170.285 25.4193 170.379 23.4276H166.073ZM123.79 179.449C125.367 180.695 127.155 181.356 129.07 181.669C129.935 181.813 130.409 181.5 130.231 180.424C128.087 180.102 125.943 179.779 123.79 179.449Z" fill="black"/> +<path d="M139.435 48.15C138.478 48.2517 137.647 48.3619 136.808 48.4212C123.061 49.3366 112.662 55.9727 105.043 67.1855C98.966 76.1353 96.1691 86.0514 97.6862 96.832C98.5083 102.714 97.7455 108.536 98.0507 114.384C98.0591 114.605 97.9489 114.825 97.8133 115.342C96.2539 115.342 94.6351 115.384 93.0164 115.333C89.8042 115.232 86.6006 114.927 83.3969 114.969C78.5491 115.037 73.7012 115.384 68.8449 115.503C62.0392 115.681 55.2251 115.893 48.411 115.842C43.8767 115.808 39.351 115.359 34.8252 115.088C34.4438 115.062 34.0624 114.978 33.342 114.867C33.342 114.045 33.3589 113.215 33.342 112.393C33.2403 108.875 33.1132 105.358 33.0284 101.841C32.9945 100.587 33.0623 99.3406 33.0793 98.0863C33.164 93.1537 33.3759 88.2211 33.3166 83.2969C33.2149 75.2285 33.2827 67.177 33.8929 59.117C34.6387 49.3535 34.9777 39.5646 34.4777 29.7672C34.3675 27.6823 34.4607 25.5804 34.4607 23.292C34.8336 23.2327 35.3506 23.1395 35.8676 23.0802C45.4616 21.9021 55.0895 22.0208 64.7259 22.47C71.4553 22.7751 78.1846 22.9869 84.914 23.3259C87.9566 23.4785 90.9908 23.8769 94.0334 24.1142C95.5928 24.2413 97.1608 24.2922 98.7202 24.3261C102.322 24.3939 105.924 24.4532 109.526 24.4786C110.619 24.4871 111.713 24.3769 112.806 24.3515C115.781 24.2921 118.756 24.2837 121.731 24.1989C123.451 24.1565 125.163 24.004 126.875 23.9192C128.206 23.8514 129.536 23.7497 130.858 23.7836C134.215 23.8599 137.571 24.0633 140.927 24.0887C142.402 24.0972 143.868 23.6734 145.343 23.6395C151.758 23.4615 158.174 23.3599 164.59 23.2412C164.726 23.2412 164.861 23.3683 165.141 23.4954C165.192 23.8599 165.285 24.3091 165.319 24.7667C165.446 26.5635 165.641 28.3602 165.649 30.157C165.683 38.8272 165.666 47.4889 165.666 56.5066C163.039 54.998 160.785 53.4894 158.352 52.3622C155.877 51.2181 153.284 50.2519 150.648 49.5569C148.021 48.8619 145.275 48.6161 142.588 48.1669C142.334 43.6496 144.843 40.5053 149.241 39.751C153.521 39.0137 156.937 36.7931 159.7 33.5047C160.098 33.0301 160.429 32.4792 160.717 31.9284C161.776 29.9197 162.014 27.8687 160.581 25.9448C159.225 24.1311 157.233 23.9362 155.259 24.4616C147.377 26.5466 141.927 31.2249 140.046 39.3866C139.579 41.4206 139.613 43.5733 139.452 45.6752C139.393 46.438 139.444 47.2177 139.444 48.1669L139.435 48.15Z" fill="white"/> +<path d="M117.9 132.742C117.179 133.233 116.51 133.869 115.713 134.182C114.857 134.521 113.857 134.488 112.959 134.733C110.764 135.335 109.374 136.878 108.416 138.861C107.823 140.107 108.179 141.319 108.679 142.488C109.831 145.192 112.111 146.692 114.654 147.845C116.611 148.726 118.688 149.031 120.841 148.853C121.968 148.76 123.061 148.124 124.265 148.777C124.561 148.938 125.256 148.463 125.722 148.192C129.85 145.802 133.986 143.429 138.071 140.971C140.741 139.361 143.368 137.674 145.927 135.903C146.936 135.199 147.75 134.191 148.572 133.25C149.784 131.852 150.385 130.208 150.47 128.318C150.775 121.41 148.995 115.757 144.393 109.604C144.876 109.494 145.275 109.333 145.673 109.316C152.801 109.002 159.878 108.341 166.819 106.595C171.15 105.502 175.413 104.197 179.337 101.993C179.727 101.773 180.151 101.62 180.693 101.366C180.803 101.841 180.939 102.121 180.93 102.4C180.735 110.07 181.6 117.732 180.947 125.402C180.735 127.894 180.905 130.411 180.862 132.92C180.735 140.039 180.54 147.167 178.862 154.116C177.896 158.134 176.532 162.058 175.226 165.99C174.921 166.922 174.235 167.77 173.582 168.533C169.133 173.694 163.514 176.949 156.852 178.279C154.962 178.661 152.97 178.517 151.021 178.601C145.241 178.847 139.452 179.161 133.672 179.322C127.494 179.491 121.553 178.152 115.705 176.296C110.238 174.559 106.136 170.957 103.076 166.329C101.661 164.193 100.915 161.456 100.458 158.888C99.6694 154.515 99.1354 150.065 98.8727 145.633C98.5506 140.09 98.4913 134.521 98.5507 128.97C98.6524 120.198 98.9405 111.435 99.1609 102.671C99.1694 102.307 99.2372 101.951 99.3134 101.265C104.949 104.417 110.806 106.358 116.883 107.401C122.951 108.443 129.112 108.969 135.333 109.74C135.299 109.825 135.257 110.138 135.079 110.333C132.486 113.121 132.07 116.588 131.918 120.164C131.867 121.41 131.672 122.648 131.638 123.893C131.587 125.945 130.418 127.301 128.748 128.123C126.375 129.301 123.883 130.241 121.442 131.275C120.239 131.784 119.035 132.292 117.832 132.801L117.883 132.75L117.9 132.742Z" fill="white"/> +<path d="M114.001 105.587C113.323 96.5183 114.145 87.5261 115.162 78.1864C117.383 78.4491 119.451 78.6525 121.502 78.9406C128.629 79.9407 135.774 80.9069 142.995 80.5848C146.817 80.4153 150.64 80.0085 154.437 79.4831C160.166 78.6949 165.87 77.7711 171.565 76.7879C173.709 76.415 175.794 75.72 178.057 75.1268C178.303 75.6353 178.625 76.1523 178.828 76.7201C181.363 83.8563 181.981 91.2044 181.286 98.7135C181.202 99.5949 180.829 100.112 180.066 100.527C175.921 102.799 171.514 104.358 166.955 105.528C160.183 107.274 153.25 107.909 146.309 108.468C146.072 108.485 145.834 108.545 145.605 108.511C140.707 107.79 135.774 108.858 130.892 108.299C125.689 107.706 120.527 106.79 115.349 106.002C114.976 105.943 114.62 105.782 114.001 105.587V105.587ZM160.446 97.2388C160.174 95.6285 160.107 93.9504 159.581 92.4333C158.666 89.789 157.564 87.1956 156.344 84.6784C155.649 83.2461 154.665 83.3054 153.64 84.5767C153.394 84.8818 153.14 85.2039 153.004 85.5599C151.945 88.3228 150.741 91.0518 149.919 93.891C149.292 96.0522 149.106 98.3236 150.106 100.544C151.496 103.629 155.479 104.722 158.191 102.705C160.056 101.324 160.403 99.4084 160.446 97.2472V97.2388Z" fill="white"/> +<path d="M177.701 74.0758C175.659 74.5505 173.862 75.0081 172.048 75.3726C167.217 76.3472 162.386 77.3388 157.539 78.2033C145.86 80.2967 134.164 80.3052 122.485 78.1779C116.247 77.0422 110.052 75.7116 103.839 74.4488C103.466 74.3725 103.11 74.2115 102.568 74.025C102.771 73.3978 102.907 72.813 103.153 72.2876C105.551 67.1855 108.891 62.7868 113.094 59.0322C114.052 58.1762 114.959 57.8711 116.272 58.1762C125.705 60.3289 135.266 61.2697 144.936 60.829C151.275 60.5408 157.547 59.6001 163.742 58.2102C165.124 57.8966 166.099 58.1508 167.133 59.0831C171.133 62.702 174.362 66.8889 176.744 71.7282C177.074 72.4063 177.32 73.1266 177.71 74.0674L177.701 74.0758Z" fill="white"/> +<path d="M117.849 132.793C118.535 132.725 119.273 132.776 119.917 132.564C122.578 131.657 125.214 130.665 127.85 129.699C131.604 128.318 132.952 125.419 133.045 121.639C133.104 119.461 133.206 117.266 133.579 115.13C133.943 113.096 135.062 111.367 136.715 110.028C138.605 108.494 140.825 108.282 142.724 109.808C143.987 110.816 145.249 112.011 146.021 113.401C148.716 118.215 149.996 123.41 149.207 128.953C148.724 132.377 146.606 134.81 143.783 136.547C137.656 140.31 131.435 143.929 125.248 147.599C125.053 147.717 124.824 147.794 124.332 148.006C123.688 147.421 122.807 146.861 122.256 146.082C119.764 142.565 118.552 138.522 117.713 134.352C117.612 133.852 117.823 133.284 117.891 132.742L117.84 132.793H117.849Z" fill="white"/> +<path d="M102.093 75.2624C106.145 76.1438 110.094 77.0083 114.018 77.8643C113.484 87.0176 112.95 96.0438 112.416 105.18C112.238 105.18 111.925 105.256 111.662 105.172C107.721 103.883 103.746 102.663 100.17 100.51C99.6441 100.197 99.0593 99.5271 98.9915 98.9677C98.0762 90.9077 98.7796 83.0342 101.89 75.4658C101.941 75.3387 102.11 75.2539 102.093 75.2624V75.2624Z" fill="#C8E2E2"/> +<path d="M115.883 56.9134C119.349 54.1674 123.036 52.3452 127.002 51.0824C131.062 49.7942 135.215 49.1585 139.495 49.506C142.758 49.7688 146.072 49.6332 149.292 50.1417C154.818 51.0146 160.768 53.9132 164.149 56.9643C148.114 61.4816 132.172 61.5324 115.883 56.9134V56.9134Z" fill="white"/> +<path d="M165.302 179.517C165.463 180.796 165.522 181.729 165.717 182.644C166.183 184.899 165.209 186.229 163.149 187.077C157.657 189.34 152.301 192.026 146.428 193.17C143.86 193.67 141.19 193.577 138.571 193.755C137.876 186.238 143.122 183.703 147.843 183.22C149.546 183.042 151.284 183.237 152.996 183.127C156.217 182.915 159.352 182.288 162.285 180.856C163.192 180.415 164.124 180.042 165.302 179.517V179.517Z" fill="white"/> +<path d="M140.173 44.2598C140.427 42.675 140.639 40.8104 141.029 38.9883C142.478 32.2419 147.072 28.2585 153.098 25.6651C154.276 25.1566 155.683 25.0295 156.988 25.0125C159.658 24.9702 161.234 26.8008 160.725 29.4281C160.497 30.6062 159.937 31.8266 159.217 32.7928C156.725 36.0897 153.42 38.217 149.335 39.0221C144.173 40.0392 142.504 41.7597 141.749 47.0313C141.673 47.5737 141.698 48.3026 140.936 48.1076C140.605 48.0229 140.283 47.3449 140.215 46.8872C140.097 46.1244 140.181 45.3362 140.181 44.2683L140.173 44.2598Z" fill="white"/> +<path d="M101.432 179.745C105.382 175.906 109.67 175.338 114.298 176.813C117.696 177.898 121.129 178.898 124.239 180.796C125.799 181.754 127.655 182.212 129.621 183C128.926 184.229 128.401 185.568 127.519 186.602C126.426 187.873 124.934 188.661 123.137 188.178C118.179 186.856 113.01 186.339 108.238 184.237C105.763 183.144 103.865 181.33 101.424 179.737L101.432 179.745Z" fill="white"/> +<path d="M116.739 134.793C117.603 139.378 118.773 143.743 121.892 147.412C120.476 148.421 118.942 147.997 117.612 147.794C114.671 147.345 112.162 145.904 110.348 143.471C108.068 140.42 109.238 136.844 112.908 135.674C114.086 135.301 115.323 135.106 116.747 134.793H116.739Z" fill="white"/> +<path d="M99.0846 0.493516C101.958 0.535892 103.39 1.8072 103.365 4.2735C103.339 6.71438 101.415 8.65523 99.0846 8.57896C96.5675 8.50268 94.8046 6.68049 94.8809 4.23114C94.9487 2.00214 96.6861 0.451183 99.0846 0.485084V0.493516Z" fill="white"/> +<path d="M151.081 182.085C150.835 181.364 150.623 180.737 150.318 179.839C155.089 179.152 159.835 179.508 164.107 177.11C164.226 177.347 164.336 177.593 164.455 177.83C164.2 178.195 164.022 178.728 163.675 178.906C159.819 180.873 155.844 182.424 151.081 182.085V182.085Z" fill="white"/> +<path d="M33.02 25.004C31.6978 25.004 30.452 25.0464 29.2146 24.9871C28.3925 24.9532 27.9602 24.487 28.0196 23.5886C28.0874 22.6225 28.7061 22.4445 29.4773 22.5038C30.257 22.5716 31.0283 22.7496 31.808 22.792C33.1386 22.8598 33.342 23.6056 33.02 24.9956V25.004Z" fill="white"/> +<path d="M166.082 23.4107H170.387C170.294 25.4024 169.904 25.5719 166.285 25.2159C166.226 24.6905 166.158 24.1226 166.082 23.4107Z" fill="white"/> +<path d="M123.799 179.432C125.943 179.754 128.087 180.076 130.24 180.407C130.426 181.491 129.952 181.797 129.079 181.652C127.163 181.339 125.375 180.678 123.799 179.432Z" fill="white"/> +<path d="M160.446 97.2472C160.412 99.4169 160.064 101.324 158.191 102.705C155.479 104.722 151.496 103.638 150.106 100.544C149.106 98.3236 149.292 96.0522 149.919 93.891C150.741 91.0518 151.936 88.3228 153.004 85.5599C153.14 85.2039 153.394 84.8818 153.64 84.5767C154.665 83.3054 155.649 83.2461 156.344 84.6784C157.573 87.1955 158.666 89.789 159.581 92.4333C160.107 93.9504 160.174 95.6284 160.446 97.2387V97.2472ZM154.903 84.5937C154.606 84.9157 154.31 85.0937 154.216 85.348C153.081 88.5855 151.894 91.8146 150.894 95.0945C150.411 96.6709 150.445 98.3405 151.131 99.9339C151.919 101.773 154.182 103.027 156.106 102.646C157.912 102.282 159.157 100.62 159.284 98.5355C159.429 96.1539 159.149 93.8402 158.284 91.6196C157.361 89.2381 156.581 86.7803 154.903 84.5937V84.5937Z" fill="black"/> +<path d="M154.903 84.5852C156.572 86.7633 157.352 89.2296 158.284 91.6112C159.149 93.8317 159.429 96.1455 159.284 98.527C159.157 100.62 157.911 102.282 156.106 102.638C154.182 103.019 151.928 101.765 151.131 99.9254C150.445 98.3321 150.411 96.6625 150.894 95.0861C151.894 91.8061 153.081 88.5771 154.216 85.3395C154.301 85.0853 154.598 84.9073 154.903 84.5852V84.5852ZM155.403 101.231C154.784 100.502 154.428 100.103 154.106 99.6797C153.801 99.2813 153.53 98.8575 153.14 98.2812C152.725 100.358 153.047 100.815 155.403 101.231V101.231Z" fill="white"/> +<path d="M155.403 101.239C153.047 100.824 152.725 100.366 153.14 98.2897C153.53 98.866 153.801 99.2898 154.106 99.6881C154.428 100.112 154.784 100.502 155.403 101.239V101.239Z" fill="black"/> +<path d="M106.053 133.789C106.053 133.789 107.632 138.789 108.34 138.914C109.047 139.04 108.987 134.803 115.38 134.353L114.874 129.307L106.053 133.789Z" fill="#7EDDDD" stroke="black" stroke-width="0.4"/> +<path d="M107.053 134.789C107.053 134.789 107.632 138.789 108.34 138.914C109.047 139.04 108.987 134.803 115.38 134.353L115.874 130.307L107.053 134.789Z" fill="#F9E6A6" stroke="black" stroke-width="0.4"/> +</g> +<defs> +<clipPath id="clip0_8_72"> +<rect width="210" height="200" fill="white"/> +</clipPath> +</defs> +</svg> diff --git a/frontend/app/svg/ca-no-recordings.svg b/frontend/app/svg/ca-no-recordings.svg new file mode 100644 index 000000000..4c5c3b173 --- /dev/null +++ b/frontend/app/svg/ca-no-recordings.svg @@ -0,0 +1,71 @@ +<svg width="210" height="160" viewBox="0 0 210 160" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect x="13" y="7.86084" width="88.2438" height="80.702" rx="4" fill="#FDF9F3"/> +<rect x="103.398" y="31.9722" width="88.2438" height="80.702" rx="40.351" fill="#E4EDF6"/> +<g clip-path="url(#clip0_299_2)"> +<path d="M198.247 49.1014H55.7612V137.451H198.247V49.1014Z" fill="white"/> +<path d="M198.298 137.51C198.084 137.51 55.7384 137.665 55.539 137.665C55.539 137.104 55.4873 49.3895 55.4873 48.8356C96.0141 48.7322 158.17 48.3187 198.239 48.3925H198.948V49.1015C198.889 77.2664 198.446 109.426 198.298 137.51ZM198.18 137.392C198.129 116.996 197.759 91.5851 197.619 71.0707L197.53 49.1015L198.239 49.8104C158.451 49.8104 95.9919 49.5519 55.7532 49.3673C56.2405 47.4695 56.1593 138.876 55.7532 137.237C55.7605 137.237 198.18 137.392 198.18 137.392Z" fill="black"/> +<path d="M72.5015 81.7046V63.9593H97.8972" fill="white"/> +<path d="M72.3391 81.7045C72.1766 76.5796 72.1692 69.1581 72.1323 63.9593V63.5901H72.5016C79.9822 63.6196 90.52 63.6344 97.8972 63.7969V64.1218C90.5643 64.2695 79.9452 64.3064 72.5016 64.3285L72.8708 63.9593L72.8191 72.8356C72.7896 75.7969 72.7379 78.7507 72.664 81.7119C72.664 81.7046 72.3391 81.7045 72.3391 81.7045Z" fill="black"/> +<path d="M159.596 63.9593H184.991V81.7046" fill="white"/> +<path d="M159.596 63.7821C166.929 63.6344 177.548 63.6196 184.991 63.5901H185.361V63.9593L185.316 72.8356C185.294 75.7969 185.242 78.7507 185.169 81.7119H184.814C184.681 76.587 184.637 69.1655 184.622 63.9667L184.991 64.3359L172.297 64.2916C168.066 64.2695 163.834 64.2178 159.603 64.1439C159.596 64.1365 159.596 63.7821 159.596 63.7821Z" fill="black"/> +<path d="M95.3566 133.316H69.9609V115.57" fill="white"/> +<path d="M95.3568 133.471C88.0239 133.869 77.4048 133.604 69.9611 133.641H69.6362V133.316L69.6953 124.439L69.8061 115.563H70.1088L70.2196 124.439L70.2787 133.316L69.9538 132.991C74.4067 132.976 84.6861 132.924 88.9987 132.969C91.1181 132.983 93.2301 133.05 95.3495 133.161C95.3568 133.161 95.3568 133.471 95.3568 133.471Z" fill="black"/> +<path d="M185.198 115.57V133.316H159.802" fill="white"/> +<path d="M185.316 115.57C185.493 121.323 185.545 127.585 185.567 133.316V133.685H185.198C182.03 133.677 175.664 133.655 172.504 133.633C169.343 133.611 162.963 133.515 159.81 133.441V133.197C162.978 133.124 169.336 133.028 172.504 133.005C175.672 132.983 182.037 132.961 185.198 132.954L184.829 133.323C184.851 129.047 184.873 124.306 184.954 120.009L185.072 115.57H185.316Z" fill="black"/> +<path d="M115.03 87.3021H121.417" stroke="black" stroke-width="0.738462" stroke-miterlimit="10"/> +<path d="M114.815 94.251L114.66 87.3021H115.399L115.244 94.251H114.815Z" fill="black"/> +<path d="M143.328 87.3021V94.251" stroke="black" stroke-width="0.738462" stroke-miterlimit="10"/> +<path d="M136.939 86.9329L143.327 87.1175V87.4867L136.939 87.6713V86.9329Z" fill="black"/> +<path d="M115.03 114.448V107.499" stroke="black" stroke-width="0.738462" stroke-miterlimit="10"/> +<path d="M121.417 114.802L115.03 114.817V114.079L121.417 114.093V114.802Z" fill="black"/> +<path d="M143.379 114.448H136.991" stroke="black" stroke-width="0.738462" stroke-miterlimit="10"/> +<path d="M143.571 107.499L143.748 114.448H143.01L143.194 107.499H143.571Z" fill="black"/> +<path d="M160.637 72.8356C162.167 72.8356 163.407 71.5958 163.407 70.0664C163.407 68.537 162.167 67.2971 160.637 67.2971C159.108 67.2971 157.868 68.537 157.868 70.0664C157.868 71.5958 159.108 72.8356 160.637 72.8356Z" fill="#FAE3E3"/> +<path d="M163.221 70.0664C163.221 66.699 158.237 66.7211 158.222 70.0664C158.377 73.2713 163.029 73.2935 163.221 70.0664ZM163.591 70.0664C163.561 74.1575 157.594 74.1722 157.521 70.0664C157.72 66.1008 163.384 66.123 163.591 70.0664Z" fill="black"/> +<path d="M89.907 67.2971H79.9229C78.6011 67.2971 77.5229 68.3679 77.5229 69.6971C77.5229 71.019 78.5937 72.0971 79.9229 72.0971H89.907C91.2288 72.0971 92.307 71.0264 92.307 69.6971C92.2996 68.3679 91.2288 67.2971 89.907 67.2971Z" fill="#EFF7FC"/> +<path d="M89.9067 67.4301C86.6279 67.4449 83.3344 67.4965 80.0556 67.5704C78.3276 67.4596 77.1977 69.6233 78.3054 70.9452C78.6894 71.4399 79.3024 71.7279 79.9227 71.7279C83.2014 71.7427 86.495 71.7944 89.7737 71.8682C92.8162 71.979 92.927 67.5409 89.9067 67.4301ZM89.9067 67.1643C93.3774 67.2603 93.2224 72.3335 89.7737 72.3187C86.495 72.3925 83.2014 72.4442 79.9227 72.459C79.0808 72.4516 78.2464 72.0455 77.7516 71.3735C76.3264 69.6012 77.8033 66.8319 80.0556 67.0239L81.1485 67.0461C84.0654 67.1199 86.9824 67.1495 89.9067 67.1643Z" fill="black"/> +<path d="M167.161 72.0309V66.4108H169.06C169.499 66.4108 169.86 66.4858 170.141 66.6358C170.423 66.784 170.632 66.988 170.767 67.2478C170.902 67.5076 170.97 67.803 170.97 68.1342C170.97 68.4653 170.902 68.7589 170.767 69.0151C170.632 69.2712 170.424 69.4724 170.144 69.6188C169.864 69.7633 169.506 69.8356 169.071 69.8356H167.534V69.2209H169.049C169.349 69.2209 169.591 69.177 169.774 69.0891C169.958 69.0013 170.092 68.8769 170.174 68.7159C170.258 68.5531 170.3 68.3592 170.3 68.1342C170.3 67.9091 170.258 67.7125 170.174 67.5442C170.09 67.3758 169.956 67.246 169.771 67.1545C169.586 67.0612 169.342 67.0145 169.038 67.0145H167.842V72.0309H167.161ZM169.806 69.5063L171.19 72.0309H170.399L169.038 69.5063H169.806ZM172.104 72.0309V66.4108H175.496V67.0145H172.785V68.9135H175.32V69.5172H172.785V71.4272H175.54V72.0309H172.104ZM181.245 68.1671H180.564C180.524 67.9713 180.454 67.7994 180.353 67.6512C180.254 67.503 180.134 67.3786 179.991 67.278C179.85 67.1755 179.694 67.0987 179.522 67.0475C179.35 66.9962 179.17 66.9706 178.984 66.9706C178.644 66.9706 178.335 67.0566 178.059 67.2286C177.785 67.4005 177.566 67.6539 177.403 67.9887C177.242 68.3235 177.162 68.7342 177.162 69.2209C177.162 69.7075 177.242 70.1182 177.403 70.453C177.566 70.7878 177.785 71.0412 178.059 71.2132C178.335 71.3851 178.644 71.4711 178.984 71.4711C179.17 71.4711 179.35 71.4455 179.522 71.3943C179.694 71.3431 179.85 71.2671 179.991 71.1665C180.134 71.0641 180.254 70.9387 180.353 70.7906C180.454 70.6405 180.524 70.4686 180.564 70.2746H181.245C181.194 70.5619 181.1 70.8189 180.965 71.0458C180.83 71.2726 180.661 71.4656 180.46 71.6248C180.259 71.7821 180.033 71.902 179.782 71.9843C179.534 72.0666 179.267 72.1078 178.984 72.1078C178.504 72.1078 178.078 71.9907 177.705 71.7565C177.332 71.5224 177.038 71.1894 176.824 70.7576C176.61 70.3259 176.503 69.8136 176.503 69.2209C176.503 68.6281 176.61 68.1159 176.824 67.6841C177.038 67.2524 177.332 66.9194 177.705 66.6852C178.078 66.451 178.504 66.334 178.984 66.334C179.267 66.334 179.534 66.3751 179.782 66.4574C180.033 66.5398 180.259 66.6605 180.46 66.8197C180.661 66.977 180.83 67.1691 180.965 67.396C181.1 67.621 181.194 67.878 181.245 68.1671Z" fill="white"/> +<path d="M63.1377 59.2332C104.24 58.6941 151.657 59.2406 192.871 59.2332V59.5359C163.975 59.6689 124.467 59.691 95.5709 59.7279L79.3543 59.7501C73.9488 59.7575 68.5432 59.7796 63.1377 59.5359V59.2332Z" fill="black"/> +<path d="M65.7594 56.7408C67.443 56.7408 67.443 54.1267 65.7594 54.1267C64.0757 54.1193 64.0757 56.7408 65.7594 56.7408Z" fill="#F0C3BA"/> +<path d="M71.2847 56.7408C72.9684 56.7408 72.9684 54.1267 71.2847 54.1267C69.601 54.1193 69.601 56.7408 71.2847 56.7408Z" fill="#E9CC9E"/> +<path d="M76.8129 56.7408C78.4966 56.7408 78.4966 54.1267 76.8129 54.1267C75.1292 54.1267 75.1218 56.7408 76.8129 56.7408Z" fill="#71BCBE"/> +<path d="M128.373 106.465C131.338 106.465 133.742 104.062 133.742 101.097C133.742 98.1315 131.338 95.7279 128.373 95.7279C125.408 95.7279 123.005 98.1315 123.005 101.097C123.005 104.062 125.408 106.465 128.373 106.465Z" fill="white"/> +<path d="M133.388 101.097C133.506 96.6436 127.871 94.2805 124.748 97.4707C122.732 99.3612 122.761 102.832 124.762 104.715C127.886 107.883 133.439 105.52 133.388 101.097ZM134.111 101.097C134.17 106.103 127.864 108.651 124.401 105.077C122.244 102.98 122.266 99.2356 124.415 97.131C127.886 93.5642 134.104 96.1119 134.111 101.097Z" fill="black"/> +<path d="M133.964 95.9421L123.271 106.724L122.747 106.207L133.528 95.5064L133.964 95.9421Z" fill="black"/> +</g> +<g clip-path="url(#clip1_299_2)"> +<path d="M49.6948 69.5582C49.6948 69.5582 27.5096 74.2763 27.978 93.1622C28.3378 107.561 31.9901 126.352 33.7076 134.464C34.6376 138.863 37.387 142.733 41.372 144.803C43.5443 145.93 46.1851 146.609 49.24 146.113C49.5387 146.066 49.8306 146.018 50.1293 145.984C53.2317 145.638 71.9207 143.513 77.5417 141.782C77.5417 141.782 88.3967 139.42 84.147 112.517L79.7819 90.0191C77.8879 80.2638 70.7056 72.2193 61.0861 69.7347C57.6578 68.8522 53.8223 68.6146 49.688 69.5582H49.6948Z" fill="white"/> +<path d="M49.7222 69.6872C38.1951 72.5656 27.2723 80.9834 28.2498 93.9565C28.7522 105.803 30.7005 117.554 32.9543 129.183C33.511 131.382 33.8844 134.749 34.7533 136.82C37.0071 142.936 43.6532 147.179 50.1567 145.652C59.267 144.518 68.581 143.71 77.4944 141.511C86.1295 137.96 85.2742 122.401 84.2287 114.696C83.7535 110.718 81.7102 101.166 80.9906 97.0928C80.2234 93.7121 79.6668 88.6817 78.3294 85.4979C74.2563 74.2084 61.5344 66.7681 49.7358 69.694L49.7222 69.6872ZM49.6679 69.4292C61.6294 66.5373 74.3649 74.0115 78.5874 85.3893C79.9587 88.5731 80.5561 93.6171 81.3232 97.0182C83.3801 108.328 87.0935 121.057 84.4527 132.523C83.5227 136.358 81.6219 140.594 77.603 142.047C70.0269 143.995 61.9757 144.79 54.1552 145.828L50.2246 146.29C48.9211 146.487 47.5498 146.697 46.2261 146.561C40.7544 146.141 36.0499 142.081 34.1491 137.051C33.3005 134.885 32.8525 131.579 32.3433 129.298C30.2389 118.28 28.4399 107.167 27.8425 95.9523L27.7746 93.9701C27.5642 89.9851 28.372 85.9052 30.3814 82.4362C34.441 75.4847 42.0579 71.3572 49.6679 69.4292Z" fill="#231F20"/> +<path d="M51.216 69.2595C51.216 69.2595 50.958 68.8115 50.4964 68.0715C44.7464 58.8797 34.0815 53.7883 23.3759 55.499C20.674 55.9335 18.1962 56.7549 16.5533 58.2009C15.182 59.4092 14.4828 61.2693 14.9716 63.0343C15.5961 65.2678 18.1215 67.9221 27.1843 66.2725C27.2657 66.2589 27.3472 66.2386 27.4287 66.225C28.6914 65.9399 40.4017 63.3398 44.407 65.3425L49.6885 69.5582L51.2092 69.2595H51.216Z" fill="white"/> +<path d="M51.0324 69.178C49.8852 67.637 48.8737 65.9331 47.5363 64.5346C44.624 61.2761 40.897 58.7371 36.8239 57.1758C30.9517 55.0577 24.0205 54.6708 18.2977 57.3998C13.0026 60.2306 14.7744 66.1028 20.6194 66.3879C23.7897 66.7002 26.9736 66.001 30.0692 65.3764C33.2463 64.7994 36.4505 64.3378 39.6887 64.3242C41.3111 64.3513 43.0151 64.4056 44.5697 65.1456L49.8173 69.3953C49.8376 69.4021 49.6544 69.3545 49.6544 69.3545C50.1092 69.2595 50.5437 69.2188 51.0257 69.1712L51.0324 69.178ZM51.399 69.3478C50.8424 69.5039 50.2314 69.6668 49.6408 69.7754L49.5661 69.7211L44.2574 65.5461C43.1033 65.0506 41.2025 64.8605 39.6955 64.8808C36.5048 64.908 33.3209 65.39 30.1846 65.9738C27.0075 66.6323 23.8305 67.3451 20.5583 67.0464C14.0616 66.7409 12.0997 59.993 17.999 56.8431C23.9119 54.0394 30.9924 54.4535 37.0275 56.653C43.0898 59.0087 48.446 63.5299 51.4058 69.3545L51.399 69.3478Z" fill="#231F20"/> +<path d="M81.5205 98.9393C81.5205 98.9393 96.5912 95.1377 98.1051 95.4432C98.1051 95.4432 104.303 94.3842 105.973 103.006C107.643 111.627 83.7675 110.507 83.7675 110.507L81.5205 98.9393Z" fill="white"/> +<path d="M81.3979 98.8646C81.6899 98.7085 81.8256 98.7289 82.0021 98.6678C82.3144 98.5931 83.2173 98.3487 83.5432 98.2672C84.833 97.9278 88.3834 97.0521 89.7208 96.7262C92.314 96.156 95.8509 95.1649 98.0504 95.1105C102.64 94.6353 105.484 98.6474 106.19 102.707C107.432 108.986 97.5616 109.957 93.2441 110.446C90.0466 110.718 86.8492 110.785 83.645 110.65L83.6246 110.534L81.3979 98.8646ZM81.6491 99.0208L83.9165 110.48C83.9165 110.48 83.774 110.358 83.7808 110.358C86.9239 110.453 90.0806 110.358 93.2101 110.052C97.0321 109.55 103.841 108.919 105.586 105.002C106.013 103.983 105.62 102.721 105.369 101.689C102.735 93.1758 96.4483 95.9591 89.8362 97.1946C88.4853 97.4933 84.9416 98.2808 83.6314 98.5931C83.3327 98.6678 82.3823 98.885 82.0836 98.9597C81.9207 98.9868 81.7034 99.0887 81.6559 99.0208H81.6491Z" fill="#231F20"/> +<path d="M29.139 107.615C29.139 107.615 15.4735 126.603 14.815 131.287C14.815 131.287 12.6494 139.759 21.1148 142.482C21.1148 142.482 28.4533 146.256 33.7077 134.464L29.139 107.615Z" fill="white"/> +<path d="M29.1055 107.758C29.1191 107.934 29.1055 107.907 29.0036 108.056L28.7593 108.409L28.2637 109.122C27.6595 109.998 25.9691 112.51 25.3514 113.433C22.7921 117.275 20.2531 121.152 17.9586 125.157C16.8656 127.126 15.664 129.142 15.1413 131.335C14.2248 135.252 15.7862 139.814 19.6082 141.524C21.3257 142.319 23.179 143.045 25.1409 142.651C28.5692 142.054 30.9859 139.121 32.6288 136.243L33.5724 134.403C33.5927 134.831 29.0987 107.656 29.0987 107.751L29.1055 107.758ZM29.1666 107.479C29.1734 107.527 33.8439 134.444 33.8507 134.485C33.8507 134.485 33.8303 134.518 33.8303 134.525L32.9275 136.412C31.2982 139.481 28.8407 142.536 25.236 143.215C23.1315 143.663 21.1696 142.95 19.3231 142.095C15.2363 140.268 13.4713 135.428 14.4828 131.212C15.698 126.888 18.6578 122.828 21.0745 118.939C22.9007 116.128 25.0187 113.094 26.9738 110.351C27.2725 109.95 28.1958 108.64 28.5013 108.24L28.7593 107.887L28.9018 107.69C28.9765 107.595 29.0851 107.527 29.1598 107.479H29.1666Z" fill="#231F20"/> +<path d="M103.495 97.6902C103.088 97.181 103.162 96.4411 103.665 96.0338C104.866 95.0562 107.195 93.7053 109.883 94.839C112.32 95.8708 113.148 98.7356 113.427 100.772C113.678 102.605 113.128 104.506 111.763 105.762C110.474 106.95 108.403 107.737 105.273 106.108C105.273 106.108 107.955 103.243 103.488 97.6834L103.495 97.6902Z" fill="white"/> +<path d="M103.257 97.8803C101.56 95.545 106.767 93.6985 108.512 94.1194C112.442 94.5742 114.119 99.2041 113.685 102.673C113.338 105.11 111.207 107.316 108.661 107.283C107.31 107.303 106.075 106.787 104.921 106.196C107.039 103.623 105.036 100.08 103.257 97.8803ZM103.733 97.5001C105.572 99.8694 107.521 103.467 105.45 106.271L105.389 105.911C107.364 106.997 109.978 107.255 111.648 105.517C114.771 102.687 112.856 95.1513 108.423 94.7847C107.33 94.6489 106.21 94.9001 105.212 95.4092C104.479 95.8437 103.013 96.4683 103.726 97.5001H103.733Z" fill="#231F20"/> +<path d="M15.3651 137.451C15.0188 136.691 14.0277 136.514 13.4371 137.112C12.1744 138.388 10.6334 140.696 11.5906 143.853C12.5953 147.172 16.5531 148.021 19.0852 148.211C20.7416 148.34 22.3777 147.77 23.5521 146.589C24.3803 145.754 25.1882 144.593 25.5073 143.065C25.5073 143.065 18.2299 143.737 15.3651 137.458V137.451Z" fill="white"/> +<path d="M15.114 137.56C13.7834 135.496 11.6315 140.038 11.6518 141.212C11.197 145.082 14.1636 147.193 17.6733 147.709C19.8525 148.204 22.2828 147.797 23.7423 145.964C24.4891 145.122 25.0458 144.111 25.2969 143.011L25.5345 143.276C21.3324 143.561 16.8791 141.531 15.1276 137.56H15.114ZM15.6096 137.336C17.2388 141.131 21.4953 143.072 25.4802 142.841L25.7789 142.828L25.7178 143.106C25.1612 146.086 22.4797 148.679 19.3365 148.53C4.99898 147.831 12.9213 132.801 15.6096 137.342V137.336Z" fill="#231F20"/> +<path d="M27.9717 95.4024C27.9717 95.4024 50.4216 73.6314 78.9542 86.8081Z" fill="white"/> +<path d="M27.8154 95.2395C31.0197 91.947 35.1064 89.5982 39.1524 87.4666C47.3531 83.3391 56.7553 81.4111 65.8996 82.667C70.4547 83.3051 74.8877 84.6764 79.0559 86.5976L78.8658 87.0117C74.7248 85.1448 70.3257 83.8211 65.8249 83.2305C54.5083 81.6962 42.9405 85.1381 33.5654 91.4786C31.651 92.7006 29.9471 94.2144 28.1345 95.5654L27.8154 95.2327V95.2395Z" fill="#231F20"/> +<path d="M29.75 111.2C29.75 111.2 54.3452 96.957 81.52 101.648Z" fill="white"/> +<path d="M29.6416 111.016C32.4385 109.129 35.6495 108.002 38.7383 106.685C43.9316 104.662 49.3761 103.053 54.8409 101.994C60.2379 100.881 66.0218 100.406 71.5341 100.46C73.4417 100.596 76.3541 100.664 78.2277 100.989L81.5541 101.444L81.4795 101.865L78.1666 101.444C66.0218 100.026 53.5783 102.089 42.0919 106.122C39.071 107.228 35.7378 108.559 32.8662 109.978C31.8615 110.453 30.8975 111.009 29.852 111.39L29.6416 111.023V111.016Z" fill="#231F20"/> +<path d="M68.8597 83.4545L70.1223 100.698C70.1223 100.698 55.6083 100.433 44.0065 105.124L38.3584 88.9872C38.3584 88.9872 48.6839 79.1777 68.8529 83.4545H68.8597Z" fill="#C8E2E2"/> +<path d="M68.9612 83.3662L70.2306 100.691V100.806C65.7705 100.793 61.2968 101.173 56.8774 101.852C52.4988 102.531 48.1744 103.603 44.047 105.226L43.9451 105.266L43.9112 105.164L38.0458 89.1026L37.9644 88.8854L38.1273 88.7428C46.6877 81.866 58.5882 81.0581 68.9612 83.3662ZM68.7575 83.5427C58.561 81.574 46.8778 82.3955 38.5957 89.2316L38.6839 88.8718L44.1081 105.09C44.1081 105.09 43.9655 105.029 43.9723 105.029C48.1133 103.399 52.458 102.327 56.8503 101.648C61.2425 100.969 65.6823 100.589 70.1288 100.596C70.1288 100.596 70.0202 100.704 70.027 100.704L68.7643 83.5427H68.7575Z" fill="#231F20"/> +<path d="M46.8301 146.276C46.8301 146.276 46.6808 149.739 48.344 149.589C48.344 149.589 56.212 149.413 59.8371 148.218C59.8371 148.218 60.9165 146.704 60.9029 144.667L46.8301 146.276Z" fill="white" stroke="#231F20" stroke-width="0.678862" stroke-miterlimit="10"/> +<path d="M63.0074 144.382C63.0074 144.382 62.5458 146.337 63.0074 147.132C63.469 147.926 72.6948 147.2 74.8128 146.276C74.8128 146.276 76.1773 145.367 76.0212 142.183L63.0006 144.382H63.0074Z" fill="white" stroke="#231F20" stroke-width="0.678862" stroke-miterlimit="10"/> +<path d="M74.8135 146.276C74.8135 146.276 88.2074 156.697 85.1389 159.419C82.0705 162.141 62.7976 161.388 62.7976 161.388C62.7976 161.388 57.8487 156.921 63.0013 147.132C63.0013 147.132 66.3345 147.96 74.8067 146.276H74.8135Z" fill="white"/> +<path d="M74.8946 145.95C75.0983 146.025 75.3359 146.276 75.5192 146.412C77.7051 148.279 88.2207 156.697 85.3355 159.596C84.6023 160.2 83.7538 160.424 82.9391 160.648C79.6331 161.415 76.2523 161.517 72.8852 161.632C69.5045 161.68 66.0626 161.741 62.7091 161.51C59.0636 157.464 60.6046 151.409 62.8856 147.071C62.9467 146.948 62.9059 146.962 63.0553 146.996C66.9384 147.512 71.0726 146.65 74.9014 145.957L74.8946 145.95ZM74.7385 146.609C71.8669 147.179 68.9003 147.519 65.9744 147.525C64.9765 147.512 63.9785 147.485 62.9738 147.274L63.1435 147.2C60.8015 151.544 59.5727 157.416 62.8109 161.238C67.8345 161.367 72.8784 161.361 77.8816 160.926C80.2508 160.614 82.9799 160.566 84.9893 159.209C87.1141 156.663 77.5558 148.985 75.5871 147.322C75.3223 147.125 74.9286 146.779 74.6706 146.589C74.6231 146.555 74.6774 146.602 74.7385 146.602V146.609Z" fill="#231F20"/> +<path d="M48.344 149.589C48.344 149.589 41.3856 155.638 42.146 159.568C42.9063 163.499 62.1384 162.596 62.8037 161.381C62.8037 161.381 64.6842 158.027 59.8439 148.211C59.8439 148.211 49.4166 150.248 48.3507 149.582L48.344 149.589Z" fill="white"/> +<path d="M48.3715 149.929C48.4123 149.942 48.4598 149.935 48.4801 149.922C48.4937 149.915 48.4462 149.956 48.4258 149.969L48.2901 150.092C46.9391 151.327 45.6629 152.678 44.5495 154.131C43.4701 155.631 42.255 157.403 42.3975 159.297C42.9203 163.031 58.731 162.359 61.779 161.619C62.0845 161.53 62.4511 161.483 62.648 161.272C63.4423 158.828 62.6751 156.161 61.9691 153.757C61.3649 151.877 60.5842 150.064 59.7289 148.279C59.7153 148.265 59.8579 148.347 59.8579 148.34C56.5247 149.019 53.1847 149.732 49.7903 150.03C49.3219 150.051 48.8399 150.092 48.3715 149.976V149.969V149.942V149.929ZM48.3172 149.25C48.4598 149.277 48.5888 149.338 48.7381 149.358C50.5167 149.501 52.3089 149.182 54.074 148.985C55.9884 148.727 57.9027 148.428 59.8171 148.096L59.9122 148.082L59.9529 148.164C61.6501 151.734 63.3268 155.495 63.3268 159.534C63.2725 160.227 63.2997 161.082 62.8109 161.632C62.5258 161.822 62.1864 161.904 61.8741 161.978C57.896 162.8 44.6174 163.506 42.0988 160.173C40.6325 156.758 45.561 151.789 47.842 149.589C47.9846 149.467 48.1339 149.29 48.3172 149.25Z" fill="#231F20"/> +<path d="M51.2158 88.9872L52.6211 95.9455L58.1063 90.9559L51.2158 88.9872Z" fill="white" stroke="#231F20" stroke-width="0.678862" stroke-miterlimit="10"/> +</g> +<defs> +<clipPath id="clip0_299_2"> +<rect width="144" height="90.0923" fill="white" transform="translate(55 47.8608)"/> +</clipPath> +<clipPath id="clip1_299_2"> +<rect width="102.752" height="107.81" fill="white" transform="translate(11 54.8608)"/> +</clipPath> +</defs> +</svg> diff --git a/frontend/app/svg/ca-no-search-results.svg b/frontend/app/svg/ca-no-search-results.svg new file mode 100644 index 000000000..d595ee688 --- /dev/null +++ b/frontend/app/svg/ca-no-search-results.svg @@ -0,0 +1,52 @@ +<svg viewBox="0 0 210 197" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect width="210" height="197" fill="white"/> +<path d="M11.7727 185.874C15.0263 184.5 15.9445 184.649 20.3026 183.518C29.6116 181.102 30.5103 181.623 41.8592 180.47C67.2667 177.889 94.8738 177.591 121.082 178.953C138.273 179.847 154.41 181.603 170.471 183.704C178.025 184.693 186.937 186.093 197.768 187.542" stroke="black" stroke-linecap="round"/> +<path d="M133.583 130.96C132.961 131.32 132.503 131.667 131.991 131.875C130.106 132.637 128.928 134.04 128.105 135.858C127.385 137.45 126.525 138.975 125.726 140.525C125.25 141.452 124.597 141.684 123.682 141.244C122.652 140.75 122.115 139.347 122.554 138.28C122.761 137.786 122.993 137.298 123.353 136.505C121.218 137.407 119.364 138.17 117.54 138.987C117.29 139.097 117.149 139.494 117.003 139.78C116.93 139.921 116.954 140.11 116.942 140.281C116.57 145.106 116.112 149.925 115.862 154.756C115.734 157.239 114.782 159.294 113.508 161.326C111.476 164.565 108.798 167.06 105.388 168.768C104.638 169.146 103.815 169.39 103.064 169.768C102.778 169.908 102.589 170.25 102.338 170.506C103.754 171.025 105.041 171.372 106.206 171.958C107.603 172.659 107.542 172.903 107.151 174.38C106.499 176.856 104.687 178.253 102.625 179.351C98.5016 181.535 94.1096 182.218 89.5102 181.267C88.583 181.071 87.6009 180.833 86.8201 180.333C86.1674 179.918 85.6306 179.156 85.3134 178.424C85.0389 177.79 85.0267 176.978 85.0999 176.265C85.2646 174.624 84.5936 173.123 84.5143 171.305C82.6721 171.208 80.897 171.116 79.0609 171.019C79.7807 171.824 80.4212 172.562 81.08 173.269C81.9889 174.245 82.0316 174.862 80.9885 175.648C78.2679 177.704 75.2789 178.955 71.7652 178.784C68.9714 178.65 66.5375 177.692 64.1646 176.271C61.2183 174.508 60.4436 170.836 62.7982 167.816C63.1703 167.334 63.5424 166.852 63.9511 166.328C63.6949 166.029 63.5363 165.797 63.335 165.626C59.1443 162.009 57.5095 157.214 56.9178 151.901C56.4298 147.515 56.5579 143.123 56.5701 138.725C56.5823 134.723 56.5091 130.722 56.4847 126.714C56.4725 124.915 56.503 123.121 56.5091 121.322C56.5091 119.297 56.6006 117.29 56.2468 115.258C55.2525 109.543 56.0699 103.986 58.4062 98.7212C62.4444 89.6566 68.9531 83.2394 78.7193 80.5188C79.8539 80.2016 81.0251 80.0247 82.4281 79.7319C80.9824 78.7742 79.5855 78.2679 78.0483 78.0422C76.8283 77.8653 75.5595 77.7555 74.4249 77.3285C73.5221 76.9869 72.5826 76.3647 72.0092 75.6022C70.0511 72.9792 71.0577 69.9536 74.1565 68.8312C76.261 68.0687 78.4082 68.1114 80.4456 68.941C84.5936 70.6246 87.1739 73.7173 88.2109 78.0849C88.3268 78.5668 88.4427 79.0487 88.5586 79.5367C89.9189 79.7136 91.2121 79.8234 92.4748 80.0613C100.228 81.5009 106.334 85.5879 110.964 91.8648C115.832 98.4712 118.113 105.864 117.259 114.112C117.216 114.502 117.235 114.923 117.094 115.283C116.143 117.656 116.558 120.035 117.04 122.554C117.442 122.462 117.857 122.401 118.26 122.279C121.932 121.169 125.531 119.876 128.971 118.15C129.813 117.729 130.447 117.277 130.704 116.265C130.874 115.588 131.271 114.837 131.802 114.41C133.272 113.233 133.601 111.489 134.473 109.835C133.839 109.494 133.259 109.189 132.686 108.866C124.347 104.205 119.955 97.1047 120.364 87.546C120.809 77.1089 128.459 68.2578 138.676 65.7934C146.588 63.8841 153.945 65.2139 160.398 70.2586C171.567 78.9877 171.915 95.5736 161.149 105.053C155 110.47 147.814 112.611 139.658 111.245C139.274 111.177 138.878 111.171 138.329 111.116C137.444 112.617 136.547 114.142 135.626 115.71C136.236 116.07 136.743 116.308 137.176 116.643C139.591 118.552 140.189 121.297 138.896 124.396C138.475 125.409 137.871 126.275 136.913 126.854C136.334 127.202 135.779 127.629 135.15 127.843C133.674 128.355 133.076 129.288 133.564 130.948L133.583 130.96ZM116.137 117.814C115.679 117.973 115.301 118.046 114.984 118.229C112.208 119.791 109.189 120.706 106.145 121.517C101.552 122.743 96.8363 123.121 92.1149 123.445C90.9986 123.524 89.8762 123.414 88.7538 123.42C86.4602 123.432 84.1666 123.536 81.8791 123.469C80.1406 123.42 78.4082 123.158 76.6697 123.024C70.1549 122.511 63.8657 121.151 58.0402 118.04C57.8572 117.942 57.6254 117.942 57.2838 117.863C57.2838 118.967 57.296 119.974 57.2838 120.974C57.235 124.463 57.1435 127.952 57.1252 131.448C57.1191 133.363 57.235 135.279 57.2838 137.194C57.4119 142.202 57.3875 147.222 57.7169 152.218C57.9548 155.842 58.9918 159.325 61.322 162.253C64.2927 165.992 68.1236 168.457 72.7474 169.652C73.7234 169.902 74.7299 170.085 75.7303 170.14C79.1036 170.323 82.483 170.433 85.8563 170.567C86.3626 170.585 86.8689 170.604 87.3691 170.579C90.4618 170.427 93.5606 170.146 96.6533 170.14C98.6175 170.14 100.49 169.86 102.259 169.14C109.707 166.114 115.008 161.399 115.234 152.615C115.313 149.528 115.777 146.454 116.033 143.367C116.137 142.147 116.155 140.927 116.216 139.5C115.508 139.719 114.99 139.902 114.453 140.037C111.617 140.75 108.798 141.549 105.931 142.117C103.827 142.538 101.667 142.519 99.5508 141.995C96.4581 141.226 95.1283 139.164 95.8725 136.072C96.1592 134.882 96.7936 133.778 97.2023 132.613C97.5805 131.545 98.2515 130.722 99.2214 130.209C101.009 129.264 102.802 128.312 104.663 127.525C108.341 125.964 112.068 124.518 115.771 123.018C115.868 122.981 115.935 122.871 116.125 122.688V117.79L116.137 117.814ZM59.37 98.9408C59.2297 99.1421 59.1016 99.2642 59.0406 99.4106C56.9483 104.779 56.4664 110.354 57.0642 116.039C57.1069 116.442 57.5217 116.948 57.8999 117.155C59.1321 117.833 60.407 118.449 61.7063 118.979C67.8857 121.523 74.4432 122.31 81.0129 122.841C86.753 123.304 92.4992 123.042 98.2149 122.231C101.222 121.804 104.229 121.395 107.103 120.443C107.786 116.192 107.048 103.217 105.913 100.631C90.3398 104.266 74.8702 102.894 59.3639 98.9469L59.37 98.9408ZM103.15 85.0938C102.009 87.5826 101.308 88.2658 98.7334 89.3638C97.9587 89.6932 97.1535 89.9738 96.3483 90.2117C91.1267 91.755 85.7953 91.8709 80.4273 91.3707C78.0056 91.145 75.6327 90.657 73.3757 89.7298C71.5823 88.9917 69.9109 88.0706 69.2155 85.8624C64.7441 89.0222 61.749 93.1092 59.5713 98.0136C60.0898 98.221 60.4314 98.4223 60.7974 98.4894C66.1045 99.5142 71.3932 100.631 76.7246 101.515C81.629 102.326 86.5944 102.595 91.5476 102.04C95.8542 101.558 100.142 100.899 104.419 100.191C107.109 99.7461 109.671 98.9103 111.617 96.7875C112.562 95.7566 112.66 95.5858 111.989 94.4634C110.293 91.6391 108.219 89.1259 105.706 86.9848C104.913 86.3077 104.028 85.7343 103.162 85.0938H103.15ZM130.148 118.272C126.787 120.236 123.414 121.615 119.9 122.664C113.843 124.475 108.085 127.05 102.259 129.465C101.692 129.703 101.161 130.033 100.606 130.313C97.855 131.71 96.8241 134.205 96.3971 137.029C96.1409 138.756 96.9278 140.189 98.5443 140.854C99.5142 141.251 100.594 141.482 101.643 141.562C104.138 141.763 106.59 141.336 109.006 140.738C117.058 138.749 124.482 135.26 131.68 131.228C132.662 130.679 132.814 129.929 132.717 128.983C132.351 125.378 131.478 121.901 130.142 118.272H130.148ZM143.837 110.842C146.74 110.909 149.296 110.519 151.791 109.732C160.899 106.859 167.523 98.6724 168.261 89.6017C169.262 77.2553 159.727 66.0862 146.002 65.5189C141.019 65.3115 136.407 66.7084 132.174 69.2826C123.902 74.309 119.62 83.3675 121.31 92.6273C122.835 100.954 128.074 106.352 135.675 109.482C138.298 110.561 141.208 111.007 143.837 110.836V110.842ZM87.3996 84.7461C86.3992 84.6058 85.8929 84.1239 85.5818 83.2943C85.3134 82.5623 84.8925 81.8852 84.5021 81.202C84.1239 80.5493 83.5383 80.3358 82.7819 80.4273C78.5241 80.9336 74.5408 82.2329 70.9357 84.5814C70.0451 85.1609 69.8499 86.0332 70.5209 86.8628C71.0028 87.4606 71.625 88.0279 72.3082 88.3634C73.4611 88.9368 74.675 89.4248 75.9194 89.7603C80.5493 91.0169 85.2707 91.1023 90.0104 90.6753C92.9262 90.413 95.842 89.9555 98.5321 88.7111C99.685 88.1804 100.801 87.4423 101.71 86.5639C102.741 85.5635 102.485 84.5875 101.216 83.8555C98.4101 82.2329 95.4028 81.1593 92.1881 80.6835C91.0413 80.5127 89.8884 80.1772 88.6745 80.5493C88.3817 81.9828 88.6379 83.5505 87.4118 84.74L87.3996 84.7461ZM107.926 120.205C110.488 119.473 112.77 118.601 114.966 117.454C115.868 116.985 116.308 116.423 116.387 115.411C116.863 109.421 116.131 103.62 113.807 98.0564C113.624 97.6171 113.343 97.2146 113.197 96.9523C111.098 98.2821 109.122 99.5386 107.048 100.856C108.335 107.164 107.865 113.624 107.926 120.193V120.205ZM86.9177 83.9775C87.912 81.8913 88.0096 78.6034 87.2532 76.8283C86.3565 74.7177 85.0572 72.8816 83.3431 71.3871C80.8055 69.185 77.8836 68.1846 74.5834 69.3192C71.6188 70.344 70.1731 73.1561 73.2537 75.9499C73.8332 76.4745 74.7238 76.7246 75.5168 76.9259C76.383 77.1455 77.3102 77.1028 78.1947 77.2614C81.7815 77.9202 84.8742 79.2744 86.2223 83.0564C86.3382 83.3797 86.6493 83.6298 86.9116 83.9775H86.9177ZM86.0576 174.392C86.0576 175.197 86.1064 176.088 86.0454 176.966C85.9478 178.393 86.5761 179.345 87.8449 179.894C88.5647 180.199 89.2906 180.528 90.047 180.681C94.4329 181.553 98.5565 180.711 102.406 178.54C104.504 177.356 106.218 175.783 106.651 173.129C105.663 172.354 104.583 171.824 103.308 171.72C102.473 171.653 101.716 171.708 101.442 172.696C101.283 173.251 100.899 173.489 100.338 173.574C98.1722 173.904 96.025 174.435 93.8473 174.55C91.3402 174.685 88.8209 174.465 86.0515 174.398L86.0576 174.392ZM81.3423 174.807C80.2687 173.245 79.2439 172.306 77.9751 171.683C77.5969 171.5 76.8222 171.47 76.6209 171.702C75.8523 172.611 74.8519 172.562 73.8515 172.653C71.9422 172.824 70.1548 172.391 68.4468 171.586C66.6229 170.726 64.8173 169.841 63.0056 168.969C61.6575 170.994 61.7063 172.989 63.2252 174.672C63.7071 175.203 64.3598 175.581 64.9759 175.978C70.1426 179.29 75.9682 178.625 80.2931 175.368C80.6042 175.136 80.9885 174.996 81.3362 174.813L81.3423 174.807ZM133.522 127.684C138.676 126.574 139.103 122.554 138.371 119.101C138.286 118.711 137.944 118.15 137.621 118.07C136.596 117.826 135.523 117.747 134.467 117.65C133.546 117.564 133.4 118.54 132.826 118.931C132.771 118.967 132.637 118.906 132.509 118.888C132.656 117.442 134.083 116.1 132.985 114.502C131.527 115.313 130.874 116.503 131.198 117.948C131.637 119.913 132.186 121.852 132.662 123.811C132.967 125.061 133.223 126.318 133.522 127.684V127.684ZM85.6611 171.439C85.533 172.983 85.5208 173.013 86.6737 173.41C87.2471 173.611 87.8571 173.745 88.461 173.812C91.9502 174.178 95.4089 173.91 98.8371 173.215C100.704 172.836 100.881 172.617 101.143 170.598C95.9762 170.878 90.8095 171.159 85.6611 171.439ZM76.2427 170.854C71.7164 170.604 67.9772 169.262 64.5734 166.694C64.183 167.243 63.8474 167.706 63.4631 168.243C65.8849 169.817 68.3248 170.909 70.8868 171.738C72.4362 172.239 73.9857 171.995 75.5229 171.647C75.7181 171.604 75.8523 171.299 76.2427 170.848V170.854ZM128.355 134.437C126 134.87 124.262 135.87 123.377 138.06C122.786 139.53 123.011 140.177 124.872 140.775C126.019 138.682 127.19 136.56 128.355 134.437V134.437ZM134.199 116.997C135.382 114.917 136.474 112.989 137.658 110.903C136.743 110.604 135.98 110.348 135.144 110.073C134.632 111.123 134.15 112.007 133.772 112.934C133.619 113.306 133.613 113.782 133.668 114.197C133.784 115.057 133.985 115.905 134.199 117.003V116.997ZM135.23 116.985C135.669 117.064 136.108 117.143 136.736 117.253C136.242 116.472 135.785 116.491 135.23 116.985Z" fill="black"/> +<path d="M116.137 117.814V122.713C115.948 122.889 115.875 122.999 115.783 123.042C112.08 124.543 108.353 125.994 104.675 127.55C102.814 128.337 101.021 129.288 99.2337 130.234C98.2638 130.746 97.5928 131.564 97.2146 132.637C96.8059 133.802 96.1715 134.906 95.8848 136.096C95.1406 139.195 96.4704 141.257 99.5631 142.019C101.68 142.544 103.839 142.562 105.944 142.141C108.811 141.574 111.629 140.769 114.465 140.061C114.996 139.927 115.521 139.744 116.228 139.524C116.161 140.952 116.143 142.178 116.045 143.392C115.789 146.472 115.319 149.553 115.246 152.639C115.027 161.417 109.726 166.138 102.272 169.164C100.503 169.884 98.6298 170.164 96.6656 170.164C93.5729 170.164 90.4741 170.451 87.3814 170.604C86.8812 170.628 86.3749 170.61 85.8686 170.591C82.4953 170.457 79.1159 170.347 75.7426 170.164C74.7422 170.11 73.7296 169.927 72.7597 169.676C68.1359 168.481 64.3051 166.016 61.3344 162.277C59.0042 159.349 57.9672 155.866 57.7293 152.243C57.3999 147.247 57.4243 142.226 57.2962 137.218C57.2474 135.303 57.1254 133.388 57.1376 131.472C57.1559 127.983 57.2413 124.494 57.2962 120.998C57.3084 119.992 57.2962 118.992 57.2962 117.887C57.6378 117.961 57.8696 117.967 58.0526 118.064C63.872 121.181 70.1611 122.536 76.682 123.048C78.4205 123.188 80.1529 123.445 81.8914 123.493C84.1789 123.554 86.4786 123.457 88.7661 123.445C89.8885 123.438 91.0109 123.542 92.1272 123.469C96.8486 123.146 101.564 122.761 106.157 121.541C109.207 120.73 112.227 119.815 114.996 118.253C115.313 118.077 115.692 118.003 116.149 117.839L116.137 117.814Z" fill="white"/> +<path d="M59.3763 98.9408C74.8825 102.888 90.3521 104.26 105.925 100.624C107.06 103.211 107.798 116.192 107.115 120.437C104.242 121.389 101.228 121.798 98.2273 122.225C92.5116 123.042 86.7653 123.298 81.0252 122.835C74.4555 122.304 67.898 121.517 61.7187 118.973C60.4194 118.436 59.1445 117.82 57.9123 117.149C57.5341 116.942 57.1193 116.442 57.0766 116.033C56.4788 110.348 56.9607 104.779 59.053 99.4044C59.1079 99.258 59.236 99.136 59.3824 98.9347L59.3763 98.9408ZM93.9268 110.708C95.647 110.872 96.135 111.842 96.2631 113.142C96.2875 113.386 96.4583 113.611 96.6535 114.044C97.0805 112.446 96.9158 111.238 96.1899 110.683C95.6043 110.232 94.9333 109.939 93.9207 110.708H93.9268ZM93.8719 108.664C96.1167 109.116 97.3977 107.7 96.867 105.352C96.2204 106.739 95.222 107.843 93.8719 108.664ZM100.692 108.487C98.3676 109.006 98.331 107.231 97.965 105.608C97.4282 107.176 97.6051 108.262 98.3859 108.805C99.1728 109.347 99.9109 109.177 100.686 108.487H100.692ZM99.5266 109.823C97.8491 110.214 97.3306 111.129 97.8186 112.91C98.3859 111.885 98.9532 110.854 99.5266 109.823Z" fill="white"/> +<path d="M103.156 85.0938C104.022 85.7343 104.907 86.3077 105.7 86.9848C108.213 89.1198 110.287 91.633 111.983 94.4634C112.66 95.5919 112.556 95.7566 111.61 96.7875C109.665 98.9103 107.103 99.746 104.412 100.191C100.136 100.899 95.848 101.552 91.5414 102.04C86.5882 102.595 81.6228 102.326 76.7184 101.515C71.387 100.631 66.0983 99.5203 60.7913 98.4894C60.4253 98.4162 60.0837 98.221 59.5652 98.0136C61.7429 93.1092 64.738 89.0283 69.2093 85.8624C69.9108 88.0706 71.5761 88.9856 73.3695 89.7298C75.6265 90.657 77.9994 91.1511 80.4211 91.3707C85.7891 91.8709 91.1205 91.7489 96.3421 90.2117C97.1534 89.9738 97.9525 89.6932 98.7272 89.3638C101.308 88.2719 102.003 87.5887 103.144 85.0938H103.156Z" fill="white"/> +<path d="M130.155 118.272C131.484 121.901 132.363 125.384 132.729 128.983C132.826 129.929 132.674 130.673 131.692 131.228C124.494 135.266 117.07 138.756 109.018 140.738C106.603 141.336 104.15 141.757 101.655 141.562C100.606 141.476 99.5265 141.25 98.5566 140.854C96.9401 140.189 96.1532 138.756 96.4094 137.029C96.8303 134.199 97.8673 131.71 100.618 130.313C101.167 130.033 101.698 129.703 102.272 129.465C108.091 127.05 113.855 124.475 119.913 122.664C123.426 121.615 126.8 120.236 130.161 118.272H130.155Z" fill="white"/> +<path d="M143.849 110.842C141.22 111.007 138.304 110.561 135.687 109.488C128.087 106.358 122.841 100.96 121.322 92.6334C119.632 83.3736 123.914 74.3151 132.186 69.2887C136.425 66.7145 141.031 65.3176 146.015 65.525C159.74 66.0923 169.274 77.2614 168.273 89.6078C167.535 98.6786 160.911 106.865 151.803 109.738C149.309 110.525 146.759 110.915 143.849 110.848V110.842ZM128.745 75.2484C127.495 76.1512 126.677 77.3346 126.348 78.8474C126.128 79.1707 125.909 79.494 125.689 79.8173C125.457 80.4517 125.226 81.0922 124.988 81.7266L125.012 81.6717C124.817 82.0621 124.555 82.4342 124.433 82.849C123.139 87.3447 123.359 91.7611 125.189 96.0739C129.496 106.236 141.342 111.037 151.962 106.932C161.893 103.089 167.456 92.6944 164.998 82.6721C163.204 75.3643 156.415 68.1175 146.344 67.5868C144.49 67.4892 142.653 67.5441 140.878 68.1724L140.933 68.1541C139.329 68.2334 137.792 68.5201 136.48 69.5327L136.529 69.5022C136.212 69.5693 135.864 69.5815 135.577 69.7157C133.125 70.8625 130.984 72.4424 129.16 74.4432C128.953 74.675 128.855 75.0044 128.703 75.2911L128.739 75.2484H128.745Z" fill="white"/> +<path d="M87.4056 84.7461C88.6317 83.5566 88.3755 81.9828 88.6683 80.5554C89.8883 80.1833 91.0351 80.5188 92.1819 80.6896C95.3905 81.1654 98.404 82.239 101.21 83.8616C102.479 84.5936 102.735 85.5696 101.704 86.57C100.795 87.4484 99.6788 88.1865 98.5259 88.7172C95.8419 89.9616 92.9261 90.413 90.0042 90.6814C85.2706 91.1084 80.5431 91.023 75.9132 89.7664C74.6749 89.4309 73.4549 88.9429 72.302 88.3695C71.6249 88.0279 71.0027 87.4667 70.5147 86.8689C69.8437 86.0393 70.0328 85.167 70.9295 84.5875C74.5346 82.239 78.5179 80.9397 82.7757 80.4334C83.5321 80.3419 84.1177 80.5554 84.4959 81.2081C84.8924 81.8852 85.3072 82.5684 85.5756 83.3004C85.8806 84.1361 86.393 84.6119 87.3934 84.7522L87.4056 84.7461Z" fill="white"/> +<path d="M107.932 120.199C107.871 113.624 108.335 107.164 107.054 100.862C109.134 99.5447 111.11 98.2881 113.203 96.9583C113.349 97.2206 113.623 97.6171 113.813 98.0624C116.137 103.626 116.875 109.427 116.393 115.417C116.314 116.43 115.874 116.985 114.972 117.46C112.776 118.607 110.494 119.48 107.932 120.212V120.199Z" fill="#C8E2E2"/> +<path d="M86.9299 83.9714C86.6615 83.6298 86.3504 83.3736 86.2406 83.0503C84.8925 79.2622 81.7998 77.9141 78.213 77.2553C77.3285 77.0906 76.4013 77.1333 75.5351 76.9198C74.7421 76.7185 73.8515 76.4684 73.272 75.9438C70.1915 73.1561 71.6311 70.3379 74.6018 69.3131C77.8958 68.1785 80.8238 69.1789 83.3614 71.381C85.0816 72.8755 86.3748 74.7116 87.2715 76.8222C88.0279 78.5973 87.9303 81.8852 86.936 83.9714H86.9299Z" fill="white"/> +<path d="M86.0696 174.386C88.839 174.459 91.3645 174.679 93.8655 174.538C96.0371 174.422 98.1903 173.886 100.356 173.562C100.917 173.477 101.301 173.239 101.46 172.684C101.734 171.696 102.491 171.635 103.327 171.708C104.601 171.812 105.675 172.342 106.669 173.117C106.242 175.77 104.528 177.344 102.424 178.528C98.5747 180.699 94.451 181.541 90.0651 180.669C89.3087 180.516 88.5767 180.193 87.863 179.882C86.5881 179.339 85.9598 178.387 86.0635 176.954C86.1245 176.069 86.0758 175.185 86.0758 174.38L86.0696 174.386Z" fill="white"/> +<path d="M81.3546 174.807C81.0008 174.99 80.6226 175.13 80.3115 175.362C75.9927 178.619 70.1672 179.284 64.9944 175.972C64.3844 175.581 63.7256 175.197 63.2437 174.666C61.7187 172.983 61.676 170.994 63.0241 168.963C64.8358 169.835 66.6414 170.726 68.4653 171.58C70.1733 172.385 71.9606 172.818 73.8699 172.647C74.8703 172.556 75.8707 172.605 76.6393 171.696C76.8406 171.464 77.6153 171.488 77.9935 171.677C79.2623 172.3 80.2871 173.239 81.3607 174.801L81.3546 174.807Z" fill="white"/> +<path d="M133.534 127.684C133.235 126.318 132.973 125.061 132.674 123.811C132.198 121.852 131.649 119.913 131.21 117.948C130.886 116.497 131.539 115.307 132.997 114.502C134.101 116.1 132.674 117.442 132.521 118.888C132.649 118.906 132.777 118.973 132.838 118.931C133.412 118.54 133.552 117.564 134.479 117.65C135.535 117.747 136.608 117.826 137.633 118.07C137.956 118.15 138.304 118.711 138.383 119.101C139.115 122.56 138.688 126.58 133.534 127.684V127.684Z" fill="white"/> +<path d="M85.6732 171.439C90.8216 171.159 95.9883 170.878 101.155 170.598C100.893 172.617 100.716 172.836 98.8492 173.215C95.421 173.91 91.9623 174.178 88.4731 173.812C87.8692 173.751 87.2592 173.611 86.6858 173.41C85.5329 173.007 85.5451 172.977 85.6732 171.439Z" fill="white"/> +<path d="M76.2549 170.854C75.8584 171.305 75.7242 171.61 75.5351 171.653C73.9978 172.001 72.4485 172.245 70.8991 171.744C68.3371 170.915 65.8909 169.823 63.4753 168.249C63.8596 167.712 64.1951 167.249 64.5855 166.7C67.9894 169.268 71.7348 170.61 76.2549 170.86V170.854Z" fill="white"/> +<path d="M128.367 134.437C127.202 136.56 126.031 138.682 124.884 140.775C123.024 140.177 122.804 139.524 123.39 138.06C124.268 135.87 126.013 134.87 128.367 134.437V134.437Z" fill="white"/> +<path d="M134.211 116.997C133.997 115.899 133.796 115.051 133.68 114.191C133.625 113.782 133.631 113.306 133.784 112.928C134.162 112.001 134.644 111.116 135.156 110.067C135.992 110.342 136.755 110.592 137.67 110.897C136.486 112.983 135.394 114.911 134.211 116.991V116.997Z" fill="white"/> +<path d="M135.242 116.985C135.797 116.491 136.255 116.472 136.749 117.253C136.12 117.137 135.681 117.064 135.242 116.985Z" fill="white"/> +<path d="M93.9268 110.708C94.9394 109.933 95.6104 110.232 96.196 110.683C96.9219 111.245 97.0865 112.446 96.6595 114.044C96.4704 113.617 96.2936 113.386 96.2692 113.142C96.1411 111.842 95.653 110.866 93.9328 110.708H93.9268Z" fill="black"/> +<path d="M93.8657 108.658C95.2199 107.837 96.2183 106.733 96.8608 105.346C97.3915 107.694 96.1105 109.11 93.8657 108.658Z" fill="black"/> +<path d="M100.685 108.481C99.9105 109.177 99.1724 109.341 98.3855 108.798C97.5986 108.256 97.4217 107.17 97.9646 105.602C98.3306 107.225 98.3672 108.994 100.691 108.481H100.685Z" fill="black"/> +<path d="M99.5203 109.817C98.9469 110.848 98.3796 111.879 97.8123 112.904C97.3243 111.123 97.8428 110.201 99.5203 109.817Z" fill="black"/> +<path d="M125.683 79.8112C125.903 79.4879 126.122 79.1646 126.342 78.8413C127.141 77.6396 127.94 76.444 128.739 75.2423L128.703 75.285C128.928 75.1386 129.191 75.0288 129.374 74.8397C131.259 72.8267 133.448 71.2224 135.919 70.0085C136.151 69.8926 136.328 69.673 136.529 69.5022L136.48 69.5327C137.963 69.0752 139.451 68.6116 140.933 68.1541L140.878 68.1724C141.214 68.1724 141.555 68.1785 141.891 68.1602C143.349 68.0809 144.819 67.8735 146.271 67.9467C148.534 68.0687 150.693 68.7641 152.798 69.5998C161.246 72.9426 166.315 82.0133 164.705 90.901C162.119 105.218 145.563 112.556 133.302 104.699C126.47 100.326 123.451 93.9327 124.286 85.8197C124.433 84.4289 124.768 83.0564 125.012 81.6778L124.988 81.7327C125.22 81.0983 125.451 80.4578 125.689 79.8234L125.683 79.8112ZM152.779 99.6423C152.993 99.6606 153.2 99.6789 153.414 99.6972C159.892 97.2206 161.868 89.4126 160.374 84.7278C159.916 83.2882 159.215 81.8425 157.501 81.0129C157.598 81.4582 157.647 81.7205 157.708 81.9828C158.269 84.4411 158.385 86.9177 157.476 89.2906C156.677 91.3829 155.707 93.4142 154.683 95.4089C153.847 97.0315 152.822 98.5504 151.883 100.112C151.925 100.149 151.968 100.185 152.011 100.222C152.267 100.027 152.517 99.8314 152.773 99.6362L152.779 99.6423Z" fill="white"/> +<path d="M125.012 81.6717C124.762 83.0503 124.427 84.4228 124.286 85.8136C123.451 93.9327 126.47 100.319 133.302 104.693C145.563 112.55 162.119 105.212 164.705 90.8949C166.309 82.0072 161.24 72.9365 152.798 69.5937C150.693 68.7641 148.54 68.0626 146.271 67.9406C144.819 67.8613 143.355 68.0748 141.891 68.1541C141.555 68.1724 141.214 68.1602 140.878 68.1663C142.653 67.538 144.49 67.4831 146.344 67.5807C156.415 68.1114 163.204 75.3582 164.998 82.666C167.456 92.6883 161.893 103.083 151.962 106.926C141.342 111.031 129.496 106.236 125.189 96.0677C123.359 91.755 123.14 87.3325 124.433 82.8429C124.549 82.4281 124.811 82.056 125.012 81.6656V81.6717Z" fill="black"/> +<path d="M136.535 69.5022C136.334 69.673 136.157 69.8987 135.925 70.0085C133.448 71.2224 131.265 72.8267 129.38 74.8397C129.203 75.0288 128.934 75.1386 128.709 75.285C128.861 74.9983 128.953 74.6689 129.166 74.4371C130.99 72.4363 133.131 70.8503 135.583 69.7096C135.87 69.5754 136.218 69.5632 136.535 69.4961V69.5022Z" fill="black"/> +<path d="M128.745 75.2484C127.946 76.4501 127.147 77.6457 126.348 78.8474C126.678 77.3346 127.501 76.1512 128.745 75.2484Z" fill="black"/> +<path d="M140.933 68.148C139.451 68.6055 137.963 69.0691 136.48 69.5266C137.792 68.514 139.329 68.2273 140.933 68.148Z" fill="black"/> +<path d="M125.683 79.8113C125.451 80.4457 125.219 81.0862 124.981 81.7206C125.213 81.0862 125.445 80.4457 125.683 79.8113Z" fill="black"/> +<path d="M153.414 99.6972C153.2 99.6789 152.993 99.6606 152.779 99.6423C153.164 99.0262 153.542 98.404 153.926 97.7879C155.957 94.5244 157.8 91.1755 158.312 87.2837C158.489 85.9295 158.513 84.5631 158.611 83.1967C159.209 83.7518 159.593 84.3618 159.77 85.0267C161.112 90.0104 159.343 95.0795 155.213 98.2271C154.597 98.6968 154.012 99.2092 153.408 99.6972H153.414Z" fill="#EEEFFC"/> +<path d="M153.414 99.6973C154.012 99.2032 154.603 98.6969 155.22 98.2272C159.349 95.0796 161.118 90.0105 159.776 85.0268C159.599 84.3619 159.215 83.7519 158.617 83.1968C158.526 84.5571 158.495 85.9296 158.318 87.2838C157.806 91.1756 155.964 94.5245 153.932 97.788C153.548 98.4041 153.164 99.0263 152.786 99.6424C152.529 99.8376 152.279 100.033 152.023 100.228C151.98 100.191 151.938 100.155 151.895 100.118C152.834 98.5505 153.865 97.0316 154.695 95.4151C155.72 93.4204 156.684 91.383 157.489 89.2968C158.398 86.9239 158.282 84.4473 157.721 81.989C157.66 81.7267 157.605 81.4644 157.513 81.0191C159.227 81.8487 159.923 83.2944 160.386 84.734C161.881 89.4127 159.904 97.2268 153.426 99.7034L153.414 99.6973Z" fill="black"/> +<g clip-path="url(#clip0_11_940)"> +<path d="M100.79 38.1C100.51 37.6 100.19 37.11 99.9801 36.58C99.6401 35.73 99.4201 34.83 99.0801 33.98C98.3801 32.22 97.3101 30.96 95.1601 31.2C94.8301 31.24 94.4801 31.04 93.8201 30.87C94.3001 30.34 94.5601 29.82 94.9601 29.67C97.8401 28.59 99.6901 26.84 99.3901 23.5C99.3801 23.34 99.5201 23.16 99.6001 23.01C99.6401 22.94 99.7301 22.89 100.09 22.56C100.45 23.13 100.98 23.63 101.1 24.21C101.68 26.89 103.44 28.24 105.96 28.9C106.81 29.12 107.61 29.51 108.45 30.36C107.55 30.6 106.66 30.86 105.76 31.09C102.6 31.91 101.8 32.85 101.58 36.02C101.53 36.69 101.4 37.35 101.3 38.02C101.13 38.05 100.97 38.07 100.8 38.1H100.79ZM100.33 33.13C101.86 31.93 103.28 30.8 104.51 29.84C103.14 28.8 101.72 27.73 100.35 26.68C99.4001 27.76 98.4001 28.87 97.5301 29.86C98.5601 31.05 99.4901 32.14 100.34 33.13H100.33Z" fill="black"/> +<path d="M117 53.22C114.553 55.1133 112.977 57.59 112.27 60.65C110.64 59.73 111.1 57.82 110.21 56.65C109.3 55.45 108.37 54.32 106.4 53.99C107.46 53.03 108.87 52.25 109.48 51.06C110.08 49.89 109.87 48.31 110.25 46.79C112 49.39 113.56 52.08 117 53.22V53.22ZM109.09 53.59C110.09 54.71 110.93 55.66 111.79 56.61C112.68 55.58 113.55 54.58 114.54 53.44C113.44 52.56 112.38 51.73 111.25 50.82C110.51 51.78 109.85 52.62 109.09 53.59V53.59Z" fill="black"/> +<path d="M89.4099 48.94C86.4299 46.4334 84.1699 45.03 82.6299 44.73C83.6399 43.7 84.7299 42.9 85.3999 41.83C86.0599 40.78 86.2899 39.46 86.8199 37.96C88.4899 40.28 89.8699 42.6 92.8699 43.31C90.2499 44.34 89.8499 46.62 89.4199 48.93L89.4099 48.94ZM90.0499 43.56C89.8399 43.77 89.6299 43.97 89.4199 44.18C88.6699 43.08 87.9099 41.99 87.1199 40.84C86.5399 41.91 86.0199 42.86 85.4799 43.85C86.5499 44.65 87.5499 45.39 88.5499 46.14C89.0499 45.28 89.5399 44.42 90.0399 43.56H90.0499Z" fill="black"/> +<path d="M120.3 12.02C121.41 11.97 122.67 13.17 122.64 14.25C122.62 15.09 121.32 16.19 120.3 16.24C119.22 16.29 118.25 15.3 118.25 14.14C118.25 12.96 119.1 12.08 120.3 12.03V12.02ZM120.08 15.59C120.65 15.09 121.29 14.79 121.35 14.41C121.41 14.03 120.87 13.57 120.6 13.15C120.21 13.45 119.59 13.69 119.49 14.07C119.39 14.43 119.81 14.95 120.09 15.6L120.08 15.59Z" fill="black"/> +<path d="M135.85 47.1C135.82 48.77 134.9 49.59 133.64 49.54C132.4 49.5 131.75 48.81 131.84 47.62C131.92 46.51 133.02 45.49 134.13 45.8C134.86 46 135.43 46.77 135.84 47.1H135.85ZM133.74 46.55C133.39 47.09 133 47.49 133.07 47.61C133.26 47.93 133.64 48.16 133.99 48.33C134.06 48.37 134.51 47.95 134.47 47.84C134.35 47.47 134.09 47.14 133.73 46.55H133.74Z" fill="black"/> +<path d="M76.62 11.37C76.56 12.63 75.5 13.65 74.3 13.59C73.11 13.53 72.35 12.74 72.46 11.67C72.57 10.52 73.58 9.53998 74.6 9.57998C75.8 9.62998 76.67 10.4 76.63 11.37H76.62ZM74.46 10.4C74.05 10.97 73.66 11.33 73.56 11.76C73.52 11.94 74.26 12.53 74.38 12.47C74.78 12.24 75.15 11.84 75.34 11.42C75.4 11.28 74.88 10.88 74.46 10.4Z" fill="black"/> +<path d="M100.33 33.13C99.48 32.14 98.54 31.05 97.52 29.86C98.4 28.87 99.39 27.76 100.34 26.68C101.72 27.72 103.13 28.8 104.5 29.84C103.28 30.8 101.85 31.93 100.32 33.13H100.33Z" fill="#F0C3BA"/> +<path d="M109.1 53.59C109.85 52.62 110.51 51.78 111.26 50.82C112.4 51.72 113.45 52.56 114.55 53.44C113.56 54.58 112.69 55.59 111.8 56.61C110.95 55.66 110.1 54.71 109.1 53.59V53.59Z" fill="#EEEFFC"/> +<path d="M90.05 43.56C89.55 44.42 89.05 45.28 88.56 46.14C87.55 45.39 86.55 44.64 85.49 43.85C86.03 42.86 86.55 41.91 87.13 40.84C87.92 41.99 88.67 43.08 89.43 44.18C89.64 43.97 89.85 43.77 90.06 43.56H90.05Z" fill="white"/> +<path d="M120.08 15.6C119.8 14.95 119.38 14.44 119.48 14.07C119.59 13.69 120.2 13.45 120.59 13.15C120.87 13.57 121.4 14.04 121.34 14.41C121.28 14.79 120.65 15.09 120.07 15.59L120.08 15.6Z" fill="white"/> +<path d="M133.74 46.55C134.09 47.14 134.35 47.46 134.48 47.84C134.52 47.95 134.07 48.37 134 48.33C133.65 48.16 133.28 47.92 133.08 47.61C133.01 47.49 133.4 47.09 133.75 46.55H133.74Z" fill="white"/> +<path d="M74.4599 10.39C74.8799 10.86 75.3999 11.26 75.3399 11.41C75.1599 11.83 74.7799 12.23 74.3799 12.46C74.2599 12.53 73.5199 11.93 73.5599 11.75C73.6599 11.33 74.0499 10.97 74.4599 10.39Z" fill="white"/> +</g> +<defs> +<clipPath id="clip0_11_940"> +<rect width="63.4006" height="51.0712" fill="white" transform="translate(72.4495 9.5788)"/> +</clipPath> +</defs> +</svg> diff --git a/frontend/app/svg/ca-no-sessions-in-vault.svg b/frontend/app/svg/ca-no-sessions-in-vault.svg index ca9bbc9c7..001c7759f 100644 --- a/frontend/app/svg/ca-no-sessions-in-vault.svg +++ b/frontend/app/svg/ca-no-sessions-in-vault.svg @@ -1,14 +1,42 @@ -<svg viewBox="0 0 250 100" fill="none" xmlns="http://www.w3.org/2000/svg"> -<rect width="250" height="100" rx="13.1579" fill="#3EAAAF" fill-opacity="0.08"/> -<rect opacity="0.6" x="86.8947" y="29.579" width="138.158" height="14.4737" rx="6.57895" fill="#3EAAAF" fill-opacity="0.5"/> -<rect opacity="0.3" x="86.8947" y="55.8948" width="46.0526" height="14.4737" rx="6.57895" fill="#3EAAAF" fill-opacity="0.5"/> -<g clip-path="url(#clip0_2_20)"> -<path d="M54 67C58.5087 67 62.8327 65.2089 66.0208 62.0208C69.2089 58.8327 71 54.5087 71 50C71 45.4913 69.2089 41.1673 66.0208 37.9792C62.8327 34.7911 58.5087 33 54 33C49.4913 33 45.1673 34.7911 41.9792 37.9792C38.7911 41.1673 37 45.4913 37 50C37 54.5087 38.7911 58.8327 41.9792 62.0208C45.1673 65.2089 49.4913 67 54 67V67ZM51.875 46.8125C51.875 48.572 50.923 50 49.75 50C48.577 50 47.625 48.572 47.625 46.8125C47.625 45.053 48.577 43.625 49.75 43.625C50.923 43.625 51.875 45.053 51.875 46.8125ZM46.1056 59.4201C45.8616 59.2792 45.6835 59.0472 45.6106 58.775C45.5377 58.5028 45.5759 58.2128 45.7168 57.9688C46.5559 56.5145 47.7632 55.3069 49.2174 54.4676C50.6715 53.6282 52.321 53.1867 54 53.1875C55.6789 53.1872 57.3283 53.6289 58.7823 54.4682C60.2364 55.3075 61.4438 56.5148 62.2832 57.9688C62.4219 58.2127 62.4585 58.5015 62.385 58.7723C62.3115 59.0431 62.1338 59.2738 61.8909 59.414C61.6479 59.5543 61.3593 59.5928 61.088 59.5211C60.8168 59.4494 60.5849 59.2733 60.443 59.0312C59.7904 57.9 58.8513 56.9607 57.7202 56.3079C56.5891 55.655 55.306 55.3117 54 55.3125C52.694 55.3117 51.4109 55.655 50.2798 56.3079C49.1487 56.9607 48.2096 57.9 47.557 59.0312C47.4161 59.2753 47.184 59.4533 46.9118 59.5263C46.6397 59.5992 46.3497 59.561 46.1056 59.4201ZM58.25 50C57.077 50 56.125 48.572 56.125 46.8125C56.125 45.053 57.077 43.625 58.25 43.625C59.423 43.625 60.375 45.053 60.375 46.8125C60.375 48.572 59.423 50 58.25 50Z" fill="#3EAAAF" fill-opacity="0.5"/> +<svg viewBox="0 0 210 198" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect width="40.2491" height="80.702" rx="4" transform="matrix(0.938191 0.346117 -0.00820296 0.999966 165.932 39.1896)" fill="#F4F5FF"/> +<rect x="13" y="8.86084" width="69.5967" height="80.702" rx="4" fill="#FDF9F3"/> +<g clip-path="url(#clip0_305_444)"> +<path d="M85.1782 121.48V4.86084" stroke="#231F20" stroke-width="0.199732" stroke-miterlimit="10"/> +<path d="M6.06396 180.785C8.25303 178.895 13.1704 175.041 21.6271 168.789C30.8268 162.094 38.7681 156.294 47.5483 149.151C60.507 139.333 72.6347 130.405 85.1739 121.48C107.332 129.066 128.656 136.209 151.229 143.795C156.666 145.58 162.099 146.471 167.116 149.151C169.205 150.042 171.714 149.599 173.807 150.49C183.422 154.508 193.037 156.737 202.521 160.129" stroke="#231F20" stroke-width="0.199732" stroke-miterlimit="10"/> +<path d="M100.277 50.7513L57.958 76.5807L59.2403 152.055L100.277 121.097V50.7513Z" fill="white" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> +<path d="M100.277 50.7513L67.7048 40.4931C67.3293 40.3732 66.9219 40.4172 66.5823 40.6129L22.4016 65.7033C21.9582 65.9549 21.6865 66.4223 21.6865 66.9336V136.8C21.6865 138.038 22.4695 139.141 23.6359 139.552L59.2402 152.055L57.9699 77.3876C57.9619 76.8843 58.2175 76.4169 58.649 76.1572L100.277 50.7473" fill="white"/> +<path d="M100.218 50.9431C93.6387 48.8658 73.8573 42.6582 67.6256 40.6928C67.1702 40.533 66.7109 40.7607 66.3314 41.0044L64.9812 41.7714C53.3168 48.4065 34.7817 58.9363 23.1213 65.5635C22.8057 65.7672 22.1746 66.0148 22.0388 66.4223C21.851 66.7578 21.9429 67.4689 21.9229 67.8524C21.9309 68.8031 21.9189 71.5155 21.9229 72.5101C21.9269 82.5647 21.9429 126.238 21.9469 136.189C21.7991 137.739 22.5621 139.061 24.1519 139.456L35.8682 143.591L59.3008 151.864C58.6097 153.254 58.1983 89.3036 57.9786 89.6072C57.9346 87.0227 57.8228 80.3956 57.7788 77.8989L57.7708 77.4116C57.7509 76.8563 58.0465 76.2971 58.5178 76.0015L60.1876 74.9828C68.7282 69.7698 91.3658 55.9523 100.17 50.5755L100.378 50.9151C91.4337 56.3758 69.0118 70.0614 60.3953 75.3224L58.7336 76.337C58.374 76.5607 58.1583 76.9761 58.1703 77.4036L58.1783 77.8909C58.2822 84.8376 59.3048 143.707 59.4366 152.047L59.4406 152.335C59.4366 152.335 59.169 152.239 59.165 152.239L35.7044 144.042L23.9762 139.944C22.622 139.604 21.4715 138.426 21.4236 136.98C21.4116 126.518 21.4436 83.1319 21.4436 72.5101C21.4555 71.5354 21.4356 68.8071 21.4436 67.8524C21.4755 67.397 21.3557 66.654 21.6073 66.2186C21.7831 65.6953 22.4542 65.4156 22.8816 65.156C34.534 58.5369 53.0971 48.019 64.7575 41.3959L66.1077 40.6289C66.575 40.3293 67.1742 40.0816 67.7375 40.2814C73.8892 42.2108 93.8824 48.5343 100.33 50.5596L100.21 50.9391L100.218 50.9431Z" fill="#231F20"/> +<path d="M68.8239 85.1292L102.635 97.1331C103.198 97.3328 103.577 97.8681 103.577 98.4673V136.664C103.577 137.85 102.431 138.701 101.292 138.354L67.9411 128.179C67.3459 128 66.9385 127.448 66.9385 126.825V86.4594C66.9385 85.4807 67.9052 84.8016 68.8239 85.1252V85.1292Z" fill="white"/> +<path d="M68.932 84.8256C77.88 88.0094 93.507 93.4141 102.295 96.5699L102.587 96.6737C103.482 96.9014 104.089 97.7843 103.989 98.703C103.993 99.0705 103.981 100.177 103.985 100.565C103.917 110.327 103.937 126.945 103.881 136.556C103.985 137.922 102.519 139.101 101.197 138.641L100.901 138.55L81.9226 132.705C80.1849 132.174 71.6124 129.53 70.0625 129.054C69.7149 128.93 68.6324 128.627 68.2849 128.507C67.6377 128.371 66.9387 127.956 66.7869 127.249C66.667 125.036 66.723 106.289 66.691 103.62C66.683 100.381 66.6431 91.8722 66.6311 88.7284C66.6471 88.0413 66.5871 86.9068 66.6431 86.1998C66.7749 85.1572 67.9493 84.4581 68.928 84.8296L68.932 84.8256ZM68.7163 85.4328C67.7735 85.1492 67.1224 85.9082 67.2463 86.8629C67.2542 87.2184 67.2343 88.3449 67.2383 88.7244C67.2263 91.8282 67.1903 100.393 67.1783 103.616C67.1744 105.734 67.1344 125.012 67.1344 126.578C67.0625 127.892 67.9773 127.98 68.9999 128.291C69.7349 128.507 71.8002 129.142 72.5631 129.37C78.603 131.195 94.8333 136.097 101.077 137.982C102.135 138.442 103.318 137.783 103.266 136.56C103.254 132.733 103.23 124.349 103.218 120.426L103.162 100.569V99.3262V98.707C103.162 98.6151 103.162 98.4793 103.162 98.4194C103.146 98.0559 102.91 97.7124 102.575 97.5646C93.5789 94.3569 77.852 88.7284 68.7243 85.4408L68.7163 85.4328Z" fill="#231F20"/> +<path d="M84.1569 119.881C87.3955 119.881 89.569 116.257 89.0113 111.787C88.4537 107.316 85.3763 103.692 82.1376 103.692C78.8989 103.692 76.7255 107.316 77.2831 111.787C77.8407 116.257 80.9182 119.881 84.1569 119.881Z" fill="#E4EDF6" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> +<path d="M79.7349 117.436C81.6564 117.436 83.076 115.35 82.9055 112.776C82.7351 110.203 81.0391 108.117 79.1175 108.117C77.1959 108.117 75.7764 110.203 75.9469 112.776C76.1173 115.35 77.8133 117.436 79.7349 117.436Z" fill="#FAE3E3" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> +<path d="M95.8797 106.213L94.3098 105.625C93.3871 105.278 92.4004 105.961 92.4004 106.948V124.117C92.4004 124.688 92.7439 125.203 93.2712 125.423L94.8411 126.078C95.7719 126.465 96.7985 125.782 96.7985 124.772V107.539C96.7985 106.948 96.431 106.42 95.8797 106.217V106.213Z" fill="#E5F1FD" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> +<path d="M66.9858 111.766H65.5996C64.8187 111.766 64.1855 112.399 64.1855 113.18V118.596C64.1855 119.377 64.8187 120.01 65.5996 120.01H66.9858C67.7668 120.01 68.3999 119.377 68.3999 118.596V113.18C68.3999 112.399 67.7668 111.766 66.9858 111.766Z" fill="white" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> +<path d="M66.9858 89.771H65.5996C64.8187 89.771 64.1855 90.4041 64.1855 91.1851V96.6018C64.1855 97.3828 64.8187 98.0159 65.5996 98.0159H66.9858C67.7668 98.0159 68.3999 97.3828 68.3999 96.6018V91.1851C68.3999 90.4041 67.7668 89.771 66.9858 89.771Z" fill="white" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> +<path d="M70.8008 85.8323L90.2267 73.5567C91.1695 72.9615 92.3958 73.6366 92.3958 74.7511V91.4967C92.3958 92.4754 91.4291 93.1544 90.5104 92.8309L70.8008 85.8363" fill="#E4EDF6"/> +<path d="M70.8008 85.8323L90.2267 73.5567C91.1695 72.9615 92.3958 73.6366 92.3958 74.7511V91.4967C92.3958 92.4754 91.4291 93.1544 90.5104 92.8309L70.8008 85.8363" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> +<path d="M57.9147 76.7285L21.6313 66.3025L21.7432 65.915L58.0026 76.4329L57.9147 76.7285Z" fill="#231F20"/> +<path d="M179.744 74.108L178.418 75.8497C178.09 76.2811 178.094 76.8803 178.429 77.3037C178.809 77.7871 179.492 77.9029 180.007 77.5673L182.125 76.2052C182.903 75.7019 182.836 74.5394 181.997 74.136L181.21 73.7565C180.702 73.5088 180.087 73.6566 179.748 74.108H179.744Z" fill="#FFF7E9" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> +<path d="M182.512 70.1972L198.087 53.6754C198.662 53.0642 199.677 53.228 200.032 53.991L203.248 60.9177C203.5 61.461 203.308 62.1121 202.797 62.4277L185.851 72.9775C185.496 73.1972 185.048 73.2172 184.677 73.0254L182.831 72.0707C182.128 71.7072 181.968 70.7725 182.512 70.1972Z" fill="#FFF7E9" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> +<path d="M149.112 154.121C149.112 154.121 149.18 158.455 147.754 159.19C146.328 159.925 139.753 159.885 140.037 159.006C140.32 158.127 140.676 155.012 140.676 155.012C140.676 155.012 140.52 157.876 140.037 159.006C139.553 160.137 134.033 159.909 131.991 159.478C129.95 159.046 129.579 154.468 129.579 154.468" fill="white"/> +<path d="M149.112 154.121C149.112 154.121 149.18 158.455 147.754 159.19C146.328 159.925 139.753 159.885 140.037 159.006M140.037 159.006C140.32 158.127 140.676 155.012 140.676 155.012C140.676 155.012 140.52 157.876 140.037 159.006ZM140.037 159.006C139.553 160.137 134.033 159.909 131.991 159.478C129.95 159.046 129.579 154.468 129.579 154.468" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> +<path d="M142.592 75.1146C142.592 75.1146 124.273 73.2132 117.494 91.7523C112.736 104.767 113.216 126.885 113.923 138.909C114.27 144.785 117.869 149.97 123.254 152.351C129.386 155.059 141.086 156.242 152.507 154.268C158.012 153.318 162.617 149.559 164.611 144.342C165.925 140.898 167.163 136.089 168.054 129.342C170.818 108.414 176.483 76.5807 142.588 75.1146H142.592Z" fill="white"/> +<path d="M142.566 75.4822C132.918 74.883 123.607 79.6885 119.249 88.4528C113.588 99.9174 113.56 117.845 113.732 130.596C113.788 132.981 113.884 135.366 114.004 137.751C114.06 138.941 114.124 140.123 114.387 141.286C115.618 147.258 120.411 151.764 126.247 153.246C134.528 155.599 146.165 155.643 154.514 153.557C159.027 152.147 162.778 148.604 164.416 144.17C166.062 139.704 166.957 134.994 167.612 130.293C170.92 106.588 175.11 76.8483 142.562 75.4822H142.566ZM142.617 74.7511C159.902 75.4023 169.398 84.4262 170.432 101.779C171.055 111.378 169.497 120.905 168.223 130.377C167.52 135.114 166.573 139.848 164.872 144.342C163.166 148.9 159.295 152.539 154.645 153.973C149.996 155.187 145.15 155.419 140.364 155.443C134.408 155.319 128.273 154.76 122.772 152.311C118.394 150.246 115.122 146.076 114.116 141.346C113.844 140.175 113.768 138.961 113.704 137.771C113.568 135.386 113.461 132.997 113.393 130.608C113.249 125.831 113.241 121.049 113.425 116.267C113.728 109.109 114.339 101.891 116.205 94.9401C117.435 90.2983 119.528 85.7764 122.88 82.273C127.881 76.9482 135.383 74.3517 142.617 74.7551V74.7511Z" fill="#231F20"/> +<path d="M133.5 97.3608C127.5 96.5608 120.333 93.3608 117.5 91.8608C115.1 99.4608 114.5 107.028 114.5 109.861C116.9 111.461 125.833 114.528 130 115.861L133.5 97.3608Z" fill="#C8E2E2" stroke="black" stroke-width="0.005"/> +<path d="M127.82 144.562C127.82 144.562 127.872 151.948 134.923 149.982C141.973 148.017 138.262 142.856 138.262 142.856C138.262 142.856 136.041 147.086 127.816 144.562" fill="white"/> +<path d="M127.82 144.562C127.82 144.562 127.872 151.948 134.923 149.982C141.973 148.017 138.262 142.856 138.262 142.856C138.262 142.856 136.041 147.086 127.816 144.562" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> +<path d="M169.897 113.799C139.198 125.711 114.063 109.528 114.063 109.528C122.488 114.442 130.114 116.044 130.146 115.576L133.721 97.1171C133.721 97.1171 121.893 94.2809 117.499 91.7523C117.499 91.7523 142.19 104.475 169.23 95.6591C169.23 95.6591 170.935 104.272 169.897 113.799Z" fill="white" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> +<path d="M135.642 121.345C133.441 118.361 128.871 118.369 126.922 121.516C126.542 122.128 126.291 122.791 126.291 123.478C126.291 123.478 124.277 138.318 126.291 142.896C127.501 145.644 131.476 145.556 134.464 145.045C137.779 144.482 140.308 141.741 140.548 138.39C140.548 138.338 140.556 138.286 140.56 138.238C140.58 137.906 140.564 137.571 140.532 137.239C140.292 134.986 139.142 126.082 135.646 121.345H135.642Z" fill="white" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> +<path d="M128.756 78.1545C128.756 78.1545 141.81 88.7963 160.154 80.3995Z" fill="white"/> +<path d="M128.756 78.1545C128.756 78.1545 141.81 88.7963 160.154 80.3995" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> +<path d="M143.328 78.1546C142.773 78.6619 141.882 78.2265 141.95 77.4795C142.286 73.6606 143.728 65.3238 149.923 60.0948C158.168 53.1321 167.144 53.8671 167.144 53.8671C167.144 53.8671 175.361 54.2346 169.968 61.9283C164.576 69.622 147.175 67.4249 146.807 70.9043L143.328 78.1586V78.1546Z" fill="white" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> +<path d="M132.167 159.522C132.055 159.494 131.931 159.51 131.831 159.569C130.729 160.221 124.189 164.171 124.317 166.348C124.457 168.725 141.063 167.171 142.109 166.408C142.868 165.853 142.177 162.29 140.292 159.386C140.164 159.186 139.896 159.126 139.693 159.246C138.898 159.713 136.657 160.604 132.167 159.522Z" fill="white" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> +<path d="M147.363 159.278C147.582 159.23 147.81 159.35 147.886 159.561C148.265 160.624 149.643 163.624 149.727 164.862C149.807 166.001 144.486 166.34 142.665 166.396C142.401 166.404 142.185 166.185 142.205 165.917C142.265 165.154 142.122 163.412 140.56 160.085C140.396 159.737 140.715 159.358 141.083 159.462C142.154 159.761 144.119 159.981 147.359 159.278H147.363Z" fill="white" stroke="#231F20" stroke-width="0.399464" stroke-miterlimit="10"/> </g> -<path d="M28.375 28.0625C28.375 26.7198 28.9084 25.4322 29.8578 24.4828C30.8072 23.5334 32.0948 23 33.4375 23H73.9375C75.2802 23 76.5678 23.5334 77.5172 24.4828C78.4666 25.4322 79 26.7198 79 28.0625V71.9375C79 73.2802 78.4666 74.5678 77.5172 75.5172C76.5678 76.4666 75.2802 77 73.9375 77H33.4375C32.0948 77 30.8072 76.4666 29.8578 75.5172C28.9084 74.5678 28.375 73.2802 28.375 71.9375V66.875H26.6875C26.2399 66.875 25.8107 66.6972 25.4943 66.3807C25.1778 66.0643 25 65.6351 25 65.1875C25 64.7399 25.1778 64.3107 25.4943 63.9943C25.8107 63.6778 26.2399 63.5 26.6875 63.5H28.375V51.6875H26.6875C26.2399 51.6875 25.8107 51.5097 25.4943 51.1932C25.1778 50.8768 25 50.4476 25 50C25 49.5524 25.1778 49.1232 25.4943 48.8068C25.8107 48.4903 26.2399 48.3125 26.6875 48.3125H28.375V36.5H26.6875C26.2399 36.5 25.8107 36.3222 25.4943 36.0057C25.1778 35.6893 25 35.2601 25 34.8125C25 34.3649 25.1778 33.9357 25.4943 33.6193C25.8107 33.3028 26.2399 33.125 26.6875 33.125H28.375V28.0625ZM33.4375 26.375C32.9899 26.375 32.5607 26.5528 32.2443 26.8693C31.9278 27.1857 31.75 27.6149 31.75 28.0625V71.9375C31.75 72.3851 31.9278 72.8143 32.2443 73.1307C32.5607 73.4472 32.9899 73.625 33.4375 73.625H73.9375C74.3851 73.625 74.8143 73.4472 75.1307 73.1307C75.4472 72.8143 75.625 72.3851 75.625 71.9375V28.0625C75.625 27.6149 75.4472 27.1857 75.1307 26.8693C74.8143 26.5528 74.3851 26.375 73.9375 26.375H33.4375Z" fill="#3EAAAF" fill-opacity="0.5"/> <defs> -<clipPath id="clip0_2_20"> -<rect width="34" height="34" fill="white" transform="translate(37 33)"/> +<clipPath id="clip0_305_444"> +<rect width="197.555" height="176" fill="white" transform="translate(6 4.86084)"/> </clipPath> </defs> </svg> diff --git a/frontend/app/svg/ca-no-sessions.svg b/frontend/app/svg/ca-no-sessions.svg index e38cd449a..6a0de60e1 100644 --- a/frontend/app/svg/ca-no-sessions.svg +++ b/frontend/app/svg/ca-no-sessions.svg @@ -1,13 +1,40 @@ -<svg viewBox="0 0 250 100" fill="none" xmlns="http://www.w3.org/2000/svg"> -<rect width="250" height="100" rx="13.1579" fill="#3EAAAF" fill-opacity="0.08"/> -<rect opacity="0.6" x="86.8421" y="29.579" width="138.158" height="14.4737" rx="6.57895" fill="#3EAAAF" fill-opacity="0.5"/> -<rect opacity="0.3" x="86.8421" y="55.8948" width="46.0526" height="14.4737" rx="6.57895" fill="#3EAAAF" fill-opacity="0.5"/> -<g clip-path="url(#clip0_1_2)"> -<path d="M51.9737 76.9474C59.1276 76.9474 65.9884 74.1055 71.047 69.047C76.1055 63.9884 78.9474 57.1276 78.9474 49.9737C78.9474 42.8198 76.1055 35.959 71.047 30.9004C65.9884 25.8419 59.1276 23 51.9737 23C44.8198 23 37.959 25.8419 32.9004 30.9004C27.8419 35.959 25 42.8198 25 49.9737C25 57.1276 27.8419 63.9884 32.9004 69.047C37.959 74.1055 44.8198 76.9474 51.9737 76.9474V76.9474ZM48.602 44.9161C48.602 47.7079 47.0914 49.9737 45.2303 49.9737C43.3691 49.9737 41.8586 47.7079 41.8586 44.9161C41.8586 42.1243 43.3691 39.8586 45.2303 39.8586C47.0914 39.8586 48.602 42.1243 48.602 44.9161ZM39.4478 64.9205C39.0606 64.6969 38.7781 64.3287 38.6623 63.8968C38.5466 63.465 38.6072 63.0048 38.8308 62.6176C40.1622 60.3102 42.0779 58.3941 44.3851 57.0624C46.6923 55.7306 49.3097 55.03 51.9737 55.0313C54.6376 55.0307 57.2546 55.7316 59.5617 57.0633C61.8688 58.395 63.7847 60.3106 65.1166 62.6176C65.3366 63.0046 65.3947 63.4629 65.278 63.8926C65.1614 64.3222 64.8796 64.6882 64.494 64.9108C64.1085 65.1334 63.6506 65.1945 63.2202 65.0807C62.7898 64.9669 62.4219 64.6875 62.1967 64.3035C61.1612 62.5086 59.6711 61.0182 57.8764 59.9823C56.0818 58.9465 54.0458 58.4017 51.9737 58.403C49.9015 58.4017 47.8656 58.9465 46.0709 59.9823C44.2762 61.0182 42.7861 62.5086 41.7507 64.3035C41.5271 64.6906 41.1589 64.9732 40.727 65.0889C40.2951 65.2046 39.835 65.144 39.4478 64.9205ZM58.7171 49.9737C56.8559 49.9737 55.3454 47.7079 55.3454 44.9161C55.3454 42.1243 56.8559 39.8586 58.7171 39.8586C60.5783 39.8586 62.0888 42.1243 62.0888 44.9161C62.0888 47.7079 60.5783 49.9737 58.7171 49.9737Z" fill="#3EAAAF" fill-opacity="0.5"/> -</g> -<defs> -<clipPath id="clip0_1_2"> -<rect width="53.9474" height="53.9474" fill="white" transform="translate(25 23)"/> -</clipPath> -</defs> +<svg width="210" height="200" viewBox="0 0 210 200" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect width="210" height="200" fill="white"/> +<circle cx="93" cy="92.0155" r="91.7371" fill="#FDF9F3"/> +<path d="M181.796 189.581C171.142 189.581 160.542 188.84 149.911 188.182C122.108 186.459 94.3121 186.082 66.4659 186.082C57.1337 186.082 47.8015 186.082 38.4693 186.082C35.326 186.082 33.7208 186.082 28.515 186.782" stroke="black" stroke-linecap="round"/> +<path d="M199.968 75.089C201.928 76.759 203.098 78.959 203.968 81.309C206.858 89.109 206.728 96.869 203.448 104.529C202.308 107.189 200.748 109.579 198.568 111.519C196.128 113.689 193.338 114.949 190.008 114.869C187.428 114.809 184.848 114.749 182.278 114.769C180.008 114.789 177.868 114.469 175.988 113.069C175.628 112.799 175.178 112.549 174.748 112.499C171.078 112.049 167.408 111.629 163.738 111.269C163.258 111.219 162.618 111.469 162.248 111.799C158.908 114.799 155.058 116.189 150.468 115.529C150.278 116.179 150.058 116.689 149.988 117.219C149.388 121.319 146.818 124.239 144.048 126.989C143.178 127.859 141.898 128.449 140.698 128.829C138.258 129.609 137.438 131.049 137.258 133.659C136.858 139.449 136.008 145.219 135.248 150.979C134.708 155.079 132.798 158.479 129.658 161.199C128.408 162.289 127.258 163.499 126.108 164.699C123.998 166.909 121.208 167.949 118.518 169.149C116.838 169.899 115.088 170.469 113.208 171.189C113.148 172.169 113.078 173.149 113.028 174.039C115.838 174.719 118.538 175.209 121.138 176.039C125.248 177.359 127.888 180.069 128.398 184.899C127.228 185.239 126.048 185.699 124.818 185.919C121.088 186.579 117.358 187.179 113.608 187.729C109.328 188.359 105.138 187.559 100.968 186.729C99.8776 186.509 98.7275 186.519 97.6975 186.169C96.6175 185.799 95.6475 185.119 94.6375 184.579C94.9975 184.599 95.3676 184.579 95.7176 184.659C101.298 185.899 106.878 187.119 112.658 186.719C114.958 186.559 117.268 186.439 119.548 186.099C122.148 185.709 124.718 185.089 127.328 184.559C126.758 181.129 125.178 178.809 122.338 177.649C119.968 176.679 117.458 175.989 114.958 175.409C114.068 175.199 112.908 175.499 112.038 175.919C106.918 178.419 101.628 178.749 96.1576 177.429C95.6476 177.309 95.1076 177.249 94.3476 177.119C94.3476 178.379 94.3076 179.459 94.3576 180.539C94.4276 181.909 94.5775 183.279 94.6975 184.649C94.3775 184.369 93.8275 184.139 93.7675 183.809C93.2975 181.279 92.5776 178.759 93.6476 176.179C93.9476 175.469 93.9175 174.619 94.0675 173.639H81.7276C81.0476 175.179 81.8276 175.529 83.0276 175.679C86.1076 176.079 88.4675 177.709 89.6975 180.479C90.5775 182.459 90.8675 184.689 91.5075 187.179C89.7675 187.629 88.3976 188.129 86.9876 188.309C82.0876 188.929 77.2076 188.379 72.3376 187.769C70.5976 187.549 68.8675 187.269 67.1375 186.939C66.2375 186.769 65.3276 186.539 64.4776 186.199C61.2176 184.889 60.8475 184.349 61.2575 180.839C61.4675 179.019 61.9676 177.229 62.3476 175.439C62.5576 174.459 62.7876 173.479 63.0476 172.339C62.3076 171.959 61.7076 171.569 61.0476 171.309C56.7076 169.589 52.8576 167.049 49.1876 164.209C45.2276 161.139 43.2076 156.789 41.4276 152.279C39.3876 147.119 38.8676 141.709 38.7276 136.239C38.6076 131.709 38.4576 127.179 38.5376 122.649C38.6476 116.469 39.0976 110.289 39.1676 104.109C39.2176 99.499 38.8376 94.889 38.7376 90.279C38.5176 79.849 40.0376 69.879 45.9176 60.889C50.3076 54.169 55.0976 47.929 61.9976 43.559C65.4776 41.349 69.1576 39.759 73.1876 38.929C74.3576 38.689 75.4876 38.279 77.0976 37.819C76.3676 36.919 75.9176 35.999 75.1776 35.509C73.1776 34.189 71.1476 32.869 68.9576 31.919C67.4876 31.279 65.7376 31.209 64.1076 30.979C62.5576 30.759 60.9876 30.599 59.4276 30.529C54.6876 30.299 54.1176 25.259 55.9876 22.449C59.1576 17.689 65.3876 16.689 69.7476 20.519C71.5276 22.079 72.9376 24.089 74.4176 25.979C76.4076 28.519 78.2876 31.139 80.2176 33.729C80.6876 34.919 80.3976 36.399 81.7976 37.799C81.7876 34.139 84.1576 32.939 86.5876 31.749C87.4076 31.349 88.2175 30.869 88.8875 30.259C89.4775 29.719 89.9876 29.009 90.2976 28.279C90.8476 26.989 90.3176 26.119 88.9076 25.969C88.1976 25.889 87.3976 25.919 86.7476 26.189C84.7476 26.999 83.0676 28.209 82.1076 30.269C81.5376 31.479 80.8076 32.619 80.1576 33.799C80.5976 30.319 82.0476 27.489 85.2976 25.819C85.8676 25.529 86.4675 25.259 87.0775 25.089C88.6975 24.649 90.4676 25.139 91.0576 26.219C91.8376 27.669 91.2375 28.979 90.2675 29.999C89.3975 30.919 88.2976 31.629 87.2276 32.339C86.3076 32.949 85.3076 33.429 84.3376 33.949C83.0776 34.629 82.7975 35.659 83.1975 37.229C84.3475 37.299 85.5176 37.359 86.6876 37.459C95.6576 38.239 104.408 40.009 112.348 44.409C115.858 46.359 119.028 48.949 122.238 51.409C123.108 52.079 123.548 53.319 124.188 54.299C123.558 53.799 122.858 53.369 122.298 52.799C118.788 49.179 114.558 46.589 110.108 44.319C108.808 43.649 107.768 43.539 106.688 44.769C106.188 45.339 105.308 45.569 104.598 45.949C105.348 45.079 106.088 44.199 106.938 43.209C106.508 42.929 106.238 42.669 105.918 42.569C103.898 41.939 101.878 41.249 99.8275 40.759C94.4375 39.479 89.0276 38.229 83.4376 38.199C81.9776 38.199 80.6876 38.289 80.4676 40.289C80.1476 37.319 79.5276 34.699 77.7176 32.409C76.0576 30.309 74.6576 28.009 73.0376 25.879C71.8076 24.269 70.5776 22.599 69.0576 21.289C65.1476 17.919 59.5076 18.799 56.7376 23.039C54.7076 26.149 56.0776 29.299 59.7176 29.729C61.7376 29.969 63.7676 30.039 65.7976 30.199C67.8676 30.359 69.7576 31.029 71.4776 32.209C72.3876 32.829 73.3575 33.369 74.3275 33.909C76.8475 35.299 78.4176 37.249 78.2276 40.709C77.3876 39.059 76.2776 39.229 74.9676 39.459C67.8876 40.679 62.0175 44.219 56.7675 48.909C55.2975 50.229 54.0476 51.789 52.2376 53.739C55.9176 54.329 58.8875 55.019 61.8875 55.239C69.6075 55.819 77.1575 54.449 84.6975 52.939C91.1875 51.649 97.3876 49.709 103.148 46.409C103.608 46.149 104.158 46.059 104.668 45.879C104.378 46.209 104.148 46.689 103.788 46.849C99.8276 48.609 95.9676 50.659 91.8576 51.939C87.1176 53.419 82.1976 54.459 77.2776 55.199C72.5476 55.909 67.7276 56.229 62.9376 56.229C59.9376 56.229 56.9275 55.399 53.9475 54.799C52.3975 54.489 51.3075 54.729 50.4475 56.149C48.5375 59.309 46.4376 62.359 44.6076 65.569C43.6776 67.199 42.6976 68.909 42.6076 70.829C42.8876 71.029 43.0276 71.189 43.1876 71.219C54.8576 73.289 66.5275 74.599 78.4475 73.199C94.3975 71.329 108.888 65.899 121.928 56.589C122.788 55.979 123.398 55.029 124.128 54.229C124.838 54.879 125.668 55.449 126.228 56.209C129.718 60.889 132.688 65.859 134.388 71.849C136.198 71.589 138.028 71.669 139.568 71.029C141.688 70.149 143.818 69.849 146.028 69.749C148.338 69.639 150.638 69.599 152.948 69.569C155.628 69.539 158.068 70.369 160.178 71.969C161.318 72.829 162.388 72.979 163.768 72.709C168.838 71.719 173.898 70.599 179.028 70.039C183.228 69.579 187.498 69.829 191.738 69.829C192.248 69.829 192.768 70.189 193.278 70.379C185.578 69.959 181.218 72.559 177.898 79.539C177.698 79.959 177.518 80.379 177.348 80.809C175.078 86.559 174.598 92.469 175.968 98.479C176.988 102.959 178.798 107.079 182.178 110.329C186.948 114.909 193.218 114.979 198.108 110.529C200.698 108.169 202.438 105.249 203.458 101.929C206.098 93.389 205.328 85.169 201.018 77.299C200.628 76.579 200.318 75.819 199.978 75.079L199.968 75.089ZM146.198 72.029C146.398 71.599 146.608 71.179 147.048 70.249C144.708 70.739 142.778 70.939 140.988 71.549C136.998 72.919 134.478 76.009 132.598 79.579C127.808 88.659 129.328 101.169 135.768 109.059C138.628 112.569 141.888 115.129 146.958 114.299C146.678 113.999 146.488 113.699 146.218 113.519C141.728 110.439 139.238 106.039 137.748 100.919C136.648 97.129 136.328 93.249 136.718 89.399C137.258 83.999 138.968 78.909 142.638 74.739C143.628 73.619 145.038 72.869 146.268 71.959C145.938 72.369 145.668 72.859 145.268 73.189C142.298 75.719 140.348 78.929 139.148 82.579C137.008 89.099 137.048 95.649 139.188 102.159C140.178 105.169 141.658 107.929 143.798 110.289C149.098 116.129 156.628 116.329 161.998 110.569C163.668 108.779 165.088 106.619 166.098 104.389C168.798 98.379 169.018 92.089 167.538 85.699C166.498 81.199 164.658 77.129 161.258 73.909C157.198 70.059 152.628 69.329 147.508 71.639C147.098 71.819 146.648 71.899 146.208 72.019L146.198 72.029ZM52.8076 127.019C55.0076 128.779 56.8076 130.729 59.0276 131.869C61.7876 133.289 64.8276 134.279 67.8576 135.009C73.8476 136.449 79.5976 135.489 84.9976 132.519C86.8576 131.499 88.6575 130.329 90.5675 129.419C96.8275 126.439 100.078 120.689 104.038 115.239C99.0676 112.969 94.6376 110.399 91.7976 106.339C88.2476 108.119 84.9476 109.969 81.4876 111.459C78.6976 112.659 75.7276 113.639 72.6276 113.339C69.6976 113.049 66.7875 112.479 63.8875 111.889C62.4675 111.599 61.0576 111.049 60.1676 109.589C62.2776 110.159 64.1276 110.739 65.9976 111.149C67.7076 111.519 69.4776 111.619 71.1776 112.009C74.4276 112.749 77.3076 111.679 80.2276 110.369C66.7576 109.359 53.5675 106.899 40.0775 103.649C40.0775 106.849 40.2076 109.799 40.0476 112.739C39.7676 117.799 39.0276 122.829 39.3376 127.929C39.5676 131.709 39.6276 135.499 39.8976 139.279C40.7776 151.489 45.0375 161.729 56.5775 167.709C56.8275 167.839 57.0576 167.989 57.2976 168.119C60.4076 169.779 63.5676 171.489 67.1276 171.689C75.0476 172.129 82.9776 172.369 90.9076 172.569C93.6676 172.639 96.4476 172.179 99.2076 172.249C107.068 172.459 114.448 170.639 121.258 166.849C128.338 162.909 133.198 157.269 134.588 148.889C136.468 137.529 136.648 126.099 136.648 114.639C136.648 114.009 136.478 113.389 136.358 112.569C131.008 111.809 125.708 111.119 120.428 110.289C117.798 109.869 115.098 109.479 112.648 108.519C111.258 107.979 110.128 107.709 108.738 108.069C107.408 108.409 106.058 108.649 104.638 108.949C104.258 111.199 102.208 113.409 105.138 115.259C104.398 116.439 103.698 117.419 103.138 118.479C98.1375 127.849 89.6276 132.709 80.0576 135.869C72.3176 138.419 64.8976 136.419 57.9176 132.709C55.6276 131.489 53.7876 129.729 52.8076 127.049V127.019ZM89.4876 100.929C86.5276 100.299 83.4776 99.619 80.4076 99.019C78.7276 98.689 77.5775 97.699 76.7575 96.309C75.5975 94.339 75.2176 92.219 75.4776 89.919C75.7676 87.359 77.1475 85.859 79.5775 85.349C82.2875 84.789 85.0175 84.289 87.7575 83.909C89.1775 83.709 90.4476 83.639 91.0876 82.009C91.3076 81.449 92.1675 80.989 92.8275 80.769C94.7475 80.129 96.6876 79.469 98.6776 79.119C101.948 78.549 105.268 78.209 108.568 77.759C109.208 77.669 109.998 77.719 110.438 77.369C113.158 75.169 116.538 74.939 119.718 74.079C120.308 73.919 120.858 73.629 121.548 73.339C120.318 69.049 119.818 64.719 117.788 60.629C102.138 70.089 85.2775 74.619 67.2076 75.069C66.9376 86.119 66.6876 96.779 66.4176 107.799C71.7576 108.239 76.7976 108.799 81.8476 109.019C85.8076 109.189 88.7275 106.899 91.5675 104.339C90.8275 103.109 90.2076 102.089 89.4976 100.909L89.4876 100.929ZM41.8376 72.439C41.5176 73.629 41.1376 74.859 40.8576 76.109C39.0376 84.239 39.7976 92.479 39.9176 100.689C39.9376 101.869 40.5376 102.579 41.6176 102.929C42.3176 103.159 43.0176 103.409 43.7476 103.549C50.0876 104.799 56.4376 106.039 62.7876 107.239C63.6276 107.399 64.5276 107.259 65.5276 107.259C65.8076 96.529 66.0776 86.029 66.3476 75.429C58.0876 74.419 50.1576 73.459 41.8376 72.439ZM135.848 111.609C135.948 111.439 136.048 111.269 136.148 111.089C131.438 105.499 128.938 99.109 128.988 91.799C129.038 84.519 131.268 78.079 136.658 72.909C136.348 72.749 136.278 72.669 136.208 72.679C128.488 73.399 120.858 74.649 113.368 76.659C112.418 76.909 111.448 77.779 110.878 78.629C108.378 82.329 107.428 86.599 107.058 90.929C106.558 96.959 108.058 102.499 112.288 107.019C112.978 107.759 114.088 108.339 115.088 108.529C118.618 109.189 122.198 109.659 125.748 110.179C129.118 110.669 132.488 111.139 135.858 111.609H135.848ZM92.2675 103.689C92.7775 102.949 92.9475 102.609 93.1975 102.339C97.2775 97.999 101.608 98.779 103.828 104.319C104.308 105.529 104.918 106.159 106.198 106.179C107.328 106.199 108.468 106.269 110.008 106.339C104.318 96.969 105.518 87.919 109.488 78.589C105.778 79.109 102.288 79.439 98.8776 80.119C91.4276 81.609 91.5275 81.739 89.0175 89.139C87.2775 94.279 89.1075 98.889 92.2675 103.669V103.689ZM185.168 113.929C176.888 108.679 174.348 100.979 174.268 92.279C174.188 83.519 176.778 75.789 185.068 70.339C183.348 70.339 182.348 70.359 181.348 70.339C178.788 70.279 176.768 71.259 175.018 73.179C172.498 75.939 170.398 78.919 169.088 82.439C168.588 83.789 168.408 85.059 168.718 86.489C169.508 90.139 169.618 93.859 168.898 97.499C168.378 100.139 168.998 102.449 170.088 104.709C170.768 106.109 171.748 107.369 172.618 108.669C175.528 113.009 179.598 114.709 185.168 113.949V113.929ZM62.9076 175.749C62.6276 177.489 62.3575 179.019 62.1375 180.559C61.7075 183.639 62.0376 184.149 65.0476 185.189C65.4876 185.339 65.9176 185.559 66.3676 185.609C72.1476 186.259 77.9176 187.029 83.7176 187.429C85.8276 187.579 88.1676 187.809 90.1876 186.399C89.3576 182.839 89.0375 179.039 84.7575 177.449C84.0675 178.679 83.4576 179.779 82.8476 180.869C82.7276 180.819 82.6076 180.779 82.4776 180.729C82.8876 179.559 83.2976 178.389 83.7176 177.179C82.2976 176.329 80.9676 176.239 79.4676 176.849C76.5676 178.029 73.5576 178.229 70.4976 177.499C68.0876 176.919 65.6776 176.379 62.9076 175.729V175.749ZM132.928 71.709C131.528 65.679 128.288 60.739 124.698 55.749C122.518 57.269 120.508 58.669 118.678 59.959C120.098 64.649 121.418 69.029 122.758 73.469C125.868 72.989 128.778 72.539 131.688 72.069C132.038 72.009 132.378 71.869 132.918 71.709H132.928ZM137.728 128.319C143.708 127.239 148.358 121.929 149.048 115.479C147.138 115.349 145.188 115.419 143.338 115.029C141.438 114.629 139.628 113.779 137.728 113.119V128.319ZM89.8875 84.599C87.7075 84.909 86.1776 85.099 84.6576 85.349C81.2076 85.909 80.5075 86.669 80.2675 90.139C80.2075 91.049 80.1976 91.989 80.3376 92.889C81.0076 97.439 84.3776 100.009 88.7876 99.269C86.6276 94.459 87.1776 89.759 89.8976 84.589L89.8875 84.599ZM102.188 113.529C103.848 110.129 103.908 107.109 102.628 104.049C101.148 100.509 97.7776 99.599 94.8376 102.059C93.8276 102.899 93.1076 104.099 92.2276 105.159C95.0076 108.909 98.3176 111.429 102.188 113.529V113.529ZM94.6476 175.949C100.038 178.169 105.168 177.349 110.238 175.789C112.168 175.189 112.358 174.359 111.738 171.789C111.208 171.839 110.668 171.869 110.128 171.939C107.868 172.239 105.608 172.659 103.338 172.839C100.858 173.039 98.3676 172.999 95.8776 173.099C95.5376 173.109 95.2176 173.329 94.8076 173.479C94.7576 174.259 94.7076 174.979 94.6476 175.949ZM174.848 71.459C170.718 72.289 166.898 73.059 162.828 73.879C163.918 75.489 164.838 76.749 165.638 78.079C166.448 79.429 167.138 80.849 167.798 82.079C170.148 78.549 172.368 75.199 174.858 71.469L174.848 71.459ZM64.0976 172.469C63.1376 174.149 63.8876 174.989 65.2376 175.279C68.0976 175.899 70.9676 176.549 73.8676 176.829C75.4576 176.979 77.1376 176.479 78.7276 176.099C79.7576 175.849 80.5976 175.209 79.9376 173.549C74.7276 173.199 69.3776 172.829 64.0876 172.469H64.0976ZM164.048 110.219C167.238 110.599 170.108 110.949 173.178 111.319C171.338 108.159 169.668 105.279 167.998 102.409C166.768 104.839 165.498 107.359 164.048 110.219ZM80.2675 86.269C76.8475 87.379 75.8676 89.459 76.4976 93.219C77.0476 96.469 78.5576 98.009 81.4076 97.969C79.2276 94.369 78.3075 90.639 80.2675 86.259V86.269ZM106.588 107.389C106.578 107.279 106.568 107.159 106.558 107.049C105.998 107.079 105.438 107.109 104.888 107.129C104.908 107.289 104.928 107.449 104.938 107.609C105.488 107.539 106.038 107.459 106.598 107.389H106.588Z" fill="black"/> +<path d="M199.968 75.089C200.308 75.829 200.618 76.599 201.008 77.309C205.318 85.179 206.088 93.399 203.448 101.939C202.418 105.259 200.688 108.189 198.098 110.539C193.208 114.989 186.938 114.919 182.168 110.339C178.778 107.089 176.968 102.969 175.958 98.489C174.598 92.479 175.068 86.569 177.338 80.819C177.508 80.389 177.698 79.969 177.888 79.549C181.208 72.569 185.568 69.979 193.268 70.389C193.658 70.539 194.038 70.679 194.428 70.829C195.448 71.399 196.458 71.979 197.478 72.549C198.308 73.389 199.128 74.239 199.958 75.079L199.968 75.089ZM187.358 74.169C185.558 74.279 184.108 74.919 183.398 76.719C183.138 76.979 182.888 77.229 182.628 77.489C182.078 77.929 181.388 78.269 180.988 78.819C179.018 81.469 177.968 84.529 177.528 87.769C176.638 94.409 177.528 100.729 181.688 106.189C186.608 112.649 194.118 112.439 198.858 105.849C201.068 102.769 202.248 99.279 202.658 95.569C203.358 89.319 202.448 83.409 198.398 78.309C198.318 78.239 198.248 78.169 198.168 78.099C197.308 77.279 196.448 76.469 195.598 75.649C195.158 75.359 194.708 75.059 194.268 74.769C192.048 73.169 189.708 73.209 187.278 74.209L187.368 74.169H187.358Z" fill="white"/> +<path d="M197.488 72.549C196.468 71.979 195.458 71.399 194.438 70.829C195.458 71.399 196.468 71.979 197.488 72.549Z" fill="black"/> +<path d="M52.8076 127.009C53.7876 129.689 55.6276 131.449 57.9176 132.669C64.8976 136.379 72.3176 138.389 80.0576 135.829C89.6276 132.669 98.1375 127.819 103.138 118.439C103.698 117.389 104.398 116.409 105.138 115.219C102.208 113.369 104.248 111.159 104.638 108.909C106.058 108.609 107.408 108.369 108.738 108.029C110.128 107.669 111.258 107.939 112.648 108.479C115.098 109.439 117.798 109.829 120.428 110.249C125.708 111.079 131.008 111.779 136.358 112.529C136.478 113.349 136.648 113.969 136.648 114.599C136.648 126.059 136.468 137.489 134.588 148.849C133.208 157.229 128.348 162.869 121.258 166.809C114.438 170.599 107.068 172.419 99.2076 172.209C96.4476 172.139 93.6676 172.599 90.9076 172.529C82.9776 172.329 75.0476 172.099 67.1276 171.649C63.5676 171.449 60.4076 169.729 57.2976 168.079C57.0576 167.949 56.8275 167.789 56.5775 167.669C45.0475 161.699 40.7776 151.449 39.8976 139.239C39.6276 135.459 39.5676 131.669 39.3376 127.889C39.0276 122.799 39.7676 117.759 40.0476 112.699C40.2076 109.759 40.0775 106.809 40.0775 103.609C53.5675 106.859 66.7676 109.319 80.2276 110.329C77.3076 111.639 74.4276 112.709 71.1776 111.969C69.4676 111.579 67.6976 111.489 65.9976 111.109C64.1176 110.699 62.2776 110.119 60.1676 109.549C61.0676 111.009 62.4775 111.559 63.8875 111.849C66.7775 112.439 69.6876 113.009 72.6276 113.299C75.7276 113.609 78.6976 112.619 81.4876 111.419C84.9476 109.929 88.2476 108.089 91.7976 106.299C94.6476 110.359 99.0776 112.929 104.038 115.199C100.068 120.649 96.8275 126.389 90.5675 129.379C88.6575 130.289 86.8576 131.449 84.9976 132.479C79.5976 135.449 73.8376 136.409 67.8576 134.969C64.8276 134.239 61.7876 133.249 59.0276 131.829C56.8076 130.689 55.0076 128.739 52.8076 126.979V127.009Z" fill="white"/> +<path d="M104.578 45.959C105.288 45.579 106.168 45.349 106.668 44.779C107.748 43.549 108.778 43.659 110.088 44.329C114.538 46.599 118.768 49.179 122.278 52.809C122.828 53.379 123.528 53.809 124.168 54.309L124.118 54.259C123.388 55.059 122.778 56.009 121.918 56.619C108.878 65.939 94.3876 71.359 78.4376 73.229C66.5176 74.629 54.8476 73.319 43.1776 71.249C43.0176 71.219 42.8775 71.049 42.5975 70.859C42.6875 68.939 43.6675 67.229 44.5975 65.599C46.4175 62.389 48.5276 59.339 50.4376 56.179C51.2976 54.759 52.3876 54.509 53.9376 54.829C56.9176 55.429 59.9276 56.259 62.9276 56.259C67.7076 56.259 72.5375 55.939 77.2675 55.229C82.1875 54.499 87.0975 53.449 91.8475 51.969C95.9575 50.689 99.8275 48.629 103.778 46.879C104.148 46.719 104.368 46.239 104.658 45.909L104.598 45.969L104.578 45.959Z" fill="white"/> +<path d="M89.4875 100.919C90.1975 102.099 90.8176 103.119 91.5576 104.349C88.7176 106.909 85.7975 109.199 81.8375 109.029C76.7875 108.809 71.7475 108.249 66.4075 107.809C66.6775 96.789 66.9275 86.119 67.1975 75.079C85.2775 74.639 102.138 70.109 117.778 60.639C119.808 64.729 120.308 69.059 121.538 73.349C120.848 73.629 120.298 73.929 119.708 74.089C116.538 74.949 113.148 75.179 110.428 77.379C109.988 77.739 109.198 77.689 108.558 77.769C105.258 78.219 101.948 78.549 98.6675 79.129C96.6775 79.479 94.7375 80.139 92.8175 80.779C92.1575 80.999 91.3075 81.459 91.0775 82.019C90.4375 83.649 89.1676 83.719 87.7476 83.919C85.0076 84.299 82.2775 84.799 79.5675 85.359C77.1275 85.859 75.7575 87.369 75.4675 89.929C75.2075 92.229 75.5776 94.349 76.7476 96.319C77.5676 97.709 78.7175 98.699 80.3975 99.029C83.4675 99.629 86.5175 100.309 89.4775 100.939L89.4875 100.919Z" fill="#C8E2E2"/> +<path d="M41.8375 72.429C50.1575 73.439 58.0875 74.409 66.3475 75.419C66.0775 86.009 65.7975 96.519 65.5275 107.249C64.5275 107.249 63.6275 107.389 62.7875 107.229C56.4376 106.039 50.0876 104.789 43.7476 103.539C43.0276 103.399 42.3276 103.149 41.6176 102.919C40.5476 102.569 39.9376 101.859 39.9176 100.679C39.7976 92.469 39.0376 84.239 40.8576 76.099C41.1376 74.849 41.5175 73.619 41.8375 72.429Z" fill="white"/> +<path d="M104.648 45.899C104.138 46.069 103.588 46.159 103.128 46.429C97.3776 49.729 91.1676 51.669 84.6776 52.959C77.1376 54.459 69.5876 55.839 61.8676 55.259C58.8576 55.029 55.8875 54.339 52.2175 53.759C54.0275 51.809 55.2776 50.249 56.7476 48.929C61.9976 44.229 67.8575 40.699 74.9475 39.479C76.2575 39.249 77.3675 39.079 78.2075 40.729C78.3975 37.269 76.8276 35.319 74.3076 33.929C73.3376 33.399 72.3675 32.859 71.4575 32.229C69.7375 31.049 67.8475 30.389 65.7775 30.219C63.7475 30.059 61.7175 29.989 59.6975 29.749C56.0575 29.319 54.6775 26.179 56.7175 23.059C59.4875 18.819 65.1275 17.939 69.0375 21.309C70.5575 22.619 71.7875 24.289 73.0175 25.899C74.6375 28.029 76.0375 30.339 77.6975 32.429C79.5075 34.709 80.1275 37.339 80.4475 40.309C80.6675 38.299 81.9575 38.209 83.4175 38.219C89.0175 38.249 94.4176 39.499 99.8076 40.779C101.868 41.269 103.878 41.959 105.898 42.589C106.218 42.689 106.488 42.949 106.918 43.229C106.068 44.219 105.328 45.099 104.578 45.969L104.638 45.909L104.648 45.899Z" fill="white"/> +<path d="M135.858 111.599C132.488 111.119 129.118 110.659 125.748 110.169C122.188 109.649 118.618 109.179 115.088 108.519C114.088 108.329 112.978 107.749 112.288 107.009C108.058 102.479 106.558 96.949 107.058 90.919C107.418 86.579 108.378 82.309 110.878 78.619C111.448 77.769 112.418 76.899 113.368 76.649C120.858 74.649 128.488 73.389 136.208 72.669C136.278 72.669 136.358 72.739 136.658 72.899C131.268 78.069 129.038 84.509 128.988 91.789C128.938 99.099 131.448 105.489 136.148 111.079C136.048 111.249 135.948 111.419 135.848 111.599H135.858Z" fill="white"/> +<path d="M146.197 72.029C146.627 71.899 147.088 71.829 147.498 71.649C152.618 69.339 157.198 70.069 161.248 73.919C164.648 77.149 166.478 81.219 167.528 85.709C169.008 92.099 168.788 98.389 166.088 104.399C165.088 106.629 163.658 108.789 161.988 110.579C156.618 116.329 149.088 116.129 143.788 110.299C141.648 107.939 140.168 105.179 139.178 102.169C137.038 95.659 136.997 89.109 139.137 82.589C140.337 78.939 142.287 75.729 145.257 73.199C145.657 72.859 145.927 72.379 146.257 71.969L146.197 72.029V72.029ZM150.238 74.659C149.248 75.079 148.018 75.259 147.308 75.959C145.888 77.369 144.408 78.899 143.558 80.669C139.808 88.499 139.958 96.389 144.048 104.049C144.968 105.769 146.468 107.339 148.028 108.539C151.398 111.119 155.478 110.899 158.718 108.169C159.828 107.229 160.918 106.119 161.628 104.879C166.368 96.6089 166.317 88.2089 161.957 79.8189C160.697 77.3889 158.728 75.519 156.118 74.549C154.158 73.819 152.087 73.109 150.147 74.719L150.218 74.659H150.238Z" fill="white"/> +<path d="M92.2775 103.679C89.1075 98.889 87.2775 94.289 89.0275 89.149C91.5375 81.749 91.4375 81.619 98.8875 80.129C102.297 79.449 105.778 79.119 109.498 78.599C105.538 87.919 104.337 96.979 110.017 106.349C108.477 106.279 107.347 106.209 106.207 106.189C104.927 106.159 104.317 105.539 103.837 104.329C101.617 98.779 97.2875 98.009 93.2075 102.349C92.9575 102.619 92.7875 102.949 92.2775 103.699V103.679Z" fill="white"/> +<path d="M146.258 71.959C145.038 72.879 143.618 73.619 142.628 74.739C138.958 78.909 137.258 83.999 136.708 89.399C136.318 93.249 136.638 97.129 137.738 100.919C139.228 106.039 141.718 110.439 146.208 113.519C146.478 113.709 146.668 113.999 146.948 114.299C141.878 115.129 138.618 112.559 135.758 109.059C129.328 101.169 127.798 88.649 132.588 79.579C134.478 75.999 136.988 72.909 140.978 71.549C142.768 70.939 144.708 70.729 147.038 70.249C146.598 71.179 146.388 71.599 146.188 72.029L146.248 71.969L146.258 71.959Z" fill="white"/> +<path d="M94.6675 184.649C94.5475 183.279 94.3975 181.909 94.3275 180.539C94.2675 179.469 94.3175 178.389 94.3175 177.119C95.0875 177.249 95.6176 177.309 96.1276 177.429C101.598 178.749 106.887 178.419 112.007 175.919C112.867 175.499 114.028 175.199 114.928 175.409C117.428 175.979 119.928 176.679 122.308 177.649C125.148 178.809 126.728 181.129 127.298 184.559C124.688 185.089 122.128 185.709 119.518 186.099C117.248 186.439 114.928 186.559 112.628 186.719C106.858 187.119 101.268 185.909 95.6876 184.659C95.3376 184.579 94.9675 184.599 94.6075 184.579L94.6675 184.639V184.649Z" fill="white"/> +<path d="M185.178 113.919C179.608 114.679 175.538 112.979 172.628 108.639C171.758 107.339 170.778 106.079 170.098 104.679C169.008 102.429 168.388 100.109 168.908 97.469C169.628 93.829 169.528 90.109 168.728 86.459C168.418 85.039 168.598 83.759 169.098 82.409C170.408 78.879 172.508 75.909 175.028 73.149C176.778 71.229 178.788 70.259 181.358 70.309C182.358 70.329 183.358 70.309 185.078 70.309C176.788 75.759 174.198 83.489 174.278 92.249C174.358 100.949 176.898 108.649 185.178 113.899V113.919Z" fill="white"/> +<path d="M62.9075 175.739C65.6775 176.379 68.0975 176.929 70.4975 177.509C73.5575 178.239 76.5675 178.039 79.4675 176.859C80.9675 176.249 82.3075 176.339 83.7175 177.189C83.2975 178.399 82.8875 179.569 82.4775 180.739C82.5975 180.789 82.7175 180.829 82.8475 180.879C83.4575 179.779 84.0675 178.689 84.7575 177.459C89.0375 179.049 89.3575 182.839 90.1875 186.409C88.1675 187.819 85.8275 187.589 83.7175 187.439C77.9175 187.029 72.1475 186.259 66.3675 185.619C65.9175 185.569 65.4875 185.349 65.0475 185.199C62.0275 184.149 61.6975 183.649 62.1375 180.569C62.3475 179.029 62.6275 177.499 62.9075 175.759V175.739Z" fill="white"/> +<path d="M132.928 71.709C132.388 71.869 132.048 72.019 131.698 72.069C128.788 72.529 125.878 72.979 122.768 73.469C121.428 69.019 120.098 64.639 118.688 59.959C120.528 58.679 122.528 57.279 124.708 55.749C128.298 60.739 131.538 65.679 132.938 71.709H132.928Z" fill="white"/> +<path d="M137.728 128.309V113.109C139.628 113.779 141.438 114.619 143.338 115.019C145.188 115.409 147.138 115.339 149.048 115.469C148.358 121.919 143.708 127.229 137.728 128.309Z" fill="white"/> +<path d="M89.8875 84.589C87.1675 89.759 86.6176 94.459 88.7776 99.269C84.3676 100.009 80.9975 97.439 80.3275 92.889C80.1975 91.989 80.1975 91.049 80.2575 90.139C80.5075 86.669 81.2075 85.909 84.6475 85.349C86.1675 85.099 87.6976 84.909 89.8776 84.599L89.8875 84.589Z" fill="white"/> +<path d="M102.188 113.519C98.3276 111.419 95.0175 108.889 92.2275 105.149C93.0975 104.089 93.8175 102.899 94.8375 102.049C97.7875 99.589 101.148 100.509 102.628 104.039C103.908 107.089 103.848 110.109 102.188 113.519V113.519Z" fill="white"/> +<path d="M94.6475 175.939C94.7075 174.969 94.7576 174.249 94.8076 173.469C95.2176 173.319 95.5376 173.109 95.8776 173.089C98.3576 172.989 100.858 173.019 103.338 172.829C105.608 172.649 107.868 172.229 110.128 171.929C110.668 171.859 111.208 171.829 111.738 171.779C112.358 174.349 112.168 175.179 110.238 175.779C105.168 177.339 100.038 178.149 94.6475 175.939Z" fill="white"/> +<path d="M174.848 71.449C172.368 75.189 170.138 78.529 167.788 82.059C167.128 80.829 166.448 79.409 165.628 78.059C164.828 76.729 163.908 75.479 162.818 73.859C166.888 73.039 170.718 72.269 174.838 71.439L174.848 71.449Z" fill="white"/> +<path d="M64.0975 172.459C69.3775 172.819 74.7375 173.179 79.9475 173.539C80.6075 175.199 79.7675 175.839 78.7375 176.089C77.1375 176.469 75.4675 176.969 73.8775 176.819C70.9775 176.549 68.0975 175.899 65.2475 175.269C63.9075 174.979 63.1475 174.139 64.1075 172.459H64.0975Z" fill="white"/> +<path d="M80.1276 33.799C80.7876 32.629 81.5075 31.489 82.0775 30.269C83.0375 28.209 84.7175 27.0089 86.7175 26.1889C87.3675 25.9289 88.1676 25.899 88.8776 25.969C90.2876 26.119 90.8175 26.989 90.2675 28.279C89.9575 29.009 89.4475 29.729 88.8575 30.259C88.1875 30.869 87.3776 31.349 86.5576 31.749C84.1176 32.929 81.7475 34.139 81.7675 37.799C80.3675 36.399 80.6476 34.919 80.1876 33.729L80.1375 33.799H80.1276Z" fill="white"/> +<path d="M164.048 110.209C165.498 107.349 166.768 104.829 167.998 102.399C169.668 105.269 171.338 108.149 173.178 111.309C170.108 110.939 167.238 110.599 164.048 110.209Z" fill="white"/> +<path d="M80.2675 86.259C78.3075 90.639 79.2275 94.359 81.4075 97.969C78.5675 97.999 77.0476 96.469 76.4976 93.219C75.8576 89.459 76.8475 87.379 80.2675 86.269V86.259Z" fill="white"/> +<path d="M106.598 107.369C106.048 107.439 105.498 107.519 104.938 107.589C104.918 107.429 104.898 107.269 104.888 107.109C105.448 107.079 106.008 107.049 106.558 107.029C106.568 107.139 106.578 107.259 106.588 107.369H106.598Z" fill="white"/> +<path d="M194.258 74.769C194.698 75.059 195.148 75.359 195.588 75.649C196.448 76.469 197.308 77.279 198.158 78.099C198.238 78.169 198.308 78.239 198.388 78.309C198.568 78.919 198.638 79.579 198.938 80.119C201.798 85.259 202.438 90.769 201.658 96.479C201.118 100.389 199.728 103.989 196.898 106.869C192.848 110.979 187.468 110.939 183.438 106.799C181.258 104.559 179.898 101.849 179.088 98.869C177.028 91.219 178.138 84.069 182.628 77.499C182.888 77.239 183.138 76.989 183.398 76.729C184.718 75.879 186.038 75.029 187.358 74.179L187.268 74.219C189.598 74.409 191.928 74.589 194.258 74.779V74.769ZM187.228 98.809C189.438 96.379 190.758 93.469 192.308 90.469C191.428 90.139 190.878 89.939 190.308 89.719C190.798 88.549 191.238 87.509 191.878 85.969C188.818 88.989 188.478 90.249 189.928 92.059C189.078 94.319 187.478 96.269 187.218 98.799L187.228 98.809Z" fill="#F4F5FF"/> +<path d="M182.628 77.489C178.138 84.069 177.018 91.219 179.088 98.859C179.888 101.829 181.258 104.549 183.438 106.789C187.468 110.929 192.848 110.979 196.898 106.859C199.738 103.979 201.128 100.379 201.658 96.469C202.438 90.759 201.808 85.239 198.938 80.109C198.638 79.569 198.568 78.909 198.388 78.299C202.438 83.399 203.348 89.309 202.648 95.559C202.228 99.269 201.058 102.769 198.848 105.839C194.108 112.419 186.598 112.639 181.678 106.179C177.518 100.719 176.628 94.399 177.518 87.759C177.958 84.519 179.008 81.459 180.978 78.809C181.388 78.259 182.068 77.919 182.618 77.479L182.628 77.489Z" fill="black"/> +<path d="M194.258 74.769C191.928 74.579 189.598 74.399 187.268 74.209C189.688 73.209 192.038 73.169 194.258 74.769Z" fill="black"/> +<path d="M187.358 74.169C186.038 75.019 184.718 75.869 183.398 76.719C184.098 74.919 185.558 74.279 187.358 74.169Z" fill="black"/> +<path d="M198.158 78.089C197.298 77.269 196.438 76.459 195.588 75.639C196.448 76.459 197.308 77.269 198.158 78.089Z" fill="black"/> +<path d="M150.168 74.719C152.108 73.109 154.168 73.819 156.138 74.549C158.748 75.519 160.718 77.389 161.978 79.819C166.338 88.209 166.388 96.609 161.648 104.879C160.928 106.129 159.848 107.239 158.738 108.169C155.488 110.899 151.408 111.119 148.048 108.539C146.478 107.339 144.988 105.769 144.068 104.049C139.978 96.389 139.828 88.499 143.578 80.669C144.428 78.899 145.908 77.369 147.328 75.959C148.038 75.259 149.268 75.079 150.258 74.659C150.088 74.869 149.968 75.149 149.758 75.279C146.008 77.469 144.038 80.919 142.928 84.959C141.598 89.799 141.718 94.629 143.168 99.419C144.028 102.289 145.468 104.849 147.698 106.899C151.068 109.999 155.278 110.099 158.618 106.959C159.868 105.789 160.998 104.319 161.708 102.779C165.068 95.469 164.978 88.119 161.348 80.929C159.188 76.649 155.968 73.909 150.738 74.779C150.568 74.809 150.378 74.739 150.188 74.719H150.168Z" fill="black"/> +<path d="M187.228 98.809C187.488 96.279 189.088 94.329 189.938 92.069C188.488 90.259 188.828 88.999 191.888 85.979C191.248 87.509 190.808 88.559 190.318 89.729C190.888 89.939 191.438 90.149 192.318 90.479C190.758 93.479 189.448 96.399 187.238 98.819L187.228 98.809Z" fill="black"/> +<path d="M150.168 74.719C150.348 74.739 150.538 74.809 150.718 74.779C155.948 73.909 159.168 76.639 161.328 80.929C164.958 88.119 165.058 95.479 161.688 102.779C160.978 104.329 159.848 105.789 158.598 106.959C155.258 110.099 151.048 109.999 147.678 106.899C145.448 104.849 144.008 102.279 143.148 99.419C141.708 94.639 141.588 89.799 142.908 84.959C144.018 80.919 145.988 77.469 149.738 75.279C149.948 75.149 150.078 74.869 150.238 74.659L150.168 74.719V74.719ZM150.118 97.729C150.318 97.879 150.518 98.029 150.708 98.179C152.268 96.029 153.968 93.959 154.578 91.179C153.768 90.899 153.228 90.719 152.678 90.529C153.238 89.179 153.728 87.989 154.218 86.809C153.968 86.709 153.728 86.599 153.478 86.499C152.698 88.209 151.848 89.889 151.218 91.659C151.118 91.939 152.038 92.589 152.468 93.049C151.668 94.649 150.888 96.189 150.118 97.729Z" fill="#EEEFFC"/> +<path d="M150.118 97.729C150.888 96.189 151.668 94.639 152.468 93.049C152.028 92.579 151.118 91.939 151.218 91.659C151.848 89.899 152.698 88.209 153.478 86.499C153.728 86.599 153.968 86.709 154.218 86.809C153.728 87.999 153.238 89.179 152.678 90.529C153.228 90.719 153.768 90.899 154.578 91.179C153.968 93.959 152.268 96.029 150.708 98.179C150.508 98.029 150.308 97.879 150.118 97.729Z" fill="black"/> </svg> diff --git a/frontend/app/svg/icons/activity.svg b/frontend/app/svg/icons/activity.svg new file mode 100644 index 000000000..8647c472b --- /dev/null +++ b/frontend/app/svg/icons/activity.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-activity" viewBox="0 0 16 16"> + <path fill-rule="evenodd" d="M6 2a.5.5 0 0 1 .47.33L10 12.036l1.53-4.208A.5.5 0 0 1 12 7.5h3.5a.5.5 0 0 1 0 1h-3.15l-1.88 5.17a.5.5 0 0 1-.94 0L6 3.964 4.47 8.171A.5.5 0 0 1 4 8.5H.5a.5.5 0 0 1 0-1h3.15l1.88-5.17A.5.5 0 0 1 6 2Z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/arrow-counterclockwise.svg b/frontend/app/svg/icons/arrow-counterclockwise.svg new file mode 100644 index 000000000..db6047930 --- /dev/null +++ b/frontend/app/svg/icons/arrow-counterclockwise.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16"> + <path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z"/> + <path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/card-checklist.svg b/frontend/app/svg/icons/card-checklist.svg new file mode 100644 index 000000000..d1433f078 --- /dev/null +++ b/frontend/app/svg/icons/card-checklist.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-card-checklist" viewBox="0 0 16 16"> + <path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h13zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/> + <path d="M7 5.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm-1.496-.854a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0zM7 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm-1.496-.854a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 0 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/card-text.svg b/frontend/app/svg/icons/card-text.svg new file mode 100644 index 000000000..5ec2a474e --- /dev/null +++ b/frontend/app/svg/icons/card-text.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-card-text" viewBox="0 0 16 16"> + <path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h13zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/> + <path d="M3 5.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zM3 8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9A.5.5 0 0 1 3 8zm0 2.5a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/cross.svg b/frontend/app/svg/icons/cross.svg new file mode 100644 index 000000000..9948ce547 --- /dev/null +++ b/frontend/app/svg/icons/cross.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16"> + <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/files.svg b/frontend/app/svg/icons/files.svg new file mode 100644 index 000000000..fa47e0ef9 --- /dev/null +++ b/frontend/app/svg/icons/files.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-files" viewBox="0 0 16 16"> + <path d="M13 0H6a2 2 0 0 0-2 2 2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2 2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm0 13V4a2 2 0 0 0-2-2H5a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zM3 4a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/grid-1x2.svg b/frontend/app/svg/icons/grid-1x2.svg new file mode 100644 index 000000000..92a990e35 --- /dev/null +++ b/frontend/app/svg/icons/grid-1x2.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-grid-1x2" viewBox="0 0 16 16"> + <path d="M6 1H1v14h5V1zm9 0h-5v5h5V1zm0 9v5h-5v-5h5zM0 1a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V1zm9 0a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1V1zm1 8a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1h-5z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/grid.svg b/frontend/app/svg/icons/grid.svg new file mode 100644 index 000000000..7de4b940d --- /dev/null +++ b/frontend/app/svg/icons/grid.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-grid" viewBox="0 0 16 16"> + <path d="M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5v-3zM2.5 2a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zM1 10.5A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/ic-errors.svg b/frontend/app/svg/icons/ic-errors.svg new file mode 100644 index 000000000..64f4043d1 --- /dev/null +++ b/frontend/app/svg/icons/ic-errors.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-exclamation-circle" viewBox="0 0 16 16"> + <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/> + <path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/ic-network.svg b/frontend/app/svg/icons/ic-network.svg new file mode 100644 index 000000000..626ffd4d9 --- /dev/null +++ b/frontend/app/svg/icons/ic-network.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-cloud-arrow-down" viewBox="0 0 16 16"> + <path fill-rule="evenodd" d="M7.646 10.854a.5.5 0 0 0 .708 0l2-2a.5.5 0 0 0-.708-.708L8.5 9.293V5.5a.5.5 0 0 0-1 0v3.793L6.354 8.146a.5.5 0 1 0-.708.708l2 2z"/> + <path d="M4.406 3.342A5.53 5.53 0 0 1 8 2c2.69 0 4.923 2 5.166 4.579C14.758 6.804 16 8.137 16 9.773 16 11.569 14.502 13 12.687 13H3.781C1.708 13 0 11.366 0 9.318c0-1.763 1.266-3.223 2.942-3.593.143-.863.698-1.723 1.464-2.383zm.653.757c-.757.653-1.153 1.44-1.153 2.056v.448l-.445.049C2.064 6.805 1 7.952 1 9.318 1 10.785 2.23 12 3.781 12h8.906C13.98 12 15 10.988 15 9.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 4.825 10.328 3 8 3a4.53 4.53 0 0 0-2.941 1.1z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/ic-rage.svg b/frontend/app/svg/icons/ic-rage.svg new file mode 100644 index 000000000..898b083d8 --- /dev/null +++ b/frontend/app/svg/icons/ic-rage.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-emoji-angry" viewBox="0 0 16 16"> + <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/> + <path d="M4.285 12.433a.5.5 0 0 0 .683-.183A3.498 3.498 0 0 1 8 10.5c1.295 0 2.426.703 3.032 1.75a.5.5 0 0 0 .866-.5A4.498 4.498 0 0 0 8 9.5a4.5 4.5 0 0 0-3.898 2.25.5.5 0 0 0 .183.683zm6.991-8.38a.5.5 0 1 1 .448.894l-1.009.504c.176.27.285.64.285 1.049 0 .828-.448 1.5-1 1.5s-1-.672-1-1.5c0-.247.04-.48.11-.686a.502.502 0 0 1 .166-.761l2-1zm-6.552 0a.5.5 0 0 0-.448.894l1.009.504A1.94 1.94 0 0 0 5 6.5C5 7.328 5.448 8 6 8s1-.672 1-1.5c0-.247-.04-.48-.11-.686a.502.502 0 0 0-.166-.761l-2-1z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/ic-resources.svg b/frontend/app/svg/icons/ic-resources.svg new file mode 100644 index 000000000..5cc666d44 --- /dev/null +++ b/frontend/app/svg/icons/ic-resources.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-file-earmark-text" viewBox="0 0 16 16"> + <path d="M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z"/> + <path d="M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5L9.5 0zm0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/integrations/teams-white.svg b/frontend/app/svg/icons/integrations/teams-white.svg new file mode 100644 index 000000000..039205d76 --- /dev/null +++ b/frontend/app/svg/icons/integrations/teams-white.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"> + <path d="M9.186 4.797a2.42 2.42 0 1 0-2.86-2.448h1.178c.929 0 1.682.753 1.682 1.682v.766Zm-4.295 7.738h2.613c.929 0 1.682-.753 1.682-1.682V5.58h2.783a.7.7 0 0 1 .682.716v4.294a4.197 4.197 0 0 1-4.093 4.293c-1.618-.04-3-.99-3.667-2.35Zm10.737-9.372a1.674 1.674 0 1 1-3.349 0 1.674 1.674 0 0 1 3.349 0Zm-2.238 9.488c-.04 0-.08 0-.12-.002a5.19 5.19 0 0 0 .381-2.07V6.306a1.692 1.692 0 0 0-.15-.725h1.792c.39 0 .707.317.707.707v3.765a2.598 2.598 0 0 1-2.598 2.598h-.013Z"/> + <path d="M.682 3.349h6.822c.377 0 .682.305.682.682v6.822a.682.682 0 0 1-.682.682H.682A.682.682 0 0 1 0 10.853V4.03c0-.377.305-.682.682-.682Zm5.206 2.596v-.72h-3.59v.72h1.357V9.66h.87V5.945h1.363Z"/> +</svg> diff --git a/frontend/app/svg/icons/integrations/teams.svg b/frontend/app/svg/icons/integrations/teams.svg new file mode 100644 index 000000000..e93adb2b8 --- /dev/null +++ b/frontend/app/svg/icons/integrations/teams.svg @@ -0,0 +1,44 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="-334.32495 -518.3335 2897.4829 3110.001"> + <path + d="M1554.637 777.5h575.713c54.391 0 98.483 44.092 98.483 98.483v524.398c0 199.901-162.051 361.952-361.952 361.952h-1.711c-199.901.028-361.975-162-362.004-361.901V828.971c.001-28.427 23.045-51.471 51.471-51.471z" + fill="#5059C9" /> + <circle r="233.25" cy="440.583" cx="1943.75" fill="#5059C9" /> + <circle r="336.917" cy="336.917" cx="1218.083" fill="#7B83EB" /> + <path + d="M1667.323 777.5H717.01c-53.743 1.33-96.257 45.931-95.01 99.676v598.105c-7.505 322.519 247.657 590.16 570.167 598.053 322.51-7.893 577.671-275.534 570.167-598.053V877.176c1.245-53.745-41.268-98.346-95.011-99.676z" + fill="#7B83EB" /> + <path + d="M1244 777.5v838.145c-.258 38.435-23.549 72.964-59.09 87.598a91.856 91.856 0 01-35.765 7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833a631.287 631.287 0 01-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52z" + opacity=".1" /> + <path + d="M1192.167 777.5v889.978a91.802 91.802 0 01-7.257 35.765c-14.634 35.541-49.163 58.833-87.598 59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833-7.257-17.623-12.958-34.21-18.142-51.833a631.282 631.282 0 01-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52z" + opacity=".2" /> + <path + d="M1192.167 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855h-447.84A631.282 631.282 0 01622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52z" + opacity=".2" /> + <path + d="M1140.333 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855H649.472A631.282 631.282 0 01622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52z" + opacity=".2" /> + <path + d="M1244 509.522v163.275c-8.812.518-17.105 1.037-25.917 1.037-8.812 0-17.105-.518-25.917-1.037a284.472 284.472 0 01-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003a288.02 288.02 0 01-16.587-51.833h258.648c52.305.198 94.657 42.549 94.856 94.854z" + opacity=".1" /> + <path + d="M1192.167 561.355v111.442a284.472 284.472 0 01-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z" + opacity=".2" /> + <path + d="M1192.167 561.355v111.442a284.472 284.472 0 01-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z" + opacity=".2" /> + <path + d="M1140.333 561.355v103.148c-104.963-24.857-191.679-98.469-233.25-198.003h138.395c52.305.199 94.656 42.551 94.855 94.855z" + opacity=".2" /> + <linearGradient gradientTransform="matrix(1 0 0 -1 0 2075.333)" y2="394.261" x2="942.234" y1="1683.073" x1="198.099" + gradientUnits="userSpaceOnUse" id="a"> + <stop offset="0" stop-color="#5a62c3" /> + <stop offset=".5" stop-color="#4d55bd" /> + <stop offset="1" stop-color="#3940ab" /> + </linearGradient> + <path + d="M95.01 466.5h950.312c52.473 0 95.01 42.538 95.01 95.01v950.312c0 52.473-42.538 95.01-95.01 95.01H95.01c-52.473 0-95.01-42.538-95.01-95.01V561.51c0-52.472 42.538-95.01 95.01-95.01z" + fill="url(#a)" /> + <path d="M820.211 828.193h-189.97v517.297h-121.03V828.193H320.123V727.844h500.088z" fill="#FFF" /> +</svg> diff --git a/frontend/app/svg/icons/no-recordings.svg b/frontend/app/svg/icons/no-recordings.svg new file mode 100644 index 000000000..dc507ccf4 --- /dev/null +++ b/frontend/app/svg/icons/no-recordings.svg @@ -0,0 +1,20 @@ +<svg viewBox="0 0 250 100" xmlns="http://www.w3.org/2000/svg"> +<rect width="250" height="100" rx="13.1579" fill="#3EAAAF" fill-opacity="0.08"/> +<rect opacity="0.6" x="86.8421" y="28.579" width="138.158" height="14.4737" rx="6.57895" fill-opacity="0.5"/> +<rect opacity="0.3" x="86.8421" y="55.579" width="38.1579" height="14.4737" rx="6.57895" fill-opacity="0.5"/> +<rect opacity="0.3" x="129.842" y="55.579" width="18.1579" height="14.4737" rx="6.57895" fill-opacity="0.5"/> +<g clip-path="url(#clip0_101_7)"> +<path d="M46 73.625C39.7343 73.625 33.7251 71.1359 29.2946 66.7054C24.8641 62.2748 22.375 56.2657 22.375 50C22.375 43.7343 24.8641 37.7251 29.2946 33.2946C33.7251 28.8641 39.7343 26.375 46 26.375C52.2657 26.375 58.2748 28.8641 62.7054 33.2946C67.1359 37.7251 69.625 43.7343 69.625 50C69.625 56.2657 67.1359 62.2748 62.7054 66.7054C58.2748 71.1359 52.2657 73.625 46 73.625ZM46 77C53.1608 77 60.0284 74.1554 65.0919 69.0919C70.1554 64.0284 73 57.1608 73 50C73 42.8392 70.1554 35.9716 65.0919 30.9081C60.0284 25.8446 53.1608 23 46 23C38.8392 23 31.9716 25.8446 26.9081 30.9081C21.8446 35.9716 19 42.8392 19 50C19 57.1608 21.8446 64.0284 26.9081 69.0919C31.9716 74.1554 38.8392 77 46 77V77Z" fill-opacity="0.5"/> +<g clip-path="url(#clip1_101_7)"> +<path d="M56.125 50C56.125 52.6853 55.0583 55.2607 53.1595 57.1595C51.2607 59.0583 48.6853 60.125 46 60.125C43.3147 60.125 40.7394 59.0583 38.8405 57.1595C36.9417 55.2607 35.875 52.6853 35.875 50C35.875 47.3147 36.9417 44.7394 38.8405 42.8405C40.7394 40.9417 43.3147 39.875 46 39.875C48.6853 39.875 51.2607 40.9417 53.1595 42.8405C55.0583 44.7394 56.125 47.3147 56.125 50V50ZM42.6512 45.7551C42.5323 45.6363 42.3712 45.5695 42.2031 45.5695C42.0351 45.5695 41.8739 45.6363 41.7551 45.7551C41.6363 45.8739 41.5695 46.0351 41.5695 46.2031C41.5695 46.3712 41.6363 46.5323 41.7551 46.6512L45.1052 50L41.7551 53.3488C41.6963 53.4077 41.6496 53.4775 41.6177 53.5544C41.5859 53.6313 41.5695 53.7137 41.5695 53.7969C41.5695 53.8801 41.5859 53.9625 41.6177 54.0393C41.6496 54.1162 41.6963 54.1861 41.7551 54.2449C41.8739 54.3637 42.0351 54.4305 42.2031 54.4305C42.2863 54.4305 42.3687 54.4141 42.4456 54.3823C42.5225 54.3504 42.5923 54.3037 42.6512 54.2449L46 50.8948L49.3488 54.2449C49.4077 54.3037 49.4775 54.3504 49.5544 54.3823C49.6313 54.4141 49.7137 54.4305 49.7969 54.4305C49.8801 54.4305 49.9625 54.4141 50.0393 54.3823C50.1162 54.3504 50.1861 54.3037 50.2449 54.2449C50.3037 54.1861 50.3504 54.1162 50.3823 54.0393C50.4141 53.9625 50.4305 53.8801 50.4305 53.7969C50.4305 53.7137 50.4141 53.6313 50.3823 53.5544C50.3504 53.4775 50.3037 53.4077 50.2449 53.3488L46.8948 50L50.2449 46.6512C50.3037 46.5923 50.3504 46.5225 50.3823 46.4456C50.4141 46.3687 50.4305 46.2863 50.4305 46.2031C50.4305 46.1199 50.4141 46.0375 50.3823 45.9607C50.3504 45.8838 50.3037 45.8139 50.2449 45.7551C50.1861 45.6963 50.1162 45.6496 50.0393 45.6177C49.9625 45.5859 49.8801 45.5695 49.7969 45.5695C49.7137 45.5695 49.6313 45.5859 49.5544 45.6177C49.4775 45.6496 49.4077 45.6963 49.3488 45.7551L46 49.1052L42.6512 45.7551Z" fill-opacity="0.5"/> +</g> +</g> +<defs> +<clipPath id="clip0_101_7"> +<rect width="54" height="54" fill="white" transform="translate(19 23)"/> +</clipPath> +<clipPath id="clip1_101_7"> +<rect width="20.25" height="20.25" fill="white" transform="translate(35.875 39.875)"/> +</clipPath> +</defs> +</svg> diff --git a/frontend/app/svg/icons/play-circle-bold.svg b/frontend/app/svg/icons/play-circle-bold.svg new file mode 100644 index 000000000..65e426510 --- /dev/null +++ b/frontend/app/svg/icons/play-circle-bold.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"> + <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/> + <path d="M6.271 5.055a.5.5 0 0 1 .52.038l3.5 2.5a.5.5 0 0 1 0 .814l-3.5 2.5A.5.5 0 0 1 6 10.5v-5a.5.5 0 0 1 .271-.445z"/> +</svg> diff --git a/frontend/app/svg/icons/plus-lg.svg b/frontend/app/svg/icons/plus-lg.svg new file mode 100644 index 000000000..2a37f8191 --- /dev/null +++ b/frontend/app/svg/icons/plus-lg.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-plus-lg" viewBox="0 0 16 16"> + <path fill-rule="evenodd" d="M8 2a.5.5 0 0 1 .5.5v5h5a.5.5 0 0 1 0 1h-5v5a.5.5 0 0 1-1 0v-5h-5a.5.5 0 0 1 0-1h5v-5A.5.5 0 0 1 8 2Z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/plus.svg b/frontend/app/svg/icons/plus.svg index 7270c46a8..818b4f214 100644 --- a/frontend/app/svg/icons/plus.svg +++ b/frontend/app/svg/icons/plus.svg @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg viewBox="0 0 8 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> - <path d="M4,0 C4.27614237,0 4.5,0.223857625 4.5,0.5 L4.5,3.5 L7.5,3.5 C7.77614237,3.5 8,3.72385763 8,4 C8,4.27614237 7.77614237,4.5 7.5,4.5 L4.5,4.5 L4.5,7.5 C4.5,7.77614237 4.27614237,8 4,8 C3.72385763,8 3.5,7.77614237 3.5,7.5 L3.5,4.5 L0.5,4.5 C0.223857625,4.5 0,4.27614237 0,4 C0,3.72385763 0.223857625,3.5 0.5,3.5 L3.5,3.5 L3.5,0.5 C3.5,0.223857625 3.72385763,0 4,0 Z" id="Path"></path> +<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-plus" viewBox="0 0 16 16"> + <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/> </svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/pointer-sessions-search.svg b/frontend/app/svg/icons/pointer-sessions-search.svg new file mode 100644 index 000000000..56e15a190 --- /dev/null +++ b/frontend/app/svg/icons/pointer-sessions-search.svg @@ -0,0 +1,3 @@ +<svg viewBox="0 0 157 200" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M156.877 1.64707C156.914 0.898282 156.144 0.376269 155.462 0.687692L151.61 2.44671C150.865 2.78695 150.823 3.82961 151.538 4.22925L155.2 6.27642C155.848 6.63833 156.65 6.19411 156.687 5.45327L156.877 1.64707ZM0.371826 100.542C7.55254 99.968 15.7724 100.132 24.1312 100.173C32.4654 100.214 40.9251 100.134 48.4931 99.0527C56.0574 97.9722 62.8178 95.8818 67.6883 91.8446C72.5816 87.7878 75.4799 81.8404 75.4799 73.2182H74.0852C74.0852 81.5822 71.2859 87.1788 66.7306 90.955C62.152 94.7504 55.7115 96.782 48.269 97.845C40.8302 98.9071 32.4775 98.9907 24.1391 98.9495C15.8249 98.9083 7.63855 99.9611 0.371826 100.542ZM68.0514 86.3092C70.9892 88.8626 74.6421 89.6433 78.719 88.9095C82.7546 88.183 87.2025 85.9768 91.8679 82.5856C101.206 75.798 111.628 64.106 121.647 49.2607C121.647 49.2607 121.608 48.8808 121.139 48.6374C111.156 63.4289 100.138 74.9847 90.9781 81.6434C86.394 84.9754 82.1543 87.8167 78.4382 88.4857C74.7635 89.1473 71.6011 88.4469 69.0333 86.215L68.0514 86.3092ZM121.647 49.2607C147.109 11.5346 146.27 15.5123 154.168 4.98484L153.696 4.31224C145.876 14.7349 146.614 10.8914 121.139 48.6374C121.608 48.8808 121.647 49.2607 121.647 49.2607ZM75.4799 73.2182C75.4799 72.7895 75.4325 72.4126 75.3242 72.0969C75.2168 71.7832 75.0327 71.4851 74.723 71.2815C74.0475 70.8378 73.2543 71.1004 72.7178 71.3974C71.583 72.0263 70.2547 73.5012 69.143 75.2197C68.0165 76.9617 67.0397 79.0652 66.6771 81.0438C66.3186 82.9988 66.5353 84.9913 68.0514 86.3092L69.0333 86.215C67.9989 85.316 67.7208 83.0559 68.0542 81.238C68.3834 79.4433 68.593 77.4789 69.6638 75.8237C70.7498 74.1449 72.6304 72.8931 73.4626 72.4322C73.9099 72.1842 73.9629 72.311 73.8876 72.2613C73.8783 72.2552 73.9327 72.2866 73.9885 72.4497C74.0442 72.6112 74.0852 72.8588 74.0852 73.2182H75.4799Z" fill="black"/> +</svg> diff --git a/frontend/app/svg/icons/record-circle.svg b/frontend/app/svg/icons/record-circle.svg new file mode 100644 index 000000000..f63b28733 --- /dev/null +++ b/frontend/app/svg/icons/record-circle.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"> + <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/> + <path d="M11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/> +</svg> diff --git a/frontend/app/svg/icons/signpost-split.svg b/frontend/app/svg/icons/signpost-split.svg new file mode 100644 index 000000000..7fae7b246 --- /dev/null +++ b/frontend/app/svg/icons/signpost-split.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-signpost-split" viewBox="0 0 16 16"> + <path d="M7 7V1.414a1 1 0 0 1 2 0V2h5a1 1 0 0 1 .8.4l.975 1.3a.5.5 0 0 1 0 .6L14.8 5.6a1 1 0 0 1-.8.4H9v10H7v-5H2a1 1 0 0 1-.8-.4L.225 9.3a.5.5 0 0 1 0-.6L1.2 7.4A1 1 0 0 1 2 7h5zm1 3V8H2l-.75 1L2 10h6zm0-5h6l.75-1L14 3H8v2z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/speedometer2.svg b/frontend/app/svg/icons/speedometer2.svg new file mode 100644 index 000000000..f9506878a --- /dev/null +++ b/frontend/app/svg/icons/speedometer2.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-speedometer2" viewBox="0 0 16 16"> + <path d="M8 4a.5.5 0 0 1 .5.5V6a.5.5 0 0 1-1 0V4.5A.5.5 0 0 1 8 4zM3.732 5.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707zM2 10a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 10zm9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5zm.754-4.246a.389.389 0 0 0-.527-.02L7.547 9.31a.91.91 0 1 0 1.302 1.258l3.434-4.297a.389.389 0 0 0-.029-.518z"/> + <path fill-rule="evenodd" d="M0 10a8 8 0 1 1 15.547 2.661c-.442 1.253-1.845 1.602-2.932 1.25C11.309 13.488 9.475 13 8 13c-1.474 0-3.31.488-4.615.911-1.087.352-2.49.003-2.932-1.25A7.988 7.988 0 0 1 0 10zm8-7a7 7 0 0 0-6.603 9.329c.203.575.923.876 1.68.63C4.397 12.533 6.358 12 8 12s3.604.532 4.923.96c.757.245 1.477-.056 1.68-.631A7 7 0 0 0 8 3z"/> +</svg> \ No newline at end of file diff --git a/frontend/app/svg/icons/stop-record-circle.svg b/frontend/app/svg/icons/stop-record-circle.svg new file mode 100644 index 000000000..c12c7ef2b --- /dev/null +++ b/frontend/app/svg/icons/stop-record-circle.svg @@ -0,0 +1,3 @@ +<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path clip-rule="evenoddCustomFill" d="M8 16H16V8H8V16ZM12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2Z" /> +</svg> diff --git a/frontend/app/svg/login-illustration.svg b/frontend/app/svg/login-illustration.svg new file mode 100644 index 000000000..6420880b7 --- /dev/null +++ b/frontend/app/svg/login-illustration.svg @@ -0,0 +1,94 @@ +<svg viewBox="0 0 720 517" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_317_4)"> +<path d="M427.832 256.816C427.838 256.646 427.848 256.477 427.854 256.302C432.847 257.959 437.861 259.921 442.823 261.147C445.659 261.848 448.973 262.674 451.077 261.117C455.34 257.982 458.051 251.795 458.222 243.856C458.548 228.854 458.122 213.501 458.013 198.299C457.92 185.691 457.749 173.05 457.787 160.5C457.808 152.816 458.334 145.345 458.309 137.646C458.281 127.56 454.762 119.894 448.105 115.676C442.752 112.275 437.336 109.285 431.927 106.699C423.912 102.857 415.881 99.4063 407.854 96.0382C401.907 93.5412 395.956 91.5363 390.006 89.1338C382.411 86.074 374.816 82.8897 367.221 79.6527C356.109 74.9209 344.998 69.7963 333.893 65.4264C328.468 63.2894 326.174 65.5295 325.539 71.9446C325.12 76.1827 324.918 80.5972 324.936 85.0648C324.998 100.884 325.272 116.792 325.306 132.605C325.319 139.149 324.924 145.524 324.712 151.975C324.252 165.883 323.565 179.687 323.41 193.725C323.31 202.599 325.704 207.83 331.527 211.722C336.057 214.744 340.736 216.533 345.352 218.824C346.294 219.291 347.249 219.466 348.194 219.881C355.873 223.254 363.549 226.586 371.225 230.048C378.553 233.357 385.88 236.732 393.205 240.207C404.748 245.688 416.289 251.272 427.832 256.816ZM348.903 68.7934C360.798 74.2473 372.686 79.9765 384.581 85.0855C401.751 92.466 418.922 99.2477 436.092 106.628C440.538 108.542 445.015 111.416 449.405 114.423C455.175 118.379 459.033 126.032 459.459 134.908C459.804 142.15 459.329 149.139 459.273 156.268C459.186 167.015 459.055 177.744 459.111 188.546C459.179 201.676 459.447 214.883 459.587 228.038C459.649 233.937 459.96 240.006 459.528 245.628C458.94 253.265 456.409 259.134 452.476 262.76C450.819 264.287 448.233 264.547 445.957 264.026C434.554 261.42 423.091 257.036 411.613 251.228C407.31 249.048 403.004 246.79 398.695 244.886C378.596 235.995 358.494 227.438 338.404 218.212C334.406 216.374 330.318 212.592 326.537 208.808C323.668 205.933 321.911 201.454 322.088 196.273C322.632 180.096 323.214 163.934 323.472 147.624C323.761 129.383 323.702 110.987 323.808 92.6643C323.854 85.1196 323.621 77.4159 324.103 70.1137C324.52 63.8001 327.52 61.4691 331.863 62.8178C337.534 64.5823 343.21 66.5122 348.887 68.375C348.894 68.5165 348.9 68.6532 348.903 68.7934Z" fill="black"/> +<path d="M407.85 198.991C407.859 198.55 407.869 198.108 407.878 197.671C407.688 197.353 407.499 196.834 407.309 196.755C402.835 194.811 398.293 192.135 393.91 191.22C388.988 190.187 384.2 188.736 379.313 185.327C378.331 184.642 377.271 184.409 376.251 184.106C372.355 182.967 368.615 183.483 364.515 180.4C361.766 178.337 358.754 177.822 355.863 176.782C355.437 176.63 355.036 177.426 354.094 178.239C372.564 185.367 390.207 192.182 407.85 198.991ZM389.414 225.755L389.436 224.753C393.913 226.608 398.396 228.647 402.866 230.264C406.805 231.689 410.726 232.657 414.282 231.146C416.745 230.096 418.448 226.395 417.929 222.922C417.183 217.933 414.425 213.135 411.329 211.905C397.625 206.449 383.924 201.033 370.223 195.784C367.095 194.584 364.648 196.254 363.019 199.402C360.862 203.567 363.184 211.264 367.844 214.109C374.989 218.477 382.22 221.922 389.414 225.755ZM428.692 149.239C428.698 149.089 428.705 148.943 428.708 148.791C432.398 149.95 436.11 151.387 439.76 152.137C441.184 152.425 443.341 151.935 443.662 150.501C444.687 145.877 445.25 140.773 445.536 135.613C445.745 131.838 444.212 128.396 441.473 126.84C439.343 125.632 437.173 124.762 435.034 124.063C428.328 121.86 421.632 120.226 414.923 117.61C401.909 112.545 388.885 106.978 375.865 101.585C363.455 96.4446 351.041 91.2259 338.63 86.1331C335.48 84.8382 334.756 85.9513 334.877 90.7345C334.977 94.6272 335.002 98.4889 335.023 102.349C335.055 107.517 335.723 109.01 339.196 110.607C351.781 116.395 364.369 122.346 376.957 127.747C394.202 135.144 411.447 142.096 428.692 149.239ZM419.306 185.519C419.306 185.5 419.306 185.476 419.306 185.452C426.553 188.454 433.797 191.492 441.044 194.442C445.533 196.272 447.094 194.006 446.391 187.176C446.024 183.614 445.717 180.042 445.633 176.563C445.455 169.347 443.761 165.857 438.976 164.076C430.613 160.957 422.263 158.284 413.9 155.102C396.12 148.341 378.34 141.532 360.557 134.42C352.89 131.35 345.221 127.546 337.554 123.934C335.412 122.927 334.013 123.31 333.715 126.507C333.556 128.213 333.441 129.972 333.478 131.764C333.568 136.276 333.994 140.941 333.842 145.328C333.687 149.934 334.119 151.464 337.333 152.93C339.469 153.901 341.614 154.152 343.76 154.979C354.109 158.969 364.462 162.917 374.815 167.123C389.644 173.146 404.474 179.375 419.306 185.519ZM335.745 164.487C335.35 168.434 334.856 172.33 334.644 176.376C334.604 177.136 335.614 179.011 336.261 179.465C338.191 180.821 340.184 181.948 342.152 182.768C349.483 185.823 349.461 186.327 349.722 174.493C349.822 170.002 348.846 167.955 345.886 166.437C343.138 165.03 340.371 163.889 337.619 162.883C337.082 162.684 336.569 163.645 336.046 164.076C335.947 164.212 335.844 164.347 335.745 164.487ZM427.831 256.815C416.291 251.273 404.747 245.687 393.204 240.206C385.879 236.731 378.551 233.356 371.224 230.048C363.548 226.586 355.872 223.253 348.196 219.882C347.248 219.465 346.296 219.291 345.351 218.823C340.738 216.534 336.059 214.744 331.529 211.723C325.703 207.83 323.312 202.6 323.409 193.725C323.567 179.688 324.251 165.882 324.711 151.975C324.926 145.525 325.317 139.148 325.305 132.604C325.271 116.796 324.997 100.884 324.935 85.0641C324.919 80.5978 325.118 76.1821 325.538 71.944C326.172 65.5288 328.467 63.2888 333.892 65.4258C344.997 69.7957 356.111 74.9216 367.219 79.6569C374.815 82.8939 382.41 86.0734 390.005 89.1331C395.955 91.5356 401.906 93.5405 407.856 96.0388C415.88 99.4056 423.911 102.861 431.928 106.7C437.335 109.289 442.754 112.281 448.104 115.675C454.76 119.898 458.28 127.56 458.311 137.647C458.333 145.344 457.807 152.815 457.785 160.5C457.748 173.049 457.922 185.692 458.012 198.299C458.121 213.501 458.547 228.853 458.224 243.857C458.05 251.794 455.339 257.982 451.079 261.123C448.972 262.674 445.657 261.847 442.822 261.147C437.86 259.92 432.846 257.958 427.853 256.302C427.846 256.477 427.84 256.646 427.831 256.815Z" fill="#D2DAFF"/> +<path d="M335.403 149.592C337.891 150.623 340.288 151.654 342.681 152.602C357.349 158.419 372.014 164.11 386.682 170.061C402.376 176.428 418.073 183.064 433.767 189.517C436.692 190.719 439.618 191.562 442.546 192.645C444.324 193.305 445.018 192.184 444.906 189.666C444.732 185.766 444.418 181.814 444.349 177.958C444.206 169.87 442.876 167.302 437.538 165.431C431.167 163.195 424.804 161.253 418.43 158.805C397.722 150.859 377.013 142.823 356.302 134.627C350.447 132.308 344.584 129.247 338.724 126.671C335.372 125.196 334.803 125.55 334.797 130.299C334.788 136.533 335.176 142.936 335.403 149.592ZM419.307 185.52C404.474 179.376 389.645 173.143 374.812 167.123C364.462 162.918 354.11 158.97 343.76 154.98C341.615 154.153 339.47 153.902 337.334 152.931C334.119 151.465 333.684 149.934 333.843 145.324C333.992 140.941 333.569 136.277 333.476 131.764C333.442 129.968 333.554 128.213 333.715 126.508C334.014 123.307 335.41 122.927 337.555 123.935C345.221 127.547 352.891 131.351 360.558 134.417C378.341 141.533 396.121 148.338 413.901 155.104C422.26 158.279 430.614 160.958 438.974 164.076C443.762 165.858 445.453 169.347 445.633 176.564C445.717 180.038 446.025 183.615 446.392 187.177C447.091 194.006 445.534 196.269 441.044 194.443C433.798 191.494 426.554 188.455 419.307 185.453C419.307 185.477 419.307 185.496 419.307 185.52Z" fill="black"/> +<path d="M347.81 92.5043C347.807 92.2779 347.804 92.0562 347.801 91.8298C344.782 90.5794 341.76 89.3756 338.741 88.0581C337.044 87.3167 336.158 87.8646 336.232 90.6213C336.332 94.3798 336.254 98.0793 336.369 101.844C336.524 107.078 336.81 107.637 340.33 109.214C356.101 116.303 371.876 123.426 387.651 130.324C393.25 132.778 398.855 134.616 404.461 136.774C415.643 141.081 426.832 145.538 438.015 149.672C442.57 151.357 442.293 150.242 443.661 145.012C444.298 142.579 444.289 139.471 444.329 136.565C444.407 131.323 442.483 128.692 438.988 127.517C431.259 124.924 423.543 122.815 415.811 119.9C408.574 117.175 401.321 113.428 394.08 110.539C378.653 104.384 363.233 98.5 347.81 92.5043ZM428.695 149.241C411.449 142.098 394.201 135.145 376.959 127.749C364.368 122.347 351.783 116.397 339.195 110.608C335.722 109.011 335.057 107.514 335.026 102.351C335.001 98.4897 334.976 94.6231 334.877 90.7352C334.755 85.9521 335.483 84.8403 338.632 86.1304C351.043 91.228 363.454 96.4454 375.865 101.586C388.885 106.979 401.908 112.546 414.925 117.612C421.631 120.222 428.328 121.861 435.034 124.064C437.173 124.763 439.346 125.634 441.472 126.841C444.211 128.397 445.747 131.835 445.536 135.614C445.25 140.774 444.69 145.879 443.661 150.502C443.344 151.937 441.186 152.427 439.759 152.133C436.109 151.383 432.397 149.951 428.71 148.793C428.704 148.939 428.701 149.091 428.695 149.241Z" fill="black"/> +<path d="M385.377 203.048C385.377 203.336 385.377 203.618 385.38 203.907C380.891 202.047 376.401 200.289 371.912 198.29C369.223 197.095 367.044 197.701 365.144 199.515C362.952 201.611 363.046 206.253 365.663 209.465C367.557 211.787 369.857 213.784 372.005 214.789C381.656 219.303 391.321 223.795 400.968 227.485C405.081 229.059 409.216 228.956 413.245 229.044C415.537 229.093 416.681 226.592 416.429 222.876C416.189 219.342 414.787 216.591 412.496 214.995C411.153 214.056 409.776 213.198 408.414 212.619C400.735 209.376 393.056 206.229 385.377 203.048ZM389.412 225.754C382.221 221.923 374.99 218.477 367.846 214.11C363.185 211.265 360.863 203.567 363.021 199.402C364.65 196.254 367.096 194.585 370.224 195.785C383.925 201.034 397.626 206.45 411.327 211.905C414.424 213.135 417.184 217.933 417.93 222.923C418.45 226.395 416.746 230.097 414.284 231.141C410.727 232.658 406.807 231.69 402.865 230.263C398.394 228.646 393.914 226.608 389.437 224.754L389.412 225.754Z" fill="black"/> +<path d="M407.85 198.992C390.207 192.182 372.564 185.367 354.094 178.239C355.036 177.427 355.437 176.63 355.863 176.782C358.754 177.822 361.767 178.337 364.515 180.4C368.616 183.483 372.356 182.967 376.251 184.107C377.271 184.409 378.331 184.642 379.313 185.327C384.201 188.736 388.988 190.187 393.91 191.22C398.293 192.135 402.835 194.812 407.309 196.756C407.499 196.834 407.688 197.354 407.878 197.672C407.869 198.108 407.859 198.55 407.85 198.992Z" fill="black"/> +<path d="M336.046 164.073C336.571 163.649 337.081 162.687 337.619 162.881C340.373 163.887 343.14 165.029 345.885 166.439C348.845 167.957 349.821 170 349.722 174.495C349.461 186.324 349.486 185.822 342.152 182.765C340.184 181.945 338.191 180.818 336.263 179.464C335.614 179.008 334.606 177.134 334.644 176.374C334.858 172.333 335.349 168.436 335.744 164.485C335.999 165.414 336.478 166.441 336.465 167.251C336.425 169.907 336.111 172.445 336.114 175.119C336.114 175.958 336.767 177.771 337.146 177.952C340.697 179.604 344.25 181.005 347.726 182.444C347.981 180.188 348.167 178.503 348.357 176.823C348.963 171.498 348.099 169.425 344.312 167.603C341.561 166.281 338.803 165.239 336.046 164.073Z" fill="black"/> +<path d="M365.883 148.778C366.878 147.552 367.758 146.47 368.638 145.388C368.504 145.074 368.37 144.755 368.237 144.441C367.459 144.603 366.682 144.76 365.827 144.937C365.672 143.666 365.529 142.495 365.383 141.328C365.165 141.19 364.944 141.055 364.727 140.917C364.397 142.309 364.064 143.704 363.806 144.79C362.843 144.539 361.941 144.31 361.043 144.081C361.702 145.432 362.358 146.782 363.07 148.245C362.177 149.524 361.273 150.816 360.368 152.108L360.9 153.109C361.758 152.617 362.613 152.123 363.545 151.59C363.847 153.065 364.092 154.269 364.338 155.473L364.857 155.707C365.09 154.491 365.327 153.281 365.569 152.026C366.468 153.088 367.211 153.97 367.957 154.849C368.106 154.624 368.255 154.403 368.404 154.177C367.618 152.496 366.834 150.816 365.883 148.778ZM404.925 168.351C406.228 169.585 407.232 170.538 408.233 171.489C408.348 171.177 408.463 170.866 408.575 170.553C407.297 168.692 406.019 166.836 404.658 164.858C405.466 163.545 406.153 162.436 406.837 161.321C406.678 161.025 406.517 160.733 406.355 160.441C405.575 160.707 404.794 160.977 404.048 161.234C403.523 159.584 403.038 158.061 402.55 156.536C402.348 156.572 402.146 156.604 401.943 156.64C402.009 157.955 402.071 159.27 402.136 160.595C401.241 159.841 400.451 159.169 399.658 158.5C399.565 158.74 399.472 158.984 399.379 159.223C400.271 160.852 401.16 162.48 402.108 164.219C401.104 165.83 400.171 167.326 399.239 168.827C399.447 169.177 399.658 169.528 399.867 169.873C400.806 169.141 401.741 168.403 402.683 167.662C403.193 168.947 403.582 169.927 404.201 171.491C404.53 170.061 404.723 169.221 404.925 168.351ZM382.109 151.217C381.938 151.409 381.77 151.598 381.602 151.792C382.283 153.708 382.967 155.625 383.623 157.468C382.18 158.853 380.915 160.068 379.649 161.283C379.811 161.68 379.97 162.077 380.131 162.469C381.219 161.976 382.308 161.483 383.486 160.946C383.7 162.377 383.893 163.678 384.195 165.705C384.838 164.529 385.18 163.9 385.51 163.303C386.402 164.439 387.167 165.412 388.507 167.122C386.237 159.351 386.237 159.351 388.441 156.024C387.493 154.606 386.545 153.188 385.423 151.512C385.112 152.48 384.767 153.554 384.418 154.631C383.647 153.492 382.88 152.355 382.109 151.217ZM350.553 150.318C350.808 150.223 351.066 150.128 351.321 150.033C350.419 147.71 348.716 144.954 348.821 143.177C348.949 141.026 350.603 139.699 352.064 137.224C350.879 137.424 350.255 137.39 349.732 137.653C349.145 137.946 348.65 138.503 347.842 139.174V135.097C347.659 134.945 347.472 134.795 347.286 134.642C346.872 135.61 346.459 136.579 346.275 137.011C345.016 136.609 344.015 136.29 343.017 135.973C343.033 136.353 343.048 136.733 343.064 137.118C345.862 139.805 345.862 139.805 344.024 145.971C344.83 145.983 345.492 145.994 346.082 146.004C346.4 147.046 346.667 147.923 347.167 149.563C347.572 147.977 347.792 147.11 348.066 146.031C349.036 147.702 349.794 149.008 350.553 150.318ZM335.403 149.59C335.176 142.933 334.788 136.531 334.797 130.297C334.803 125.548 335.372 125.199 338.724 126.674C344.584 129.249 350.447 132.311 356.301 134.625C377.013 142.821 397.722 150.857 418.43 158.808C424.803 161.251 431.167 163.193 437.537 165.429C442.875 167.3 444.206 169.872 444.349 177.956C444.418 181.816 444.732 185.764 444.906 189.669C445.018 192.182 444.324 193.308 442.546 192.648C439.617 191.56 436.692 190.717 433.766 189.515C418.073 183.067 402.376 176.426 386.682 170.059C372.014 164.108 357.349 158.422 342.681 152.6C340.287 151.652 337.89 150.621 335.403 149.59Z" fill="white"/> +<path d="M345.846 101.527C345.234 102.552 344.684 103.474 344.273 104.161C345.389 105.563 346.247 106.641 347.233 107.878C347.466 107.151 347.743 106.293 347.939 105.689C348.874 105.995 349.621 106.233 351.004 106.681C349.816 104.872 349.235 103.984 347.724 101.682C349.017 101.336 349.994 101.075 351.11 100.775C349.428 99.2402 348.318 98.2296 347.628 97.5988C346.185 97.739 345.038 97.853 343.686 97.9875C344.509 99.3395 345.15 100.386 345.846 101.527ZM382.146 116.802C381.499 117.804 380.915 118.711 380.315 119.641C381.419 120.879 382.258 121.816 383.113 122.774C383.495 121.984 383.862 121.226 384.217 120.5C385.115 121.577 385.839 122.442 386.567 123.314C386.694 123.084 386.825 122.855 386.952 122.625C386.29 121.125 385.631 119.625 384.907 117.984C385.818 116.915 386.648 115.941 387.478 114.968C387.36 114.641 387.242 114.314 387.123 113.987C386.157 114.2 385.193 114.414 384.108 114.654C384.018 113.094 383.94 111.763 383.862 110.433C383.663 110.293 383.467 110.159 383.268 110.019C382.976 111.077 382.687 112.135 382.395 113.193C381.708 112.645 381.021 112.096 380.334 111.548C380.215 111.725 380.1 111.902 379.982 112.078C380.694 113.633 381.406 115.188 382.146 116.802ZM364.323 116.016C364.761 114.348 365.031 113.32 365.311 112.258C366.256 113.008 367.021 113.613 367.789 114.223C367.885 114.057 367.979 113.894 368.075 113.728C367.469 112.433 366.863 111.137 366.207 109.74C366.937 108.663 367.587 107.706 368.237 106.748C368.143 106.485 368.047 106.224 367.954 105.965C367.164 105.998 366.375 106.035 365.582 106.07C365.479 104.667 365.383 103.358 365.286 102.053C365.044 101.919 364.801 101.785 364.562 101.653C364.276 103.082 363.993 104.507 363.751 105.724C362.569 104.899 361.559 104.198 360.548 103.492C360.514 103.856 360.48 104.221 360.449 104.586C361.425 105.953 362.404 107.322 363.499 108.853C362.411 110.189 361.459 111.362 360.508 112.534C360.642 112.824 360.775 113.114 360.909 113.404C361.643 113.282 362.38 113.165 363.116 113.044C363.437 113.833 363.757 114.622 364.323 116.016ZM347.808 92.5046C363.231 98.5004 378.655 104.386 394.078 110.54C401.322 113.425 408.572 117.171 415.809 119.9C423.541 122.816 431.258 124.92 438.986 127.517C442.484 128.693 444.405 131.324 444.331 136.562C444.287 139.471 444.296 142.579 443.659 145.012C442.291 150.242 442.568 151.358 438.013 149.672C426.831 145.539 415.645 141.078 404.459 136.775C398.857 134.617 393.251 132.774 387.652 130.326C371.874 123.422 356.103 116.3 340.328 109.215C336.809 107.637 336.526 107.079 336.367 101.845C336.255 98.081 336.33 94.3801 336.23 90.6216C336.156 87.8649 337.042 87.317 338.742 88.0597C341.761 89.3772 344.78 90.5797 347.799 91.8301C347.802 92.0518 347.805 92.2782 347.808 92.5046Z" fill="white"/> +<path d="M385.376 203.048C393.055 206.229 400.734 209.376 408.414 212.619C409.775 213.197 411.152 214.055 412.496 214.995C414.787 216.59 416.189 219.341 416.428 222.875C416.68 226.591 415.536 229.092 413.245 229.044C409.216 228.956 405.081 229.059 400.968 227.484C391.321 223.795 381.655 219.303 372.005 214.788C369.857 213.783 367.556 211.786 365.663 209.464C363.045 206.253 362.952 201.611 365.144 199.515C367.043 197.701 369.222 197.094 371.912 198.29C376.401 200.288 380.89 202.047 385.38 203.907C385.376 203.618 385.376 203.335 385.376 203.048Z" fill="#FFEAD1"/> +<path d="M339.385 171.781C339.22 172.067 339.059 172.355 338.894 172.636C339.525 173.535 340.113 174.779 340.803 175.262C342.429 176.4 346.225 174.341 347.254 171.73C344.35 170.939 342.566 175.728 339.385 171.781ZM336.046 164.075C338.801 165.24 341.561 166.283 344.313 167.604C348.099 169.422 348.964 171.5 348.358 176.82C348.168 178.504 347.981 180.19 347.726 182.441C344.251 181.001 340.697 179.606 337.147 177.948C336.764 177.771 336.115 175.959 336.115 175.121C336.111 172.447 336.425 169.908 336.463 167.246C336.475 166.442 336 165.416 335.745 164.486C335.844 164.35 335.947 164.211 336.046 164.075Z" fill="white"/> +<path d="M350.553 150.317C349.795 149.011 349.036 147.701 348.066 146.03C347.792 147.109 347.572 147.976 347.167 149.562C346.667 147.922 346.4 147.045 346.082 146.003C345.495 145.995 344.83 145.982 344.024 145.97C345.862 139.804 345.862 139.804 343.064 137.117C343.048 136.737 343.033 136.352 343.017 135.972C344.015 136.289 345.016 136.608 346.275 137.015C346.459 136.578 346.872 135.609 347.286 134.641C347.472 134.794 347.659 134.948 347.842 135.096V139.173C348.65 138.502 349.145 137.945 349.735 137.653C350.255 137.389 350.88 137.423 352.064 137.228C350.603 139.703 348.949 141.025 348.821 143.181C348.716 144.953 350.419 147.709 351.321 150.032C351.066 150.127 350.808 150.222 350.553 150.317Z" fill="black"/> +<path d="M382.108 151.217C382.879 152.355 383.65 153.494 384.421 154.628C384.769 153.555 385.114 152.481 385.425 151.513C386.544 153.189 387.492 154.607 388.44 156.02C386.236 159.351 386.236 159.351 388.506 167.122C387.166 165.413 386.404 164.441 385.509 163.303C385.182 163.901 384.837 164.529 384.197 165.706C383.895 163.679 383.699 162.377 383.488 160.948C382.307 161.484 381.218 161.977 380.133 162.471C379.972 162.073 379.81 161.676 379.648 161.283C380.914 160.068 382.179 158.854 383.625 157.469C382.966 155.62 382.285 153.709 381.601 151.793C381.772 151.6 381.94 151.411 382.108 151.217Z" fill="black"/> +<path d="M404.924 168.35C404.722 169.224 404.529 170.064 404.203 171.49C403.584 169.931 403.192 168.945 402.686 167.667C401.744 168.407 400.805 169.139 399.869 169.877C399.657 169.526 399.449 169.176 399.238 168.825C400.17 167.329 401.106 165.829 402.11 164.219C401.162 162.485 400.27 160.855 399.378 159.221C399.471 158.982 399.564 158.743 399.657 158.504C400.45 159.172 401.243 159.841 402.135 160.598C402.073 159.269 402.008 157.958 401.946 156.644C402.148 156.608 402.35 156.572 402.552 156.536C403.037 158.059 403.522 159.582 404.047 161.232C404.793 160.976 405.574 160.71 406.354 160.439C406.516 160.731 406.677 161.028 406.839 161.32C406.152 162.434 405.465 163.549 404.657 164.857C406.018 166.834 407.296 168.695 408.577 170.553C408.462 170.864 408.347 171.181 408.235 171.494C407.231 170.541 406.23 169.59 404.924 168.35Z" fill="black"/> +<path d="M365.883 148.779C366.835 150.816 367.618 152.496 368.405 154.178C368.255 154.403 368.106 154.624 367.957 154.85C367.211 153.971 366.468 153.088 365.569 152.026C365.327 153.281 365.091 154.491 364.857 155.707L364.338 155.473C364.093 154.269 363.847 153.066 363.545 151.59C362.613 152.123 361.758 152.617 360.9 153.11L360.368 152.109C361.273 150.816 362.178 149.524 363.07 148.246C362.358 146.782 361.702 145.432 361.043 144.082C361.941 144.31 362.843 144.54 363.807 144.79C364.065 143.704 364.397 142.309 364.727 140.918C364.944 141.056 365.165 141.19 365.383 141.328C365.529 142.495 365.672 143.666 365.827 144.938C366.682 144.76 367.46 144.603 368.237 144.441C368.371 144.755 368.504 145.074 368.638 145.388C367.758 146.47 366.878 147.553 365.883 148.779Z" fill="black"/> +<path d="M364.324 116.016C363.758 114.622 363.438 113.833 363.117 113.044C362.377 113.164 361.644 113.282 360.907 113.403C360.776 113.114 360.643 112.824 360.509 112.534C361.46 111.361 362.412 110.189 363.497 108.851C362.402 107.32 361.426 105.953 360.447 104.585C360.481 104.22 360.515 103.856 360.546 103.49C361.56 104.198 362.57 104.899 363.748 105.722C363.991 104.505 364.277 103.081 364.56 101.656C364.802 101.785 365.045 101.919 365.287 102.053C365.384 103.358 365.477 104.666 365.58 106.069C366.375 106.034 367.165 105.997 367.955 105.965C368.048 106.224 368.141 106.483 368.238 106.748C367.585 107.704 366.935 108.661 366.208 109.74C366.864 111.137 367.47 112.433 368.076 113.728C367.98 113.894 367.883 114.055 367.79 114.223C367.022 113.612 366.257 113.008 365.312 112.257C365.032 113.32 364.762 114.348 364.324 116.016Z" fill="black"/> +<path d="M382.146 116.801C381.406 115.186 380.694 113.632 379.982 112.077C380.1 111.901 380.218 111.724 380.336 111.548C381.023 112.096 381.71 112.644 382.398 113.192C382.687 112.134 382.979 111.076 383.268 110.018C383.467 110.158 383.666 110.293 383.862 110.431C383.94 111.762 384.017 113.092 384.111 114.654C385.192 114.417 386.159 114.2 387.123 113.986C387.241 114.312 387.359 114.639 387.478 114.966C386.647 115.94 385.817 116.918 384.906 117.987C385.631 119.624 386.293 121.124 386.952 122.624C386.825 122.854 386.697 123.083 386.567 123.312C385.842 122.447 385.115 121.575 384.216 120.499C383.865 121.23 383.498 121.984 383.116 122.774C382.261 121.816 381.421 120.879 380.318 119.641C380.918 118.711 381.499 117.802 382.146 116.801Z" fill="black"/> +<path d="M345.846 101.527C345.15 100.385 344.509 99.3393 343.686 97.9873C345.038 97.8529 346.185 97.7388 347.628 97.5986C348.318 98.2294 349.428 99.24 351.11 100.775C349.994 101.074 349.017 101.336 347.724 101.682C349.235 103.984 349.816 104.872 351.004 106.681C349.62 106.232 348.874 105.995 347.939 105.689C347.743 106.293 347.466 107.151 347.233 107.878C346.247 106.641 345.389 105.562 344.273 104.161C344.684 103.474 345.234 102.552 345.846 101.527Z" fill="black"/> +<path d="M339.386 171.781C342.566 175.728 344.351 170.939 347.254 171.73C346.225 174.341 342.429 176.4 340.803 175.262C340.113 174.779 339.526 173.535 338.895 172.636C339.059 172.355 339.221 172.067 339.386 171.781Z" fill="black"/> +<path d="M478.42 315.709C381.077 271.462 184.544 285.29 105.729 297.734V517.227H226.766C226.766 517.227 715.933 547.719 718.707 517.227L718.753 516.724C721.492 486.618 722.446 476.139 678.914 500.993C569.886 563.239 600.099 371.018 478.42 315.709Z" fill="#67B7C5" stroke="black"/> +<path d="M517.781 149.771L517.815 149.423L518.143 149.793L518.153 149.787L518.837 149.336L518.455 149.13C518.542 148.903 518.627 148.678 518.712 148.454C519.04 147.582 519.359 146.737 519.746 145.939C520.59 144.216 521.814 143.333 523.3 143.075C524.793 142.816 526.579 143.183 528.561 144.025L528.561 144.025C530.415 144.812 532.065 145.927 533.442 147.385C534.816 148.839 535.946 150.662 536.741 152.906C536.975 153.573 537.135 154.254 537.287 155.009C537.337 155.26 537.387 155.523 537.44 155.796C537.541 156.326 537.65 156.894 537.781 157.485L537.94 158.2L538.464 158.077C539.728 157.78 540.971 157.712 542.151 158.081C543.308 158.443 544.425 159.228 545.48 160.707C547.037 162.893 548.525 165.212 548.96 167.964C535.565 163.098 522.446 158.332 509.251 153.533C508.878 150.656 509.782 148.933 511.323 147.745L511.324 147.745C512.16 147.097 513.114 147.24 514.189 147.726C514.738 147.973 515.312 148.307 515.918 148.673C516.031 148.742 516.146 148.812 516.262 148.882C516.756 149.183 517.271 149.496 517.781 149.771Z" fill="#EEEFFC" stroke="black"/> +<path d="M451.996 49.888L451.996 49.888L451.998 49.8879C454.176 49.7984 456.257 51.6373 458.228 54.4272C458.433 54.7187 458.613 55.1434 458.813 55.7242C458.874 55.9024 458.939 56.0989 459.007 56.3052C459.142 56.7179 459.29 57.1699 459.453 57.594L459.7 58.234L460.139 58.0648C460.878 57.7803 461.532 57.849 462.094 58.1905C462.651 58.5289 463.236 59.207 463.796 60.4068L463.796 60.4081C464.354 61.5985 464.715 62.8092 464.221 63.7995C461.202 63.1489 458.156 61.8998 455.068 60.5594C454.647 60.3767 454.225 60.1923 453.802 60.0075C451.226 58.881 448.618 57.7408 446.015 56.911C446.01 56.4451 446.023 56.0766 446.06 55.7839C446.11 55.3896 446.2 55.1682 446.325 55.0353C446.447 54.9045 446.633 54.8265 446.953 54.8133C447.28 54.7999 447.717 54.8549 448.317 54.9643L448.215 54.433L448.674 54.6295C448.672 52.9317 449.019 51.7704 449.597 51.0239C450.174 50.2784 450.998 49.9231 451.996 49.888Z" fill="#EEEFFC" stroke="black"/> +<path d="M391.933 33.3432C391.653 32.8432 391.333 32.3532 391.123 31.8232C390.783 30.9732 390.563 30.0732 390.223 29.2232C389.523 27.4632 388.453 26.2032 386.303 26.4432C385.973 26.4832 385.623 26.2832 384.963 26.1132C385.443 25.5832 385.703 25.0632 386.103 24.9132C388.983 23.8332 390.833 22.0832 390.533 18.7432C390.523 18.5832 390.663 18.4032 390.743 18.2532C390.783 18.1832 390.873 18.1332 391.233 17.8032C391.593 18.3732 392.123 18.8732 392.243 19.4532C392.823 22.1332 394.583 23.4832 397.103 24.1432C397.953 24.3632 398.753 24.7532 399.593 25.6032C398.693 25.8432 397.803 26.1032 396.903 26.3332C393.743 27.1532 392.943 28.0932 392.723 31.2632C392.673 31.9332 392.543 32.5932 392.443 33.2632C392.273 33.2932 392.113 33.3132 391.943 33.3432H391.933ZM391.473 28.3732C393.003 27.1732 394.423 26.0432 395.653 25.0832C394.283 24.0432 392.863 22.9732 391.493 21.9232C390.543 23.0032 389.543 24.1132 388.673 25.1032C389.703 26.2932 390.633 27.3832 391.483 28.3732H391.473Z" fill="black"/> +<path d="M488.143 108.463C485.696 110.357 484.12 112.833 483.413 115.893C481.783 114.973 482.243 113.063 481.353 111.893C480.443 110.693 479.513 109.563 477.543 109.233C478.603 108.273 480.013 107.493 480.623 106.303C481.223 105.133 481.013 103.553 481.393 102.033C483.143 104.633 484.703 107.323 488.143 108.463V108.463ZM480.233 108.833C481.233 109.953 482.073 110.903 482.933 111.853C483.823 110.823 484.693 109.823 485.683 108.683C484.583 107.803 483.523 106.973 482.393 106.063C481.653 107.023 480.993 107.863 480.233 108.833V108.833Z" fill="black"/> +<path d="M501.443 217.263C502.553 217.213 503.813 218.413 503.783 219.493C503.763 220.333 502.463 221.433 501.443 221.483C500.363 221.533 499.393 220.543 499.393 219.383C499.393 218.203 500.243 217.323 501.443 217.273V217.263ZM501.223 220.833C501.793 220.333 502.433 220.033 502.493 219.653C502.553 219.273 502.013 218.813 501.743 218.393C501.353 218.693 500.733 218.933 500.633 219.313C500.533 219.673 500.953 220.193 501.233 220.843L501.223 220.833Z" fill="black"/> +<path d="M426.993 42.3431C426.963 44.0131 426.043 44.8331 424.783 44.7831C423.543 44.7431 422.893 44.0532 422.983 42.8632C423.063 41.7532 424.163 40.7332 425.273 41.0432C426.003 41.2432 426.573 42.0131 426.983 42.3431H426.993ZM424.883 41.7932C424.533 42.3332 424.143 42.7331 424.213 42.8531C424.403 43.1731 424.783 43.4031 425.133 43.5731C425.203 43.6131 425.653 43.1931 425.613 43.0831C425.493 42.7131 425.233 42.3832 424.873 41.7932H424.883Z" fill="black"/> +<path d="M337.763 26.613C337.703 27.873 336.643 28.893 335.443 28.833C334.253 28.773 333.493 27.983 333.603 26.913C333.713 25.763 334.723 24.783 335.743 24.823C336.943 24.873 337.813 25.643 337.773 26.613H337.763ZM335.603 25.643C335.193 26.213 334.803 26.573 334.703 27.003C334.663 27.183 335.403 27.773 335.523 27.713C335.923 27.483 336.293 27.083 336.483 26.663C336.543 26.523 336.023 26.123 335.603 25.643Z" fill="black"/> +<path d="M391.473 28.3731C390.623 27.3831 389.683 26.2931 388.663 25.1031C389.543 24.1131 390.533 23.0031 391.483 21.9231C392.863 22.9631 394.273 24.0431 395.643 25.0831C394.423 26.0431 392.993 27.1731 391.463 28.3731H391.473Z" fill="#F0C3BA"/> +<path d="M480.243 108.833C480.993 107.863 481.653 107.023 482.403 106.063C483.543 106.963 484.593 107.803 485.693 108.683C484.703 109.823 483.833 110.833 482.943 111.853C482.093 110.903 481.243 109.953 480.243 108.833V108.833Z" fill="#EEEFFC"/> +<path d="M290.553 84.1831C287.573 81.6765 285.313 80.2731 283.773 79.9731C284.783 78.9431 285.873 78.1431 286.543 77.0731C287.203 76.0231 287.433 74.7031 287.963 73.2031C289.633 75.5231 291.013 77.8431 294.013 78.5531C291.393 79.5831 290.993 81.8631 290.563 84.1731L290.553 84.1831ZM291.193 78.8031C290.983 79.0131 290.773 79.2131 290.563 79.4231C289.813 78.3231 289.053 77.2331 288.263 76.0831C287.683 77.1531 287.163 78.1031 286.623 79.0931C287.693 79.8931 288.693 80.6331 289.693 81.3831C290.193 80.5231 290.683 79.6631 291.183 78.8031H291.193Z" fill="black"/> +<path d="M291.193 78.8031C290.693 79.6631 290.193 80.5231 289.703 81.3831C288.693 80.6331 287.693 79.8831 286.633 79.0931C287.173 78.1031 287.693 77.1531 288.273 76.0831C289.063 77.2331 289.813 78.3231 290.573 79.4231C290.783 79.2131 290.993 79.0131 291.203 78.8031H291.193Z" fill="white"/> +<path d="M501.223 220.843C500.943 220.193 500.523 219.683 500.623 219.313C500.733 218.933 501.343 218.693 501.733 218.393C502.013 218.813 502.543 219.283 502.483 219.653C502.423 220.033 501.793 220.333 501.213 220.833L501.223 220.843Z" fill="white"/> +<path d="M424.883 41.7932C425.233 42.3832 425.493 42.7032 425.623 43.0832C425.663 43.1932 425.213 43.6132 425.143 43.5732C424.793 43.4032 424.423 43.1632 424.223 42.8532C424.153 42.7332 424.543 42.3332 424.893 41.7932H424.883Z" fill="white"/> +<path d="M335.603 25.6332C336.023 26.1032 336.543 26.5032 336.483 26.6532C336.303 27.0732 335.923 27.4732 335.523 27.7032C335.403 27.7732 334.663 27.1732 334.703 26.9932C334.803 26.5732 335.193 26.2132 335.603 25.6332Z" fill="white"/> +<path d="M258.798 301.522C260.386 303.829 260.927 304.467 262.654 304.867C265.365 305.495 268.038 306.405 270.903 306.221C272.136 306.142 273.385 306.364 274.627 306.368C276.88 306.375 279.173 306.711 281.575 305.707C281.461 304.808 281.349 303.94 281.222 302.95C273.59 302.464 266.203 301.994 258.798 301.522ZM311.984 301.326C310.194 301.524 308.481 301.792 306.759 301.89C301.18 302.203 295.599 302.471 290.017 302.717C288.468 302.784 286.913 302.698 285.361 302.705C284.507 302.709 283.672 302.812 283.371 303.812C283.071 304.81 283.525 305.866 284.667 306.275C286.016 306.759 287.435 307.256 288.845 307.329C295.158 307.652 301.458 307.635 307.683 306.213C311.088 305.434 311.314 305.294 311.984 301.326ZM270.235 129.835C270.162 130.35 270.019 130.746 270.065 131.119C270.43 134.022 271.567 136.468 274.102 138.164C274.985 138.755 276.119 139.201 276.57 140.572C274.101 141.302 271.809 140.453 269.124 140.422C269.771 141.399 270.114 142.155 270.664 142.705C272.012 144.052 273.647 144.934 275.554 145.241C278.032 145.641 279.884 146.994 281.564 148.818C282.945 150.319 283.724 152.082 284.562 153.862C284.831 154.429 285.333 154.886 286.012 155.758C286.314 154.693 286.542 154.132 286.628 153.551C287.338 148.651 286.447 143.974 284.001 139.747C282.252 136.724 280.255 133.72 276.732 132.34C274.623 131.512 272.503 130.709 270.235 129.835ZM367.836 219.996C369.044 224.944 370.235 229.846 371.447 234.743C371.565 235.22 371.811 235.665 371.989 236.101C377.702 234.953 380.543 231.89 380.958 226.44C381.068 225 381.111 223.524 380.917 222.101C380.726 220.692 380.196 219.33 379.797 217.886C380.113 217.457 380.454 217.067 380.711 216.628C380.993 216.146 381.199 215.619 381.589 214.788C380.428 214.788 379.58 214.597 378.859 214.821C374.998 216.016 371.29 217.567 367.836 219.996ZM259.556 305.582C259.033 309.088 259.884 312.239 262.521 314.364C265.609 316.853 269.187 318.603 273.409 318.296C275.791 318.123 278.184 318.093 280.564 317.898C281.324 317.835 282.057 317.447 282.609 317.271C282.44 314.055 282.291 311.191 282.143 308.342C278.217 308.172 274.488 308.238 270.821 307.791C267.137 307.341 263.516 306.382 259.556 305.582ZM312.004 306.574C302.892 309.308 293.776 309.563 284.458 308.219C284.033 310.775 284.207 313.047 284.506 315.285C284.603 316.019 285.163 316.927 285.784 317.313C287.352 318.283 288.994 319.36 290.751 319.761C299.008 321.643 306.852 320.186 314.177 316.091C318.053 313.924 321.289 311.073 322.078 306.08C319.498 304.33 316.823 302.982 313.462 303.953C312.937 304.897 312.441 305.79 312.004 306.574ZM333.913 179.575C330.658 182.683 326.799 184.513 323.135 186.624C323.135 187.168 323.064 187.59 323.146 187.981C324.383 193.918 324.683 199.935 324.695 205.974C324.704 210.549 324.658 215.127 324.663 219.702C324.665 220.489 324.804 221.275 324.895 222.228C330.31 220.867 335.159 218.899 339.519 216.07C341.824 204.896 338.791 187.081 333.913 179.575ZM255.294 157.63C254.634 159.567 255.178 160.877 256.373 161.88C257.399 162.74 258.478 163.657 259.695 164.144C262.289 165.179 264.941 166.146 267.653 166.793C275.523 168.671 283.535 168.673 291.53 167.967C296.914 167.492 302.269 166.582 307.254 164.337C309.03 163.537 310.787 162.513 312.254 161.245C314.948 158.919 314.637 157.014 311.526 155.26C305.172 151.679 298.313 149.783 291.074 149.187C290.487 149.14 289.883 149.311 289.21 149.391C288.359 152.032 289.205 155.194 286.661 157.118C284.893 157.014 283.932 156.166 283.313 154.644C282.732 153.214 281.943 151.858 281.146 150.529C280.571 149.569 279.665 149.198 278.487 149.307C270.033 150.09 262.305 152.844 255.294 157.63ZM365.551 219.281C364.511 219.755 363.928 219.965 363.398 220.268C357.681 223.551 351.48 225.621 345.248 227.646C340.708 229.121 336.198 230.705 331.733 232.396C327.364 234.052 323.067 235.897 318.744 237.675C315.283 239.099 311.828 240.545 308.805 242.81C308.062 243.365 307.212 243.913 306.743 244.67C305.094 247.331 303.738 250.133 303.462 253.333C303.122 257.277 304.604 259.789 308.325 260.997C309.976 261.533 311.772 261.769 313.517 261.849C319.727 262.139 325.708 260.75 331.593 258.986C344.296 255.178 356.236 249.606 367.741 243.064C368.54 242.609 369.271 242.036 370.217 241.395C370.108 233.795 368.104 226.672 365.551 219.281ZM235.483 181.592C236.532 181.947 237.276 182.29 238.055 182.448C245.988 184.056 253.904 185.762 261.87 187.179C269.027 188.453 276.247 189.286 283.545 189.335C289.893 189.378 296.202 189.015 302.49 188.148C309.383 187.198 316.253 186.096 322.98 184.299C327.122 183.192 330.387 180.746 332.969 177.043C328.751 169.273 323.186 162.887 315.84 158.029C313.129 162.997 311.511 164.373 305.915 166.416C304.552 166.914 303.164 167.369 301.756 167.719C290.171 170.594 278.526 170.723 266.859 168.298C263.807 167.664 260.833 166.693 258.101 165.126C256.373 164.134 254.786 162.981 253.84 161.137C253.577 160.623 253.23 160.151 252.781 159.434C244.967 165.349 239.358 172.606 235.483 181.592ZM322.873 222.931C324.128 216.431 322.84 192.008 321.009 187.086C320.53 187.086 320.013 187.031 319.51 187.094C310.646 188.185 301.8 189.459 292.914 190.323C288.284 190.773 283.574 190.726 278.919 190.498C274.364 190.273 269.839 189.491 265.294 189.026C256.503 188.126 247.851 186.452 239.242 184.516C237.843 184.201 236.465 183.79 234.831 183.357C230.41 193.997 229.771 204.906 230.91 215.991C237.058 219.801 243.584 221.826 250.193 223.683C257.643 225.776 265.394 225.905 272.944 227.318C273.248 227.374 273.567 227.354 273.878 227.36C278.451 227.437 283.023 227.577 287.595 227.568C291.127 227.561 294.673 227.538 298.186 227.216C302.525 226.819 306.836 226.112 311.156 225.523C315.167 224.977 319.102 224.103 322.873 222.931ZM339.354 227.314C339.774 222.981 339.656 219.837 338.958 218.323C338.041 218.784 337.138 219.265 336.213 219.699C334.71 220.405 333.229 221.178 331.675 221.752C324.739 224.315 317.662 226.348 310.282 227.168C306.979 227.535 303.685 227.976 300.378 228.283C297.59 228.542 294.789 228.686 291.992 228.82C291.067 228.864 290.134 228.666 289.204 228.671C285.058 228.697 280.905 228.908 276.765 228.758C273.143 228.627 269.538 228.057 265.918 227.762C254.316 226.812 243.151 224.262 232.761 218.816C232.416 218.635 232.017 218.554 231.483 218.374C231.363 218.91 231.219 219.283 231.208 219.66C231.03 225.268 230.842 230.877 230.722 236.486C230.678 238.563 230.785 240.643 230.863 242.721C230.947 244.899 231.148 247.076 231.178 249.256C231.246 254.35 231.272 259.445 231.259 264.541C231.242 271.046 232.82 277.111 236.286 282.619C241.929 291.587 250.031 297.217 260.25 299.784C262.236 300.283 264.345 300.366 266.405 300.482C270.036 300.685 273.674 300.778 277.309 300.893C281.241 301.02 285.149 301.481 289.113 301.159C294.081 300.756 299.077 300.602 304.062 300.548C306.375 300.523 308.634 300.36 310.835 299.716C325.593 295.388 335.484 286.258 338.648 270.838C339.435 267.005 339.373 262.998 339.678 259.07C339.689 258.92 339.46 258.752 339.25 258.468C338.321 258.765 337.341 259.074 336.362 259.388C330.117 261.389 323.808 263.096 317.221 263.526C313.974 263.737 310.762 263.525 307.678 262.468C303.206 260.934 301.052 257.343 302.082 252.698C302.635 250.194 303.845 247.84 304.691 245.396C305.335 243.536 306.582 242.227 308.144 241.115C311.137 238.984 314.48 237.556 317.854 236.195C324.789 233.399 331.738 230.638 338.676 227.853C338.861 227.779 339 227.601 339.354 227.314ZM276.507 147.644C275.515 147.274 274.564 146.948 273.636 146.568C271.251 145.59 269.373 144.015 268.156 141.721C267.435 140.363 267.417 139.8 267.874 137.736C269.513 138.186 270.785 139.872 272.96 139.238C272.535 138.754 272.207 138.326 271.827 137.952C269.787 135.947 269.016 133.383 268.654 130.662C268.514 129.62 268.659 128.586 269.603 127.627C270.088 127.837 270.629 128.054 271.156 128.299C272.096 128.737 272.987 129.341 273.972 129.615C281.843 131.803 285.701 137.693 288.181 144.88C288.45 145.657 288.644 146.46 288.908 147.373C291.597 147.786 294.257 148.124 296.891 148.611C305.335 150.173 312.949 153.642 319.555 159.106C332.283 169.638 339.919 183.036 341.867 199.56C342.569 205.518 342.295 211.386 340.662 217.177C340.467 217.868 340.3 218.626 340.377 219.325C340.647 221.772 341.03 224.207 341.397 226.836C349.975 224.599 357.967 221.478 365.62 217.469C366.255 217.808 366.789 218.092 367.356 218.395C367.625 218.301 367.928 218.227 368.2 218.094C371.09 216.681 373.93 215.159 376.878 213.885C378.071 213.37 379.506 213.153 380.81 213.219C382.834 213.323 383.516 214.667 382.674 216.473C382.414 217.031 382.017 217.526 381.603 218.173C381.828 218.894 382.078 219.674 382.311 220.457C383.366 224.009 382.876 227.451 381.537 230.821C380.646 233.063 378.915 234.534 376.885 235.684C375.263 236.602 373.6 237.451 371.816 238.406C371.753 239.761 371.693 241.09 371.618 242.699C368.014 244.704 364.372 246.915 360.568 248.799C355.076 251.52 349.51 254.105 343.884 256.535C341.968 257.363 341.055 258.428 341.154 260.474C341.209 261.614 341.113 262.761 341.108 263.905C341.081 269.501 339.823 274.755 337.343 279.818C335.544 283.491 333.474 286.933 330.767 289.957C326.743 294.448 321.828 297.693 316.203 299.871C315.672 300.075 315.212 300.488 314.763 300.857C314.663 300.94 314.748 301.248 314.748 301.609C318 302.194 321.192 303.035 323.744 305.756C323.28 308.596 322.618 311.421 320.437 313.54C318.425 315.495 316.167 317.133 313.593 318.374C306.097 321.987 298.282 322.865 290.195 321.215C287.989 320.763 285.894 319.761 283.319 318.861C280.231 319.535 276.621 319.787 272.982 319.848C268.322 319.927 264.406 317.964 261.109 315.044C258.461 312.698 257.252 309.416 257.959 305.716C258.056 305.214 258.229 304.726 258.377 304.193C257.064 301.857 255.363 300.262 252.808 299.102C239.062 292.868 231.496 281.937 229.681 267.083C229.232 263.403 229.821 259.607 229.774 255.866C229.674 247.863 229.395 239.861 229.34 231.858C229.311 227.599 229.614 223.338 229.725 219.077C229.753 218.048 229.736 216.988 229.534 215.984C227.185 204.276 228.641 192.993 233.455 182.147C237.752 172.465 244.036 164.278 252.587 157.941C258.855 153.296 265.809 150.128 273.485 148.61C274.401 148.429 275.316 148.244 276.229 148.05C276.286 148.037 276.32 147.923 276.507 147.644Z" fill="black"/> +<path d="M339.354 227.314C339 227.601 338.86 227.78 338.676 227.853C331.737 230.637 324.788 233.399 317.854 236.195C314.48 237.556 311.137 238.983 308.144 241.115C306.581 242.227 305.335 243.535 304.691 245.396C303.844 247.84 302.635 250.194 302.082 252.698C301.053 257.343 303.206 260.934 307.678 262.468C310.762 263.525 313.975 263.737 317.221 263.526C323.807 263.096 330.117 261.389 336.363 259.387C337.341 259.074 338.321 258.765 339.25 258.468C339.459 258.752 339.689 258.92 339.678 259.07C339.373 262.998 339.434 267.005 338.648 270.837C335.485 286.258 325.593 295.388 310.834 299.716C308.634 300.361 306.375 300.523 304.062 300.548C299.076 300.602 294.081 300.756 289.113 301.159C285.149 301.481 281.241 301.02 277.309 300.895C273.674 300.777 270.036 300.685 266.405 300.482C264.345 300.366 262.236 300.283 260.251 299.784C250.032 297.218 241.93 291.587 236.287 282.619C232.821 277.111 231.241 271.045 231.259 264.541C231.272 259.445 231.246 254.35 231.178 249.256C231.148 247.077 230.947 244.899 230.864 242.721C230.785 240.643 230.678 238.563 230.722 236.487C230.841 230.877 231.03 225.268 231.207 219.66C231.22 219.283 231.364 218.91 231.482 218.374C232.017 218.554 232.416 218.635 232.761 218.816C243.151 224.262 254.315 226.812 265.918 227.762C269.538 228.057 273.143 228.627 276.765 228.759C280.905 228.909 285.058 228.697 289.204 228.671C290.134 228.665 291.067 228.864 291.992 228.82C294.789 228.686 297.59 228.542 300.378 228.282C303.685 227.976 306.979 227.535 310.282 227.169C317.663 226.348 324.738 224.315 331.675 221.752C333.229 221.178 334.71 220.405 336.213 219.699C337.137 219.265 338.041 218.784 338.958 218.323C339.656 219.836 339.774 222.981 339.354 227.314Z" fill="white"/> +<path d="M322.873 222.932C319.102 224.104 315.168 224.978 311.157 225.524C306.836 226.111 302.525 226.818 298.185 227.216C294.673 227.539 291.127 227.561 287.596 227.568C283.023 227.576 278.451 227.438 273.878 227.36C273.567 227.355 273.249 227.374 272.945 227.317C265.394 225.905 257.643 225.777 250.193 223.683C243.584 221.825 237.057 219.802 230.911 215.991C229.772 204.906 230.411 193.998 234.831 183.357C236.465 183.789 237.843 184.201 239.242 184.515C247.85 186.451 256.503 188.126 265.294 189.025C269.839 189.491 274.364 190.273 278.919 190.497C283.573 190.727 288.283 190.772 292.914 190.323C301.8 189.46 310.646 188.184 319.51 187.093C320.012 187.032 320.529 187.085 321.009 187.085C322.84 192.009 324.128 216.432 322.873 222.932Z" fill="white"/> +<path d="M235.483 181.592C239.358 172.605 244.966 165.348 252.78 159.434C253.23 160.15 253.576 160.623 253.84 161.137C254.786 162.982 256.373 164.133 258.101 165.126C260.832 166.694 263.806 167.664 266.858 168.298C278.525 170.724 290.17 170.593 301.755 167.719C303.164 167.37 304.551 166.914 305.915 166.416C311.511 164.373 313.129 162.996 315.84 158.028C323.185 162.887 328.749 169.274 332.967 177.043C330.387 180.745 327.121 183.192 322.979 184.298C316.251 186.095 309.383 187.199 302.488 188.149C296.201 189.015 289.892 189.378 283.544 189.334C276.247 189.286 269.027 188.454 261.87 187.179C253.903 185.761 245.988 184.056 238.055 182.449C237.274 182.291 236.532 181.948 235.483 181.592Z" fill="white"/> +<path d="M365.55 219.281C368.103 226.672 370.107 233.795 370.216 241.394C369.27 242.036 368.54 242.609 367.741 243.064C356.236 249.606 344.295 255.178 331.592 258.986C325.707 260.75 319.726 262.139 313.516 261.849C311.773 261.769 309.977 261.533 308.325 260.997C304.603 259.789 303.121 257.277 303.461 253.333C303.737 250.133 305.093 247.331 306.743 244.67C307.212 243.913 308.063 243.365 308.804 242.81C311.828 240.545 315.282 239.099 318.743 237.675C323.066 235.897 327.364 234.052 331.733 232.396C336.197 230.705 340.707 229.121 345.247 227.646C351.479 225.621 357.681 223.551 363.399 220.268C363.927 219.965 364.51 219.755 365.55 219.281Z" fill="white"/> +<path d="M255.293 157.63C262.304 152.844 270.033 150.09 278.487 149.308C279.664 149.199 280.571 149.569 281.146 150.53C281.942 151.858 282.732 153.214 283.313 154.644C283.932 156.166 284.894 157.014 286.66 157.119C289.205 155.195 288.358 152.032 289.21 149.391C289.882 149.312 290.487 149.139 291.073 149.187C298.312 149.783 305.173 151.68 311.526 155.261C314.636 157.014 314.948 158.92 312.253 161.246C310.786 162.513 309.03 163.537 307.254 164.337C302.27 166.583 296.913 167.492 291.529 167.967C283.534 168.673 275.522 168.671 267.653 166.793C264.941 166.146 262.289 165.179 259.695 164.144C258.477 163.658 257.398 162.74 256.373 161.879C255.177 160.877 254.634 159.567 255.293 157.63Z" fill="white"/> +<path d="M333.913 179.576C338.791 187.081 341.824 204.897 339.519 216.069C335.159 218.899 330.309 220.867 324.895 222.227C324.804 221.276 324.664 220.488 324.663 219.702C324.658 215.126 324.704 210.55 324.695 205.974C324.683 199.934 324.383 193.918 323.146 187.982C323.064 187.591 323.135 187.168 323.135 186.625C326.799 184.513 330.658 182.682 333.913 179.576Z" fill="#CAE4E4"/> +<path d="M312.004 306.575C312.441 305.79 312.937 304.897 313.462 303.953C316.823 302.983 319.498 304.33 322.077 306.079C321.289 311.073 318.053 313.925 314.177 316.091C306.852 320.187 299.007 321.644 290.75 319.76C288.994 319.36 287.351 318.284 285.784 317.313C285.161 316.927 284.603 316.02 284.504 315.284C284.207 313.046 284.033 310.776 284.458 308.218C293.774 309.563 302.891 309.307 312.004 306.575Z" fill="white"/> +<path d="M259.555 305.582C263.516 306.381 267.138 307.341 270.82 307.792C274.487 308.238 278.216 308.172 282.143 308.341C282.291 311.191 282.441 314.055 282.608 317.27C282.056 317.447 281.323 317.836 280.563 317.898C278.184 318.094 275.79 318.123 273.408 318.296C269.187 318.603 265.608 316.854 262.52 314.364C259.884 312.239 259.033 309.088 259.555 305.582Z" fill="white"/> +<path d="M367.835 219.996C371.29 217.567 374.998 216.017 378.859 214.821C379.58 214.597 380.428 214.788 381.589 214.788C381.198 215.619 380.993 216.146 380.711 216.628C380.454 217.067 380.113 217.457 379.797 217.886C380.195 219.33 380.724 220.692 380.916 222.101C381.11 223.524 381.066 225 380.957 226.44C380.543 231.89 377.701 234.953 371.987 236.101C371.811 235.665 371.565 235.22 371.446 234.743C370.235 229.847 369.044 224.944 367.835 219.996Z" fill="white"/> +<path d="M270.235 129.835C272.502 130.708 274.622 131.513 276.733 132.34C280.254 133.72 282.251 136.724 284 139.747C286.447 143.973 287.338 148.651 286.627 153.55C286.542 154.132 286.314 154.692 286.012 155.758C285.332 154.887 284.83 154.43 284.562 153.863C283.723 152.083 282.945 150.32 281.563 148.818C279.884 146.993 278.032 145.642 275.554 145.242C273.646 144.935 272.012 144.053 270.664 142.705C270.114 142.156 269.77 141.4 269.123 140.421C271.808 140.453 274.1 141.302 276.57 140.572C276.119 139.202 274.986 138.755 274.101 138.163C271.567 136.467 270.429 134.022 270.065 131.119C270.018 130.747 270.161 130.35 270.235 129.835Z" fill="white"/> +<path d="M311.984 301.326C311.314 305.294 311.089 305.435 307.683 306.213C301.458 307.634 295.158 307.651 288.846 307.329C287.435 307.257 286.017 306.759 284.666 306.276C283.525 305.867 283.072 304.81 283.372 303.812C283.673 302.812 284.507 302.71 285.361 302.705C286.913 302.698 288.468 302.785 290.018 302.716C295.599 302.472 301.18 302.203 306.758 301.89C308.481 301.793 310.194 301.524 311.984 301.326Z" fill="white"/> +<path d="M258.798 301.522C266.203 301.994 273.589 302.464 281.222 302.95C281.349 303.94 281.46 304.808 281.575 305.707C279.172 306.711 276.88 306.375 274.627 306.368C273.385 306.364 272.136 306.142 270.903 306.221C268.037 306.405 265.365 305.496 262.653 304.867C260.927 304.467 260.386 303.829 258.798 301.522Z" fill="white"/> +<path d="M221.908 263.253C122.036 201.082 89.1337 187.67 0.0472646 184.896L0.0472644 419.958L0.0472604 522.747L559.194 522.748C516.33 490.079 523.684 489.993 411.57 484.079C271.428 476.687 346.749 340.968 221.908 263.253Z" fill="#3DA3B4" stroke="black"/> +<path d="M162.157 105.395C166.288 105.709 172.758 106.077 181.565 106.499L181.585 106.5L181.605 106.498C181.859 106.47 182.069 106.431 182.225 106.378C182.303 106.351 182.38 106.317 182.445 106.269C182.509 106.221 182.581 106.145 182.604 106.033C182.628 105.917 182.59 105.817 182.543 105.746C182.497 105.677 182.434 105.62 182.37 105.572C182.241 105.475 182.056 105.379 181.828 105.281L181.818 105.277L181.809 105.274C175.183 103.05 167.748 101.337 159.506 100.136L159.506 100.136L159.501 100.136C158.516 100.012 157.705 100.096 157.101 100.431C156.484 100.773 156.121 101.355 155.997 102.133L155.996 102.141L155.995 102.149C155.951 102.623 156.059 103.057 156.321 103.44C156.58 103.818 156.979 104.133 157.498 104.393C158.531 104.911 160.093 105.239 162.157 105.395ZM162.157 105.395L162.176 105.146M162.157 105.395C162.157 105.395 162.157 105.395 162.157 105.395L162.176 105.146M162.176 105.146C158.066 104.835 156.088 103.844 156.244 102.173C156.473 100.738 157.548 100.142 159.47 100.384C167.7 101.583 175.12 103.292 181.729 105.511C182.62 105.893 182.569 106.139 181.577 106.249C172.771 105.827 166.304 105.46 162.176 105.146ZM182.079 101.091L182.123 101.11L182.17 101.11C182.294 101.112 182.417 101.1 182.522 101.058C182.635 101.013 182.744 100.924 182.782 100.778C182.817 100.647 182.782 100.52 182.737 100.421C182.691 100.317 182.619 100.209 182.53 100.098L182.516 100.08L182.499 100.066C177.534 95.7287 173.556 92.6008 170.569 90.6889L170.557 90.6812L170.544 90.675C168.946 89.8908 167.598 89.7496 166.573 90.3932L166.529 90.4205L166.5 90.4626C166.2 90.8958 166.024 91.3322 165.996 91.7694C165.966 92.2098 166.087 92.6302 166.345 93.0244L166.351 93.0339L166.358 93.0429C166.683 93.4522 167.26 93.9184 168.065 94.4418C168.875 94.9686 169.934 95.5654 171.24 96.2319C173.852 97.5651 177.466 99.1851 182.079 101.091ZM185.083 96.3234L185.103 96.337L185.125 96.3464C185.257 96.4028 185.393 96.4434 185.521 96.4403C185.664 96.4369 185.808 96.3767 185.893 96.2302C185.964 96.106 185.967 95.9596 185.952 95.8331C185.936 95.6998 185.895 95.5463 185.835 95.3772L185.829 95.3591L185.819 95.3421C183.587 91.2015 181.827 88.3214 180.543 86.7174L180.543 86.7173L180.539 86.7134C179.939 85.9945 179.054 85.7685 177.957 85.9707L177.865 85.9876L177.807 86.0604C177.265 86.7383 177.089 87.4418 177.366 88.1386L177.371 88.1504L177.376 88.1616C178.951 91.1907 181.525 93.9098 185.083 96.3234ZM180.157 110.204L180.111 110.192L180.063 110.198C175.11 110.812 171.196 111.414 168.323 112.007C166.888 112.303 165.708 112.597 164.787 112.89C163.872 113.181 163.192 113.477 162.771 113.786L162.761 113.793L162.753 113.8C162.4 114.112 162.172 114.485 162.083 114.918C161.995 115.347 162.048 115.814 162.222 116.311L162.239 116.36L162.274 116.398C163.092 117.29 164.429 117.512 166.178 117.18L166.192 117.177L166.206 117.173C169.593 116.122 174.258 114.162 180.196 111.298L180.217 111.289L180.235 111.275C180.35 111.192 180.448 111.107 180.52 111.019C180.59 110.935 180.657 110.822 180.659 110.687C180.66 110.536 180.579 110.421 180.482 110.347C180.391 110.279 180.276 110.235 180.157 110.204ZM181.449 115.803L181.425 115.806L181.402 115.814C177.332 117.197 174.129 119.136 171.807 121.638L171.798 121.648L171.79 121.658C171.339 122.256 171.321 122.981 171.664 123.779L171.701 123.864L171.785 123.905C172.789 124.391 173.702 124.408 174.472 123.874L174.472 123.874L174.476 123.871C176.14 122.666 178.601 120.356 181.852 116.956L181.866 116.942L181.877 116.926C181.979 116.779 182.06 116.642 182.111 116.518C182.159 116.4 182.194 116.258 182.159 116.119C182.116 115.955 181.993 115.859 181.857 115.818C181.734 115.781 181.591 115.784 181.449 115.803Z" fill="white" stroke="black" stroke-width="0.5"/> +<mask id="path-50-outside-1_317_4" maskUnits="userSpaceOnUse" x="190.845" y="105.9" width="68" height="34" fill="black"> +<rect fill="white" x="190.845" y="105.9" width="68" height="34"/> +<path d="M195.803 119.071C195.393 119.071 195.123 118.872 194.994 118.473L191.933 108.43C191.875 108.219 191.845 108.031 191.845 107.867C191.845 107.644 191.928 107.463 192.092 107.322C192.256 107.169 192.455 107.093 192.69 107.093C193.124 107.093 193.405 107.298 193.534 107.709L195.838 115.29L198.301 107.691C198.43 107.293 198.711 107.093 199.145 107.093C199.567 107.093 199.837 107.298 199.954 107.709L202.047 115.307L204.861 107.515C205.014 107.105 205.295 106.9 205.705 106.9C205.94 106.9 206.139 106.976 206.303 107.128C206.468 107.269 206.55 107.451 206.55 107.674C206.55 107.873 206.52 108.061 206.462 108.237L202.751 118.508C202.692 118.684 202.592 118.825 202.452 118.931C202.311 119.024 202.147 119.071 201.959 119.071C201.502 119.071 201.22 118.86 201.115 118.438L199.057 111.016L196.647 118.473C196.577 118.696 196.471 118.854 196.331 118.948C196.202 119.03 196.026 119.071 195.803 119.071Z"/> +<path d="M210.768 119.071C209.936 119.071 209.185 118.895 208.517 118.544C207.86 118.18 207.344 117.67 206.969 117.013C206.594 116.345 206.406 115.559 206.406 114.656C206.406 113.918 206.571 113.214 206.899 112.546C207.239 111.866 207.708 111.32 208.306 110.91C208.916 110.5 209.608 110.294 210.381 110.294C211.097 110.294 211.706 110.441 212.211 110.734C212.727 111.016 213.114 111.356 213.372 111.754C213.63 112.153 213.758 112.511 213.758 112.827C213.758 112.98 213.712 113.126 213.618 113.267C213.524 113.408 213.389 113.531 213.213 113.636L208.499 116.345C208.664 116.708 208.951 116.99 209.361 117.189C209.783 117.389 210.252 117.488 210.768 117.488C211.448 117.488 212.029 117.236 212.51 116.732C212.697 116.544 212.891 116.45 213.09 116.45C213.325 116.45 213.518 116.527 213.671 116.679C213.835 116.832 213.917 117.019 213.917 117.242C213.917 117.43 213.846 117.605 213.706 117.77C213.377 118.133 212.938 118.444 212.387 118.702C211.847 118.948 211.308 119.071 210.768 119.071ZM212.017 112.616C211.876 112.37 211.677 112.188 211.419 112.071C211.161 111.942 210.856 111.877 210.505 111.877C209.836 111.877 209.273 112.106 208.816 112.563C208.37 113.021 208.112 113.584 208.042 114.252C208.019 114.428 208.007 114.639 208.007 114.885L212.017 112.616Z"/> +<path d="M216.453 119.071C216.23 119.071 216.031 118.995 215.855 118.843C215.691 118.678 215.609 118.491 215.609 118.28V108.078C215.609 107.844 215.685 107.65 215.838 107.498C216.002 107.334 216.207 107.252 216.453 107.252C216.676 107.252 216.87 107.334 217.034 107.498C217.21 107.662 217.298 107.855 217.298 108.078V118.28C217.298 118.491 217.21 118.678 217.034 118.843C216.858 118.995 216.664 119.071 216.453 119.071Z"/> +<path d="M223.037 119.071C222.334 119.071 221.689 118.901 221.102 118.561C220.516 118.221 220.053 117.758 219.713 117.172C219.373 116.574 219.203 115.923 219.203 115.219C219.203 114.422 219.391 113.642 219.766 112.88C220.141 112.118 220.633 111.496 221.243 111.016C221.853 110.535 222.498 110.294 223.178 110.294C223.799 110.294 224.298 110.394 224.673 110.593C225.06 110.781 225.435 111.086 225.799 111.508C225.975 111.696 226.063 111.901 226.063 112.124C226.063 112.346 225.98 112.54 225.816 112.704C225.652 112.857 225.453 112.933 225.218 112.933C225.066 112.933 224.943 112.909 224.849 112.862C224.767 112.815 224.667 112.722 224.55 112.581C224.351 112.358 224.151 112.188 223.952 112.071C223.764 111.954 223.512 111.895 223.196 111.895C222.832 111.895 222.463 112.053 222.087 112.37C221.724 112.686 221.425 113.103 221.19 113.619C220.968 114.123 220.856 114.645 220.856 115.184C220.856 115.653 220.956 116.064 221.155 116.415C221.366 116.755 221.636 117.019 221.964 117.207C222.293 117.383 222.639 117.471 223.002 117.471C223.471 117.471 223.852 117.383 224.145 117.207C224.45 117.031 224.72 116.791 224.954 116.486C225.177 116.193 225.429 116.046 225.711 116.046C225.934 116.046 226.127 116.128 226.291 116.292C226.455 116.445 226.537 116.621 226.537 116.82C226.537 117.054 226.473 117.254 226.344 117.418C226.004 117.875 225.541 118.268 224.954 118.596C224.368 118.913 223.729 119.071 223.037 119.071Z"/> +<path d="M231.088 119.071C230.42 119.071 229.804 118.889 229.241 118.526C228.69 118.162 228.256 117.67 227.94 117.049C227.623 116.415 227.465 115.729 227.465 114.991C227.465 114.158 227.652 113.384 228.028 112.669C228.403 111.942 228.907 111.367 229.54 110.945C230.174 110.511 230.854 110.294 231.581 110.294C232.249 110.294 232.859 110.482 233.41 110.857C233.973 111.232 234.412 111.731 234.729 112.352C235.057 112.974 235.221 113.642 235.221 114.357C235.221 115.202 235.034 115.987 234.659 116.714C234.283 117.43 233.773 118.004 233.128 118.438C232.495 118.86 231.815 119.071 231.088 119.071ZM231.088 117.488C231.499 117.488 231.891 117.348 232.267 117.066C232.642 116.785 232.941 116.409 233.164 115.94C233.386 115.471 233.498 114.973 233.498 114.445C233.498 114 233.41 113.584 233.234 113.197C233.058 112.798 232.818 112.481 232.513 112.247C232.208 112.001 231.874 111.877 231.51 111.877C231.1 111.877 230.713 112.024 230.349 112.317C229.998 112.599 229.716 112.98 229.505 113.46C229.294 113.929 229.189 114.434 229.189 114.973C229.189 115.501 229.276 115.952 229.452 116.327C229.64 116.703 229.874 116.99 230.156 117.189C230.449 117.389 230.76 117.488 231.088 117.488Z"/> +<path d="M237.958 119.071C237.736 119.071 237.536 118.989 237.36 118.825C237.196 118.661 237.114 118.467 237.114 118.245V111.139C237.114 110.916 237.196 110.722 237.36 110.558C237.536 110.382 237.736 110.294 237.958 110.294C238.193 110.294 238.392 110.382 238.556 110.558C238.721 110.722 238.803 110.916 238.803 111.139V112.37C239.53 110.986 240.497 110.294 241.705 110.294C242.186 110.294 242.602 110.435 242.954 110.717C243.317 110.998 243.575 111.42 243.727 111.983C244.478 110.857 245.393 110.294 246.471 110.294C247.198 110.294 247.773 110.576 248.195 111.139C248.617 111.69 248.828 112.522 248.828 113.636V118.245C248.828 118.467 248.74 118.661 248.564 118.825C248.4 118.989 248.207 119.071 247.984 119.071C247.749 119.071 247.55 118.989 247.386 118.825C247.222 118.661 247.14 118.467 247.14 118.245V113.531C247.14 112.991 247.063 112.587 246.911 112.317C246.759 112.047 246.518 111.913 246.19 111.913C245.85 111.913 245.51 112.047 245.17 112.317C244.83 112.587 244.548 112.956 244.325 113.425C244.114 113.894 244.009 114.41 244.009 114.973V118.245C244.009 118.479 243.927 118.678 243.763 118.843C243.598 118.995 243.399 119.071 243.165 119.071C242.942 119.071 242.742 118.989 242.567 118.825C242.402 118.661 242.32 118.467 242.32 118.245V113.531C242.32 112.968 242.25 112.558 242.109 112.3C241.98 112.042 241.763 111.913 241.459 111.913C241.083 111.913 240.714 112.065 240.35 112.37C239.987 112.663 239.67 113.021 239.401 113.443C239.19 113.795 239.031 114.146 238.926 114.498C238.832 114.85 238.785 115.237 238.785 115.659L238.803 118.245C238.803 118.467 238.721 118.661 238.556 118.825C238.392 118.989 238.193 119.071 237.958 119.071Z"/> +<path d="M254.912 119.071C254.08 119.071 253.329 118.895 252.661 118.544C252.004 118.18 251.488 117.67 251.113 117.013C250.738 116.345 250.55 115.559 250.55 114.656C250.55 113.918 250.714 113.214 251.043 112.546C251.383 111.866 251.852 111.32 252.45 110.91C253.059 110.5 253.751 110.294 254.525 110.294C255.24 110.294 255.85 110.441 256.354 110.734C256.87 111.016 257.257 111.356 257.515 111.754C257.773 112.153 257.902 112.511 257.902 112.827C257.902 112.98 257.855 113.126 257.762 113.267C257.668 113.408 257.533 113.531 257.357 113.636L252.643 116.345C252.807 116.708 253.095 116.99 253.505 117.189C253.927 117.389 254.396 117.488 254.912 117.488C255.592 117.488 256.173 117.236 256.653 116.732C256.841 116.544 257.035 116.45 257.234 116.45C257.468 116.45 257.662 116.527 257.814 116.679C257.978 116.832 258.061 117.019 258.061 117.242C258.061 117.43 257.99 117.605 257.849 117.77C257.521 118.133 257.081 118.444 256.53 118.702C255.991 118.948 255.452 119.071 254.912 119.071ZM256.161 112.616C256.02 112.37 255.821 112.188 255.563 112.071C255.305 111.942 255 111.877 254.648 111.877C253.98 111.877 253.417 112.106 252.96 112.563C252.514 113.021 252.256 113.584 252.186 114.252C252.162 114.428 252.151 114.639 252.151 114.885L256.161 112.616Z"/> +<path d="M212.329 139.106C211.953 139.106 211.689 139.03 211.537 138.878C211.385 138.725 211.308 138.479 211.308 138.139V128.043C211.308 127.809 211.396 127.609 211.572 127.445C211.748 127.269 211.947 127.181 212.17 127.181H215.16C216.005 127.181 216.726 127.328 217.324 127.621C217.933 127.902 218.391 128.283 218.696 128.764C219.001 129.233 219.153 129.743 219.153 130.294C219.153 131.174 218.883 131.907 218.344 132.493C218.942 132.774 219.387 133.15 219.681 133.619C219.974 134.076 220.12 134.645 220.12 135.325C220.12 136.544 219.47 137.482 218.168 138.139C216.866 138.784 214.92 139.106 212.329 139.106ZM215.758 131.948C216.239 131.948 216.632 131.784 216.937 131.455C217.253 131.115 217.412 130.734 217.412 130.312C217.412 129.902 217.212 129.562 216.814 129.292C216.427 129.01 215.876 128.87 215.16 128.87H213.067V131.948H215.758ZM213.067 137.435C214.627 137.412 215.899 137.224 216.884 136.873C217.881 136.521 218.379 136.005 218.379 135.325C218.379 134.821 218.15 134.416 217.693 134.111C217.236 133.795 216.591 133.636 215.758 133.636H213.067V137.435Z"/> +<path d="M227.601 139.247C227.367 139.247 227.173 139.183 227.021 139.054C226.88 138.925 226.81 138.755 226.81 138.544V138.297C226.329 138.813 225.661 139.071 224.805 139.071C224.16 139.071 223.579 138.913 223.063 138.596C222.548 138.28 222.143 137.834 221.85 137.26C221.557 136.673 221.41 136.011 221.41 135.272C221.41 134.451 221.586 133.66 221.938 132.898C222.29 132.124 222.782 131.496 223.415 131.016C224.06 130.535 224.799 130.294 225.631 130.294C226.171 130.294 226.657 130.371 227.091 130.523C227.525 130.664 227.977 130.84 228.446 131.051V138.368C228.446 138.602 228.364 138.807 228.199 138.983C228.035 139.159 227.836 139.247 227.601 139.247ZM224.928 137.435C225.268 137.435 225.596 137.365 225.913 137.224C226.241 137.084 226.511 136.914 226.722 136.714L226.775 132.124C226.435 131.983 226.065 131.913 225.667 131.913C225.162 131.913 224.717 132.071 224.33 132.387C223.943 132.692 223.644 133.103 223.433 133.619C223.222 134.123 223.116 134.662 223.116 135.237C223.116 135.952 223.275 136.497 223.591 136.873C223.908 137.248 224.353 137.435 224.928 137.435Z"/> +<path d="M234.176 139.071C233.473 139.071 232.828 138.901 232.241 138.561C231.655 138.221 231.192 137.758 230.852 137.172C230.512 136.574 230.342 135.923 230.342 135.219C230.342 134.422 230.53 133.642 230.905 132.88C231.28 132.118 231.772 131.496 232.382 131.016C232.992 130.535 233.637 130.294 234.317 130.294C234.938 130.294 235.437 130.394 235.812 130.593C236.199 130.781 236.574 131.086 236.938 131.508C237.114 131.696 237.202 131.901 237.202 132.124C237.202 132.346 237.119 132.54 236.955 132.704C236.791 132.857 236.592 132.933 236.357 132.933C236.205 132.933 236.082 132.909 235.988 132.862C235.906 132.815 235.806 132.722 235.689 132.581C235.49 132.358 235.29 132.188 235.091 132.071C234.903 131.954 234.651 131.895 234.335 131.895C233.971 131.895 233.602 132.053 233.226 132.37C232.863 132.686 232.564 133.103 232.329 133.619C232.107 134.123 231.995 134.645 231.995 135.184C231.995 135.653 232.095 136.064 232.294 136.415C232.505 136.755 232.775 137.019 233.103 137.207C233.432 137.383 233.778 137.471 234.141 137.471C234.61 137.471 234.991 137.383 235.284 137.207C235.589 137.031 235.859 136.791 236.093 136.486C236.316 136.193 236.568 136.046 236.85 136.046C237.073 136.046 237.266 136.128 237.43 136.292C237.594 136.445 237.676 136.621 237.676 136.82C237.676 137.054 237.612 137.254 237.483 137.418C237.143 137.875 236.68 138.268 236.093 138.596C235.507 138.913 234.868 139.071 234.176 139.071Z"/> +<path d="M240.046 139.071C239.835 139.071 239.642 138.989 239.466 138.825C239.29 138.649 239.202 138.45 239.202 138.227L239.167 128.078C239.167 127.855 239.249 127.662 239.413 127.498C239.589 127.334 239.794 127.252 240.029 127.252C240.275 127.252 240.474 127.34 240.627 127.515C240.791 127.68 240.873 127.879 240.873 128.113L240.89 133.812C241.383 133.39 241.957 132.845 242.614 132.176C243.271 131.508 243.828 130.898 244.285 130.347C244.473 130.113 244.701 129.995 244.971 129.995C245.205 129.995 245.399 130.072 245.551 130.224C245.716 130.376 245.798 130.57 245.798 130.804C245.798 131.004 245.739 131.174 245.622 131.315C245.317 131.725 244.93 132.176 244.461 132.669C244.004 133.161 243.581 133.589 243.194 133.953L246.097 137.594C246.249 137.77 246.325 137.951 246.325 138.139C246.325 138.385 246.243 138.596 246.079 138.772C245.927 138.936 245.727 139.018 245.481 139.018C245.2 139.018 244.977 138.919 244.813 138.719C243.898 137.488 243.03 136.363 242.21 135.342L241.928 134.973C241.682 135.161 241.342 135.395 240.908 135.677V138.227C240.908 138.45 240.826 138.649 240.662 138.825C240.498 138.989 240.292 139.071 240.046 139.071Z"/> +</mask> +<path d="M195.803 119.071C195.393 119.071 195.123 118.872 194.994 118.473L191.933 108.43C191.875 108.219 191.845 108.031 191.845 107.867C191.845 107.644 191.928 107.463 192.092 107.322C192.256 107.169 192.455 107.093 192.69 107.093C193.124 107.093 193.405 107.298 193.534 107.709L195.838 115.29L198.301 107.691C198.43 107.293 198.711 107.093 199.145 107.093C199.567 107.093 199.837 107.298 199.954 107.709L202.047 115.307L204.861 107.515C205.014 107.105 205.295 106.9 205.705 106.9C205.94 106.9 206.139 106.976 206.303 107.128C206.468 107.269 206.55 107.451 206.55 107.674C206.55 107.873 206.52 108.061 206.462 108.237L202.751 118.508C202.692 118.684 202.592 118.825 202.452 118.931C202.311 119.024 202.147 119.071 201.959 119.071C201.502 119.071 201.22 118.86 201.115 118.438L199.057 111.016L196.647 118.473C196.577 118.696 196.471 118.854 196.331 118.948C196.202 119.03 196.026 119.071 195.803 119.071Z" fill="white"/> +<path d="M210.768 119.071C209.936 119.071 209.185 118.895 208.517 118.544C207.86 118.18 207.344 117.67 206.969 117.013C206.594 116.345 206.406 115.559 206.406 114.656C206.406 113.918 206.571 113.214 206.899 112.546C207.239 111.866 207.708 111.32 208.306 110.91C208.916 110.5 209.608 110.294 210.381 110.294C211.097 110.294 211.706 110.441 212.211 110.734C212.727 111.016 213.114 111.356 213.372 111.754C213.63 112.153 213.758 112.511 213.758 112.827C213.758 112.98 213.712 113.126 213.618 113.267C213.524 113.408 213.389 113.531 213.213 113.636L208.499 116.345C208.664 116.708 208.951 116.99 209.361 117.189C209.783 117.389 210.252 117.488 210.768 117.488C211.448 117.488 212.029 117.236 212.51 116.732C212.697 116.544 212.891 116.45 213.09 116.45C213.325 116.45 213.518 116.527 213.671 116.679C213.835 116.832 213.917 117.019 213.917 117.242C213.917 117.43 213.846 117.605 213.706 117.77C213.377 118.133 212.938 118.444 212.387 118.702C211.847 118.948 211.308 119.071 210.768 119.071ZM212.017 112.616C211.876 112.37 211.677 112.188 211.419 112.071C211.161 111.942 210.856 111.877 210.505 111.877C209.836 111.877 209.273 112.106 208.816 112.563C208.37 113.021 208.112 113.584 208.042 114.252C208.019 114.428 208.007 114.639 208.007 114.885L212.017 112.616Z" fill="white"/> +<path d="M216.453 119.071C216.23 119.071 216.031 118.995 215.855 118.843C215.691 118.678 215.609 118.491 215.609 118.28V108.078C215.609 107.844 215.685 107.65 215.838 107.498C216.002 107.334 216.207 107.252 216.453 107.252C216.676 107.252 216.87 107.334 217.034 107.498C217.21 107.662 217.298 107.855 217.298 108.078V118.28C217.298 118.491 217.21 118.678 217.034 118.843C216.858 118.995 216.664 119.071 216.453 119.071Z" fill="white"/> +<path d="M223.037 119.071C222.334 119.071 221.689 118.901 221.102 118.561C220.516 118.221 220.053 117.758 219.713 117.172C219.373 116.574 219.203 115.923 219.203 115.219C219.203 114.422 219.391 113.642 219.766 112.88C220.141 112.118 220.633 111.496 221.243 111.016C221.853 110.535 222.498 110.294 223.178 110.294C223.799 110.294 224.298 110.394 224.673 110.593C225.06 110.781 225.435 111.086 225.799 111.508C225.975 111.696 226.063 111.901 226.063 112.124C226.063 112.346 225.98 112.54 225.816 112.704C225.652 112.857 225.453 112.933 225.218 112.933C225.066 112.933 224.943 112.909 224.849 112.862C224.767 112.815 224.667 112.722 224.55 112.581C224.351 112.358 224.151 112.188 223.952 112.071C223.764 111.954 223.512 111.895 223.196 111.895C222.832 111.895 222.463 112.053 222.087 112.37C221.724 112.686 221.425 113.103 221.19 113.619C220.968 114.123 220.856 114.645 220.856 115.184C220.856 115.653 220.956 116.064 221.155 116.415C221.366 116.755 221.636 117.019 221.964 117.207C222.293 117.383 222.639 117.471 223.002 117.471C223.471 117.471 223.852 117.383 224.145 117.207C224.45 117.031 224.72 116.791 224.954 116.486C225.177 116.193 225.429 116.046 225.711 116.046C225.934 116.046 226.127 116.128 226.291 116.292C226.455 116.445 226.537 116.621 226.537 116.82C226.537 117.054 226.473 117.254 226.344 117.418C226.004 117.875 225.541 118.268 224.954 118.596C224.368 118.913 223.729 119.071 223.037 119.071Z" fill="white"/> +<path d="M231.088 119.071C230.42 119.071 229.804 118.889 229.241 118.526C228.69 118.162 228.256 117.67 227.94 117.049C227.623 116.415 227.465 115.729 227.465 114.991C227.465 114.158 227.652 113.384 228.028 112.669C228.403 111.942 228.907 111.367 229.54 110.945C230.174 110.511 230.854 110.294 231.581 110.294C232.249 110.294 232.859 110.482 233.41 110.857C233.973 111.232 234.412 111.731 234.729 112.352C235.057 112.974 235.221 113.642 235.221 114.357C235.221 115.202 235.034 115.987 234.659 116.714C234.283 117.43 233.773 118.004 233.128 118.438C232.495 118.86 231.815 119.071 231.088 119.071ZM231.088 117.488C231.499 117.488 231.891 117.348 232.267 117.066C232.642 116.785 232.941 116.409 233.164 115.94C233.386 115.471 233.498 114.973 233.498 114.445C233.498 114 233.41 113.584 233.234 113.197C233.058 112.798 232.818 112.481 232.513 112.247C232.208 112.001 231.874 111.877 231.51 111.877C231.1 111.877 230.713 112.024 230.349 112.317C229.998 112.599 229.716 112.98 229.505 113.46C229.294 113.929 229.189 114.434 229.189 114.973C229.189 115.501 229.276 115.952 229.452 116.327C229.64 116.703 229.874 116.99 230.156 117.189C230.449 117.389 230.76 117.488 231.088 117.488Z" fill="white"/> +<path d="M237.958 119.071C237.736 119.071 237.536 118.989 237.36 118.825C237.196 118.661 237.114 118.467 237.114 118.245V111.139C237.114 110.916 237.196 110.722 237.36 110.558C237.536 110.382 237.736 110.294 237.958 110.294C238.193 110.294 238.392 110.382 238.556 110.558C238.721 110.722 238.803 110.916 238.803 111.139V112.37C239.53 110.986 240.497 110.294 241.705 110.294C242.186 110.294 242.602 110.435 242.954 110.717C243.317 110.998 243.575 111.42 243.727 111.983C244.478 110.857 245.393 110.294 246.471 110.294C247.198 110.294 247.773 110.576 248.195 111.139C248.617 111.69 248.828 112.522 248.828 113.636V118.245C248.828 118.467 248.74 118.661 248.564 118.825C248.4 118.989 248.207 119.071 247.984 119.071C247.749 119.071 247.55 118.989 247.386 118.825C247.222 118.661 247.14 118.467 247.14 118.245V113.531C247.14 112.991 247.063 112.587 246.911 112.317C246.759 112.047 246.518 111.913 246.19 111.913C245.85 111.913 245.51 112.047 245.17 112.317C244.83 112.587 244.548 112.956 244.325 113.425C244.114 113.894 244.009 114.41 244.009 114.973V118.245C244.009 118.479 243.927 118.678 243.763 118.843C243.598 118.995 243.399 119.071 243.165 119.071C242.942 119.071 242.742 118.989 242.567 118.825C242.402 118.661 242.32 118.467 242.32 118.245V113.531C242.32 112.968 242.25 112.558 242.109 112.3C241.98 112.042 241.763 111.913 241.459 111.913C241.083 111.913 240.714 112.065 240.35 112.37C239.987 112.663 239.67 113.021 239.401 113.443C239.19 113.795 239.031 114.146 238.926 114.498C238.832 114.85 238.785 115.237 238.785 115.659L238.803 118.245C238.803 118.467 238.721 118.661 238.556 118.825C238.392 118.989 238.193 119.071 237.958 119.071Z" fill="white"/> +<path d="M254.912 119.071C254.08 119.071 253.329 118.895 252.661 118.544C252.004 118.18 251.488 117.67 251.113 117.013C250.738 116.345 250.55 115.559 250.55 114.656C250.55 113.918 250.714 113.214 251.043 112.546C251.383 111.866 251.852 111.32 252.45 110.91C253.059 110.5 253.751 110.294 254.525 110.294C255.24 110.294 255.85 110.441 256.354 110.734C256.87 111.016 257.257 111.356 257.515 111.754C257.773 112.153 257.902 112.511 257.902 112.827C257.902 112.98 257.855 113.126 257.762 113.267C257.668 113.408 257.533 113.531 257.357 113.636L252.643 116.345C252.807 116.708 253.095 116.99 253.505 117.189C253.927 117.389 254.396 117.488 254.912 117.488C255.592 117.488 256.173 117.236 256.653 116.732C256.841 116.544 257.035 116.45 257.234 116.45C257.468 116.45 257.662 116.527 257.814 116.679C257.978 116.832 258.061 117.019 258.061 117.242C258.061 117.43 257.99 117.605 257.849 117.77C257.521 118.133 257.081 118.444 256.53 118.702C255.991 118.948 255.452 119.071 254.912 119.071ZM256.161 112.616C256.02 112.37 255.821 112.188 255.563 112.071C255.305 111.942 255 111.877 254.648 111.877C253.98 111.877 253.417 112.106 252.96 112.563C252.514 113.021 252.256 113.584 252.186 114.252C252.162 114.428 252.151 114.639 252.151 114.885L256.161 112.616Z" fill="white"/> +<path d="M212.329 139.106C211.953 139.106 211.689 139.03 211.537 138.878C211.385 138.725 211.308 138.479 211.308 138.139V128.043C211.308 127.809 211.396 127.609 211.572 127.445C211.748 127.269 211.947 127.181 212.17 127.181H215.16C216.005 127.181 216.726 127.328 217.324 127.621C217.933 127.902 218.391 128.283 218.696 128.764C219.001 129.233 219.153 129.743 219.153 130.294C219.153 131.174 218.883 131.907 218.344 132.493C218.942 132.774 219.387 133.15 219.681 133.619C219.974 134.076 220.12 134.645 220.12 135.325C220.12 136.544 219.47 137.482 218.168 138.139C216.866 138.784 214.92 139.106 212.329 139.106ZM215.758 131.948C216.239 131.948 216.632 131.784 216.937 131.455C217.253 131.115 217.412 130.734 217.412 130.312C217.412 129.902 217.212 129.562 216.814 129.292C216.427 129.01 215.876 128.87 215.16 128.87H213.067V131.948H215.758ZM213.067 137.435C214.627 137.412 215.899 137.224 216.884 136.873C217.881 136.521 218.379 136.005 218.379 135.325C218.379 134.821 218.15 134.416 217.693 134.111C217.236 133.795 216.591 133.636 215.758 133.636H213.067V137.435Z" fill="white"/> +<path d="M227.601 139.247C227.367 139.247 227.173 139.183 227.021 139.054C226.88 138.925 226.81 138.755 226.81 138.544V138.297C226.329 138.813 225.661 139.071 224.805 139.071C224.16 139.071 223.579 138.913 223.063 138.596C222.548 138.28 222.143 137.834 221.85 137.26C221.557 136.673 221.41 136.011 221.41 135.272C221.41 134.451 221.586 133.66 221.938 132.898C222.29 132.124 222.782 131.496 223.415 131.016C224.06 130.535 224.799 130.294 225.631 130.294C226.171 130.294 226.657 130.371 227.091 130.523C227.525 130.664 227.977 130.84 228.446 131.051V138.368C228.446 138.602 228.364 138.807 228.199 138.983C228.035 139.159 227.836 139.247 227.601 139.247ZM224.928 137.435C225.268 137.435 225.596 137.365 225.913 137.224C226.241 137.084 226.511 136.914 226.722 136.714L226.775 132.124C226.435 131.983 226.065 131.913 225.667 131.913C225.162 131.913 224.717 132.071 224.33 132.387C223.943 132.692 223.644 133.103 223.433 133.619C223.222 134.123 223.116 134.662 223.116 135.237C223.116 135.952 223.275 136.497 223.591 136.873C223.908 137.248 224.353 137.435 224.928 137.435Z" fill="white"/> +<path d="M234.176 139.071C233.473 139.071 232.828 138.901 232.241 138.561C231.655 138.221 231.192 137.758 230.852 137.172C230.512 136.574 230.342 135.923 230.342 135.219C230.342 134.422 230.53 133.642 230.905 132.88C231.28 132.118 231.772 131.496 232.382 131.016C232.992 130.535 233.637 130.294 234.317 130.294C234.938 130.294 235.437 130.394 235.812 130.593C236.199 130.781 236.574 131.086 236.938 131.508C237.114 131.696 237.202 131.901 237.202 132.124C237.202 132.346 237.119 132.54 236.955 132.704C236.791 132.857 236.592 132.933 236.357 132.933C236.205 132.933 236.082 132.909 235.988 132.862C235.906 132.815 235.806 132.722 235.689 132.581C235.49 132.358 235.29 132.188 235.091 132.071C234.903 131.954 234.651 131.895 234.335 131.895C233.971 131.895 233.602 132.053 233.226 132.37C232.863 132.686 232.564 133.103 232.329 133.619C232.107 134.123 231.995 134.645 231.995 135.184C231.995 135.653 232.095 136.064 232.294 136.415C232.505 136.755 232.775 137.019 233.103 137.207C233.432 137.383 233.778 137.471 234.141 137.471C234.61 137.471 234.991 137.383 235.284 137.207C235.589 137.031 235.859 136.791 236.093 136.486C236.316 136.193 236.568 136.046 236.85 136.046C237.073 136.046 237.266 136.128 237.43 136.292C237.594 136.445 237.676 136.621 237.676 136.82C237.676 137.054 237.612 137.254 237.483 137.418C237.143 137.875 236.68 138.268 236.093 138.596C235.507 138.913 234.868 139.071 234.176 139.071Z" fill="white"/> +<path d="M240.046 139.071C239.835 139.071 239.642 138.989 239.466 138.825C239.29 138.649 239.202 138.45 239.202 138.227L239.167 128.078C239.167 127.855 239.249 127.662 239.413 127.498C239.589 127.334 239.794 127.252 240.029 127.252C240.275 127.252 240.474 127.34 240.627 127.515C240.791 127.68 240.873 127.879 240.873 128.113L240.89 133.812C241.383 133.39 241.957 132.845 242.614 132.176C243.271 131.508 243.828 130.898 244.285 130.347C244.473 130.113 244.701 129.995 244.971 129.995C245.205 129.995 245.399 130.072 245.551 130.224C245.716 130.376 245.798 130.57 245.798 130.804C245.798 131.004 245.739 131.174 245.622 131.315C245.317 131.725 244.93 132.176 244.461 132.669C244.004 133.161 243.581 133.589 243.194 133.953L246.097 137.594C246.249 137.77 246.325 137.951 246.325 138.139C246.325 138.385 246.243 138.596 246.079 138.772C245.927 138.936 245.727 139.018 245.481 139.018C245.2 139.018 244.977 138.919 244.813 138.719C243.898 137.488 243.03 136.363 242.21 135.342L241.928 134.973C241.682 135.161 241.342 135.395 240.908 135.677V138.227C240.908 138.45 240.826 138.649 240.662 138.825C240.498 138.989 240.292 139.071 240.046 139.071Z" fill="white"/> +<path d="M195.803 119.071C195.393 119.071 195.123 118.872 194.994 118.473L191.933 108.43C191.875 108.219 191.845 108.031 191.845 107.867C191.845 107.644 191.928 107.463 192.092 107.322C192.256 107.169 192.455 107.093 192.69 107.093C193.124 107.093 193.405 107.298 193.534 107.709L195.838 115.29L198.301 107.691C198.43 107.293 198.711 107.093 199.145 107.093C199.567 107.093 199.837 107.298 199.954 107.709L202.047 115.307L204.861 107.515C205.014 107.105 205.295 106.9 205.705 106.9C205.94 106.9 206.139 106.976 206.303 107.128C206.468 107.269 206.55 107.451 206.55 107.674C206.55 107.873 206.52 108.061 206.462 108.237L202.751 118.508C202.692 118.684 202.592 118.825 202.452 118.931C202.311 119.024 202.147 119.071 201.959 119.071C201.502 119.071 201.22 118.86 201.115 118.438L199.057 111.016L196.647 118.473C196.577 118.696 196.471 118.854 196.331 118.948C196.202 119.03 196.026 119.071 195.803 119.071Z" stroke="black" mask="url(#path-50-outside-1_317_4)"/> +<path d="M210.768 119.071C209.936 119.071 209.185 118.895 208.517 118.544C207.86 118.18 207.344 117.67 206.969 117.013C206.594 116.345 206.406 115.559 206.406 114.656C206.406 113.918 206.571 113.214 206.899 112.546C207.239 111.866 207.708 111.32 208.306 110.91C208.916 110.5 209.608 110.294 210.381 110.294C211.097 110.294 211.706 110.441 212.211 110.734C212.727 111.016 213.114 111.356 213.372 111.754C213.63 112.153 213.758 112.511 213.758 112.827C213.758 112.98 213.712 113.126 213.618 113.267C213.524 113.408 213.389 113.531 213.213 113.636L208.499 116.345C208.664 116.708 208.951 116.99 209.361 117.189C209.783 117.389 210.252 117.488 210.768 117.488C211.448 117.488 212.029 117.236 212.51 116.732C212.697 116.544 212.891 116.45 213.09 116.45C213.325 116.45 213.518 116.527 213.671 116.679C213.835 116.832 213.917 117.019 213.917 117.242C213.917 117.43 213.846 117.605 213.706 117.77C213.377 118.133 212.938 118.444 212.387 118.702C211.847 118.948 211.308 119.071 210.768 119.071ZM212.017 112.616C211.876 112.37 211.677 112.188 211.419 112.071C211.161 111.942 210.856 111.877 210.505 111.877C209.836 111.877 209.273 112.106 208.816 112.563C208.37 113.021 208.112 113.584 208.042 114.252C208.019 114.428 208.007 114.639 208.007 114.885L212.017 112.616Z" stroke="black" mask="url(#path-50-outside-1_317_4)"/> +<path d="M216.453 119.071C216.23 119.071 216.031 118.995 215.855 118.843C215.691 118.678 215.609 118.491 215.609 118.28V108.078C215.609 107.844 215.685 107.65 215.838 107.498C216.002 107.334 216.207 107.252 216.453 107.252C216.676 107.252 216.87 107.334 217.034 107.498C217.21 107.662 217.298 107.855 217.298 108.078V118.28C217.298 118.491 217.21 118.678 217.034 118.843C216.858 118.995 216.664 119.071 216.453 119.071Z" stroke="black" mask="url(#path-50-outside-1_317_4)"/> +<path d="M223.037 119.071C222.334 119.071 221.689 118.901 221.102 118.561C220.516 118.221 220.053 117.758 219.713 117.172C219.373 116.574 219.203 115.923 219.203 115.219C219.203 114.422 219.391 113.642 219.766 112.88C220.141 112.118 220.633 111.496 221.243 111.016C221.853 110.535 222.498 110.294 223.178 110.294C223.799 110.294 224.298 110.394 224.673 110.593C225.06 110.781 225.435 111.086 225.799 111.508C225.975 111.696 226.063 111.901 226.063 112.124C226.063 112.346 225.98 112.54 225.816 112.704C225.652 112.857 225.453 112.933 225.218 112.933C225.066 112.933 224.943 112.909 224.849 112.862C224.767 112.815 224.667 112.722 224.55 112.581C224.351 112.358 224.151 112.188 223.952 112.071C223.764 111.954 223.512 111.895 223.196 111.895C222.832 111.895 222.463 112.053 222.087 112.37C221.724 112.686 221.425 113.103 221.19 113.619C220.968 114.123 220.856 114.645 220.856 115.184C220.856 115.653 220.956 116.064 221.155 116.415C221.366 116.755 221.636 117.019 221.964 117.207C222.293 117.383 222.639 117.471 223.002 117.471C223.471 117.471 223.852 117.383 224.145 117.207C224.45 117.031 224.72 116.791 224.954 116.486C225.177 116.193 225.429 116.046 225.711 116.046C225.934 116.046 226.127 116.128 226.291 116.292C226.455 116.445 226.537 116.621 226.537 116.82C226.537 117.054 226.473 117.254 226.344 117.418C226.004 117.875 225.541 118.268 224.954 118.596C224.368 118.913 223.729 119.071 223.037 119.071Z" stroke="black" mask="url(#path-50-outside-1_317_4)"/> +<path d="M231.088 119.071C230.42 119.071 229.804 118.889 229.241 118.526C228.69 118.162 228.256 117.67 227.94 117.049C227.623 116.415 227.465 115.729 227.465 114.991C227.465 114.158 227.652 113.384 228.028 112.669C228.403 111.942 228.907 111.367 229.54 110.945C230.174 110.511 230.854 110.294 231.581 110.294C232.249 110.294 232.859 110.482 233.41 110.857C233.973 111.232 234.412 111.731 234.729 112.352C235.057 112.974 235.221 113.642 235.221 114.357C235.221 115.202 235.034 115.987 234.659 116.714C234.283 117.43 233.773 118.004 233.128 118.438C232.495 118.86 231.815 119.071 231.088 119.071ZM231.088 117.488C231.499 117.488 231.891 117.348 232.267 117.066C232.642 116.785 232.941 116.409 233.164 115.94C233.386 115.471 233.498 114.973 233.498 114.445C233.498 114 233.41 113.584 233.234 113.197C233.058 112.798 232.818 112.481 232.513 112.247C232.208 112.001 231.874 111.877 231.51 111.877C231.1 111.877 230.713 112.024 230.349 112.317C229.998 112.599 229.716 112.98 229.505 113.46C229.294 113.929 229.189 114.434 229.189 114.973C229.189 115.501 229.276 115.952 229.452 116.327C229.64 116.703 229.874 116.99 230.156 117.189C230.449 117.389 230.76 117.488 231.088 117.488Z" stroke="black" mask="url(#path-50-outside-1_317_4)"/> +<path d="M237.958 119.071C237.736 119.071 237.536 118.989 237.36 118.825C237.196 118.661 237.114 118.467 237.114 118.245V111.139C237.114 110.916 237.196 110.722 237.36 110.558C237.536 110.382 237.736 110.294 237.958 110.294C238.193 110.294 238.392 110.382 238.556 110.558C238.721 110.722 238.803 110.916 238.803 111.139V112.37C239.53 110.986 240.497 110.294 241.705 110.294C242.186 110.294 242.602 110.435 242.954 110.717C243.317 110.998 243.575 111.42 243.727 111.983C244.478 110.857 245.393 110.294 246.471 110.294C247.198 110.294 247.773 110.576 248.195 111.139C248.617 111.69 248.828 112.522 248.828 113.636V118.245C248.828 118.467 248.74 118.661 248.564 118.825C248.4 118.989 248.207 119.071 247.984 119.071C247.749 119.071 247.55 118.989 247.386 118.825C247.222 118.661 247.14 118.467 247.14 118.245V113.531C247.14 112.991 247.063 112.587 246.911 112.317C246.759 112.047 246.518 111.913 246.19 111.913C245.85 111.913 245.51 112.047 245.17 112.317C244.83 112.587 244.548 112.956 244.325 113.425C244.114 113.894 244.009 114.41 244.009 114.973V118.245C244.009 118.479 243.927 118.678 243.763 118.843C243.598 118.995 243.399 119.071 243.165 119.071C242.942 119.071 242.742 118.989 242.567 118.825C242.402 118.661 242.32 118.467 242.32 118.245V113.531C242.32 112.968 242.25 112.558 242.109 112.3C241.98 112.042 241.763 111.913 241.459 111.913C241.083 111.913 240.714 112.065 240.35 112.37C239.987 112.663 239.67 113.021 239.401 113.443C239.19 113.795 239.031 114.146 238.926 114.498C238.832 114.85 238.785 115.237 238.785 115.659L238.803 118.245C238.803 118.467 238.721 118.661 238.556 118.825C238.392 118.989 238.193 119.071 237.958 119.071Z" stroke="black" mask="url(#path-50-outside-1_317_4)"/> +<path d="M254.912 119.071C254.08 119.071 253.329 118.895 252.661 118.544C252.004 118.18 251.488 117.67 251.113 117.013C250.738 116.345 250.55 115.559 250.55 114.656C250.55 113.918 250.714 113.214 251.043 112.546C251.383 111.866 251.852 111.32 252.45 110.91C253.059 110.5 253.751 110.294 254.525 110.294C255.24 110.294 255.85 110.441 256.354 110.734C256.87 111.016 257.257 111.356 257.515 111.754C257.773 112.153 257.902 112.511 257.902 112.827C257.902 112.98 257.855 113.126 257.762 113.267C257.668 113.408 257.533 113.531 257.357 113.636L252.643 116.345C252.807 116.708 253.095 116.99 253.505 117.189C253.927 117.389 254.396 117.488 254.912 117.488C255.592 117.488 256.173 117.236 256.653 116.732C256.841 116.544 257.035 116.45 257.234 116.45C257.468 116.45 257.662 116.527 257.814 116.679C257.978 116.832 258.061 117.019 258.061 117.242C258.061 117.43 257.99 117.605 257.849 117.77C257.521 118.133 257.081 118.444 256.53 118.702C255.991 118.948 255.452 119.071 254.912 119.071ZM256.161 112.616C256.02 112.37 255.821 112.188 255.563 112.071C255.305 111.942 255 111.877 254.648 111.877C253.98 111.877 253.417 112.106 252.96 112.563C252.514 113.021 252.256 113.584 252.186 114.252C252.162 114.428 252.151 114.639 252.151 114.885L256.161 112.616Z" stroke="black" mask="url(#path-50-outside-1_317_4)"/> +<path d="M212.329 139.106C211.953 139.106 211.689 139.03 211.537 138.878C211.385 138.725 211.308 138.479 211.308 138.139V128.043C211.308 127.809 211.396 127.609 211.572 127.445C211.748 127.269 211.947 127.181 212.17 127.181H215.16C216.005 127.181 216.726 127.328 217.324 127.621C217.933 127.902 218.391 128.283 218.696 128.764C219.001 129.233 219.153 129.743 219.153 130.294C219.153 131.174 218.883 131.907 218.344 132.493C218.942 132.774 219.387 133.15 219.681 133.619C219.974 134.076 220.12 134.645 220.12 135.325C220.12 136.544 219.47 137.482 218.168 138.139C216.866 138.784 214.92 139.106 212.329 139.106ZM215.758 131.948C216.239 131.948 216.632 131.784 216.937 131.455C217.253 131.115 217.412 130.734 217.412 130.312C217.412 129.902 217.212 129.562 216.814 129.292C216.427 129.01 215.876 128.87 215.16 128.87H213.067V131.948H215.758ZM213.067 137.435C214.627 137.412 215.899 137.224 216.884 136.873C217.881 136.521 218.379 136.005 218.379 135.325C218.379 134.821 218.15 134.416 217.693 134.111C217.236 133.795 216.591 133.636 215.758 133.636H213.067V137.435Z" stroke="black" mask="url(#path-50-outside-1_317_4)"/> +<path d="M227.601 139.247C227.367 139.247 227.173 139.183 227.021 139.054C226.88 138.925 226.81 138.755 226.81 138.544V138.297C226.329 138.813 225.661 139.071 224.805 139.071C224.16 139.071 223.579 138.913 223.063 138.596C222.548 138.28 222.143 137.834 221.85 137.26C221.557 136.673 221.41 136.011 221.41 135.272C221.41 134.451 221.586 133.66 221.938 132.898C222.29 132.124 222.782 131.496 223.415 131.016C224.06 130.535 224.799 130.294 225.631 130.294C226.171 130.294 226.657 130.371 227.091 130.523C227.525 130.664 227.977 130.84 228.446 131.051V138.368C228.446 138.602 228.364 138.807 228.199 138.983C228.035 139.159 227.836 139.247 227.601 139.247ZM224.928 137.435C225.268 137.435 225.596 137.365 225.913 137.224C226.241 137.084 226.511 136.914 226.722 136.714L226.775 132.124C226.435 131.983 226.065 131.913 225.667 131.913C225.162 131.913 224.717 132.071 224.33 132.387C223.943 132.692 223.644 133.103 223.433 133.619C223.222 134.123 223.116 134.662 223.116 135.237C223.116 135.952 223.275 136.497 223.591 136.873C223.908 137.248 224.353 137.435 224.928 137.435Z" stroke="black" mask="url(#path-50-outside-1_317_4)"/> +<path d="M234.176 139.071C233.473 139.071 232.828 138.901 232.241 138.561C231.655 138.221 231.192 137.758 230.852 137.172C230.512 136.574 230.342 135.923 230.342 135.219C230.342 134.422 230.53 133.642 230.905 132.88C231.28 132.118 231.772 131.496 232.382 131.016C232.992 130.535 233.637 130.294 234.317 130.294C234.938 130.294 235.437 130.394 235.812 130.593C236.199 130.781 236.574 131.086 236.938 131.508C237.114 131.696 237.202 131.901 237.202 132.124C237.202 132.346 237.119 132.54 236.955 132.704C236.791 132.857 236.592 132.933 236.357 132.933C236.205 132.933 236.082 132.909 235.988 132.862C235.906 132.815 235.806 132.722 235.689 132.581C235.49 132.358 235.29 132.188 235.091 132.071C234.903 131.954 234.651 131.895 234.335 131.895C233.971 131.895 233.602 132.053 233.226 132.37C232.863 132.686 232.564 133.103 232.329 133.619C232.107 134.123 231.995 134.645 231.995 135.184C231.995 135.653 232.095 136.064 232.294 136.415C232.505 136.755 232.775 137.019 233.103 137.207C233.432 137.383 233.778 137.471 234.141 137.471C234.61 137.471 234.991 137.383 235.284 137.207C235.589 137.031 235.859 136.791 236.093 136.486C236.316 136.193 236.568 136.046 236.85 136.046C237.073 136.046 237.266 136.128 237.43 136.292C237.594 136.445 237.676 136.621 237.676 136.82C237.676 137.054 237.612 137.254 237.483 137.418C237.143 137.875 236.68 138.268 236.093 138.596C235.507 138.913 234.868 139.071 234.176 139.071Z" stroke="black" mask="url(#path-50-outside-1_317_4)"/> +<path d="M240.046 139.071C239.835 139.071 239.642 138.989 239.466 138.825C239.29 138.649 239.202 138.45 239.202 138.227L239.167 128.078C239.167 127.855 239.249 127.662 239.413 127.498C239.589 127.334 239.794 127.252 240.029 127.252C240.275 127.252 240.474 127.34 240.627 127.515C240.791 127.68 240.873 127.879 240.873 128.113L240.89 133.812C241.383 133.39 241.957 132.845 242.614 132.176C243.271 131.508 243.828 130.898 244.285 130.347C244.473 130.113 244.701 129.995 244.971 129.995C245.205 129.995 245.399 130.072 245.551 130.224C245.716 130.376 245.798 130.57 245.798 130.804C245.798 131.004 245.739 131.174 245.622 131.315C245.317 131.725 244.93 132.176 244.461 132.669C244.004 133.161 243.581 133.589 243.194 133.953L246.097 137.594C246.249 137.77 246.325 137.951 246.325 138.139C246.325 138.385 246.243 138.596 246.079 138.772C245.927 138.936 245.727 139.018 245.481 139.018C245.2 139.018 244.977 138.919 244.813 138.719C243.898 137.488 243.03 136.363 242.21 135.342L241.928 134.973C241.682 135.161 241.342 135.395 240.908 135.677V138.227C240.908 138.45 240.826 138.649 240.662 138.825C240.498 138.989 240.292 139.071 240.046 139.071Z" stroke="black" mask="url(#path-50-outside-1_317_4)"/> +</g> +<defs> +<clipPath id="clip0_317_4"> +<rect width="720" height="517" fill="white"/> +</clipPath> +</defs> +</svg> diff --git a/frontend/app/svg/register.svg b/frontend/app/svg/register.svg new file mode 100644 index 000000000..e0de5d4c7 --- /dev/null +++ b/frontend/app/svg/register.svg @@ -0,0 +1,57 @@ +<svg width="720" height="499" viewBox="0 0 720 499" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_424_3)"> +<path d="M427.832 238.816C427.838 238.646 427.848 238.477 427.854 238.302C432.847 239.959 437.861 241.921 442.823 243.147C445.659 243.848 448.973 244.674 451.077 243.117C455.34 239.982 458.051 233.795 458.222 225.856C458.548 210.854 458.122 195.501 458.013 180.299C457.92 167.691 457.749 155.05 457.787 142.5C457.808 134.816 458.334 127.345 458.309 119.646C458.281 109.56 454.762 101.894 448.105 97.676C442.752 94.2753 437.336 91.2847 431.927 88.6991C423.912 84.8571 415.881 81.4063 407.854 78.0382C401.907 75.5412 395.956 73.5363 390.006 71.1338C382.411 68.074 374.816 64.8897 367.221 61.6527C356.109 56.9209 344.998 51.7963 333.893 47.4264C328.468 45.2894 326.174 47.5295 325.539 53.9446C325.12 58.1827 324.918 62.5972 324.936 67.0648C324.998 82.8845 325.272 98.7917 325.306 114.605C325.319 121.149 324.924 127.524 324.712 133.975C324.252 147.883 323.565 161.687 323.41 175.725C323.31 184.599 325.704 189.83 331.527 193.722C336.057 196.744 340.736 198.533 345.352 200.824C346.294 201.291 347.249 201.466 348.194 201.881C355.873 205.254 363.549 208.586 371.225 212.048C378.553 215.357 385.88 218.732 393.205 222.207C404.748 227.688 416.289 233.272 427.832 238.816ZM348.903 50.7934C360.798 56.2473 372.686 61.9765 384.581 67.0855C401.751 74.466 418.922 81.2477 436.092 88.6283C440.538 90.5416 445.015 93.4163 449.405 96.4227C455.175 100.379 459.033 108.032 459.459 116.908C459.804 124.15 459.329 131.139 459.273 138.268C459.186 149.015 459.055 159.744 459.111 170.546C459.179 183.676 459.447 196.883 459.587 210.038C459.649 215.937 459.96 222.006 459.528 227.628C458.94 235.265 456.409 241.134 452.476 244.76C450.819 246.287 448.233 246.547 445.957 246.026C434.554 243.42 423.091 239.036 411.613 233.228C407.31 231.048 403.004 228.79 398.695 226.886C378.596 217.995 358.494 209.438 338.404 200.212C334.406 198.374 330.318 194.592 326.537 190.808C323.668 187.933 321.911 183.454 322.088 178.273C322.632 162.096 323.214 145.934 323.472 129.624C323.761 111.383 323.702 92.9869 323.808 74.6643C323.854 67.1196 323.621 59.4159 324.103 52.1137C324.52 45.8001 327.52 43.4691 331.863 44.8178C337.534 46.5823 343.21 48.5122 348.887 50.375C348.894 50.5165 348.9 50.6532 348.903 50.7934Z" fill="black"/> +<path d="M407.85 180.991C407.859 180.55 407.869 180.108 407.878 179.671C407.688 179.353 407.499 178.834 407.309 178.755C402.835 176.811 398.293 174.135 393.91 173.22C388.988 172.187 384.2 170.736 379.313 167.327C378.331 166.642 377.271 166.409 376.251 166.106C372.355 164.967 368.615 165.483 364.515 162.4C361.766 160.337 358.754 159.822 355.863 158.782C355.437 158.63 355.036 159.426 354.094 160.239C372.564 167.367 390.207 174.182 407.85 180.991ZM389.414 207.755L389.436 206.753C393.913 208.608 398.396 210.647 402.866 212.264C406.805 213.689 410.726 214.657 414.282 213.146C416.745 212.096 418.448 208.395 417.929 204.922C417.183 199.933 414.425 195.135 411.329 193.905C397.625 188.449 383.924 183.033 370.223 177.784C367.095 176.584 364.648 178.254 363.019 181.402C360.862 185.567 363.184 193.264 367.844 196.109C374.989 200.477 382.22 203.922 389.414 207.755ZM428.692 131.239C428.698 131.089 428.705 130.943 428.708 130.791C432.398 131.95 436.11 133.387 439.76 134.137C441.184 134.425 443.341 133.935 443.662 132.501C444.687 127.877 445.25 122.773 445.536 117.613C445.745 113.838 444.212 110.396 441.473 108.84C439.343 107.632 437.173 106.762 435.034 106.063C428.328 103.86 421.632 102.226 414.923 99.6101C401.909 94.5453 388.885 88.9785 375.865 83.5853C363.455 78.4446 351.041 73.2259 338.63 68.1331C335.48 66.8382 334.756 67.9513 334.877 72.7345C334.977 76.6272 335.002 80.4889 335.023 84.3494C335.055 89.5168 335.723 91.0104 339.196 92.6069C351.781 98.3946 364.369 104.346 376.957 109.747C394.202 117.144 411.447 124.096 428.692 131.239ZM419.306 167.519C419.306 167.5 419.306 167.476 419.306 167.452C426.553 170.454 433.797 173.492 441.044 176.442C445.533 178.272 447.094 176.006 446.391 169.176C446.024 165.614 445.717 162.042 445.633 158.563C445.455 151.347 443.761 147.857 438.976 146.076C430.613 142.957 422.263 140.284 413.9 137.102C396.12 130.341 378.34 123.532 360.557 116.42C352.89 113.35 345.221 109.546 337.554 105.934C335.412 104.927 334.013 105.31 333.715 108.507C333.556 110.213 333.441 111.972 333.478 113.764C333.568 118.276 333.994 122.941 333.842 127.328C333.687 131.934 334.119 133.464 337.333 134.93C339.469 135.901 341.614 136.152 343.76 136.979C354.109 140.969 364.462 144.917 374.815 149.123C389.644 155.146 404.474 161.375 419.306 167.519ZM335.745 146.487C335.35 150.434 334.856 154.33 334.644 158.376C334.604 159.136 335.614 161.011 336.261 161.465C338.191 162.821 340.184 163.948 342.152 164.768C349.483 167.823 349.461 168.327 349.722 156.493C349.822 152.002 348.846 149.955 345.886 148.437C343.138 147.03 340.371 145.889 337.619 144.883C337.082 144.684 336.569 145.645 336.046 146.076C335.947 146.212 335.844 146.347 335.745 146.487ZM427.831 238.815C416.291 233.273 404.747 227.687 393.204 222.206C385.879 218.731 378.551 215.356 371.224 212.048C363.548 208.586 355.872 205.253 348.196 201.882C347.248 201.465 346.296 201.291 345.351 200.823C340.738 198.534 336.059 196.744 331.529 193.723C325.703 189.83 323.312 184.6 323.409 175.725C323.567 161.688 324.251 147.882 324.711 133.975C324.926 127.525 325.317 121.148 325.305 114.604C325.271 98.7959 324.997 82.8838 324.935 67.0641C324.919 62.5978 325.118 58.1821 325.538 53.944C326.172 47.5288 328.467 45.2888 333.892 47.4258C344.997 51.7957 356.111 56.9216 367.219 61.6569C374.815 64.8939 382.41 68.0734 390.005 71.1331C395.955 73.5356 401.906 75.5405 407.856 78.0388C415.88 81.4056 423.911 84.8612 431.928 88.6997C437.335 91.2888 442.754 94.2807 448.104 97.6754C454.76 101.898 458.28 109.56 458.311 119.647C458.333 127.344 457.807 134.815 457.785 142.5C457.748 155.049 457.922 167.692 458.012 180.299C458.121 195.501 458.547 210.853 458.224 225.857C458.05 233.794 455.339 239.982 451.079 243.123C448.972 244.674 445.657 243.847 442.822 243.147C437.86 241.92 432.846 239.958 427.853 238.302C427.846 238.477 427.84 238.646 427.831 238.815Z" fill="#D2DAFF"/> +<path d="M335.403 131.592C337.891 132.623 340.288 133.654 342.681 134.602C357.349 140.419 372.014 146.11 386.682 152.061C402.376 158.428 418.073 165.064 433.767 171.517C436.692 172.719 439.618 173.562 442.546 174.645C444.324 175.305 445.018 174.184 444.906 171.666C444.732 167.766 444.418 163.814 444.349 159.958C444.206 151.87 442.876 149.302 437.538 147.431C431.167 145.195 424.804 143.253 418.43 140.805C397.722 132.859 377.013 124.823 356.302 116.627C350.447 114.308 344.584 111.247 338.724 108.671C335.372 107.196 334.803 107.55 334.797 112.299C334.788 118.533 335.176 124.936 335.403 131.592ZM419.307 167.52C404.474 161.376 389.645 155.143 374.812 149.123C364.462 144.918 354.11 140.97 343.76 136.98C341.615 136.153 339.47 135.902 337.334 134.931C334.119 133.465 333.684 131.934 333.843 127.324C333.992 122.941 333.569 118.277 333.476 113.764C333.442 111.968 333.554 110.213 333.715 108.508C334.014 105.307 335.41 104.927 337.555 105.935C345.221 109.547 352.891 113.351 360.558 116.417C378.341 123.533 396.121 130.338 413.901 137.104C422.26 140.279 430.614 142.958 438.974 146.076C443.762 147.858 445.453 151.347 445.633 158.564C445.717 162.038 446.025 165.615 446.392 169.177C447.091 176.006 445.534 178.269 441.044 176.443C433.798 173.494 426.554 170.455 419.307 167.453C419.307 167.477 419.307 167.496 419.307 167.52Z" fill="black"/> +<path d="M347.81 74.5043C347.807 74.2779 347.804 74.0562 347.801 73.8298C344.782 72.5794 341.76 71.3756 338.741 70.0581C337.044 69.3167 336.158 69.8646 336.232 72.6213C336.332 76.3798 336.254 80.0793 336.369 83.8443C336.524 89.0776 336.81 89.6368 340.33 91.2143C356.101 98.3027 371.876 105.426 387.651 112.324C393.25 114.778 398.855 116.616 404.461 118.774C415.643 123.081 426.832 127.538 438.015 131.672C442.57 133.357 442.293 132.242 443.661 127.012C444.298 124.579 444.289 121.471 444.329 118.565C444.407 113.323 442.483 110.692 438.988 109.517C431.259 106.924 423.543 104.815 415.811 101.9C408.574 99.1753 401.321 95.4284 394.08 92.5394C378.653 86.3843 363.233 80.5 347.81 74.5043ZM428.695 131.241C411.449 124.098 394.201 117.145 376.959 109.749C364.368 104.347 351.783 98.3967 339.195 92.6077C335.722 91.0112 335.057 89.514 335.026 84.3515C335.001 80.4897 334.976 76.6231 334.877 72.7352C334.755 67.9521 335.483 66.8403 338.632 68.1304C351.043 73.228 363.454 78.4454 375.865 83.5861C388.885 88.9792 401.908 94.5461 414.925 99.6122C421.631 102.222 428.328 103.861 435.034 106.064C437.173 106.763 439.346 107.634 441.472 108.841C444.211 110.397 445.747 113.835 445.536 117.614C445.25 122.774 444.69 127.879 443.661 132.502C443.344 133.937 441.186 134.427 439.759 134.133C436.109 133.383 432.397 131.951 428.71 130.793C428.704 130.939 428.701 131.091 428.695 131.241Z" fill="black"/> +<path d="M385.377 185.048C385.377 185.336 385.377 185.618 385.38 185.907C380.891 184.047 376.401 182.289 371.912 180.29C369.223 179.095 367.044 179.701 365.144 181.515C362.952 183.611 363.046 188.253 365.663 191.465C367.557 193.787 369.857 195.784 372.005 196.789C381.656 201.303 391.321 205.795 400.968 209.485C405.081 211.059 409.216 210.956 413.245 211.044C415.537 211.093 416.681 208.592 416.429 204.876C416.189 201.342 414.787 198.591 412.496 196.995C411.153 196.056 409.776 195.198 408.414 194.619C400.735 191.376 393.056 188.229 385.377 185.048ZM389.412 207.754C382.221 203.923 374.99 200.477 367.846 196.11C363.185 193.265 360.863 185.567 363.021 181.402C364.65 178.254 367.096 176.585 370.224 177.785C383.925 183.034 397.626 188.45 411.327 193.905C414.424 195.135 417.184 199.933 417.93 204.923C418.45 208.395 416.746 212.097 414.284 213.141C410.727 214.658 406.807 213.69 402.865 212.263C398.394 210.646 393.914 208.608 389.437 206.754L389.412 207.754Z" fill="black"/> +<path d="M407.85 180.992C390.207 174.182 372.564 167.367 354.094 160.239C355.036 159.427 355.437 158.63 355.863 158.782C358.754 159.822 361.767 160.337 364.515 162.4C368.616 165.483 372.356 164.967 376.251 166.107C377.271 166.409 378.331 166.642 379.313 167.327C384.201 170.736 388.988 172.187 393.91 173.22C398.293 174.135 402.835 176.812 407.309 178.756C407.499 178.834 407.688 179.354 407.878 179.672C407.869 180.108 407.859 180.55 407.85 180.992Z" fill="black"/> +<path d="M336.046 146.073C336.571 145.649 337.081 144.687 337.619 144.881C340.373 145.887 343.14 147.029 345.885 148.439C348.845 149.957 349.821 152 349.722 156.495C349.461 168.324 349.486 167.822 342.152 164.765C340.184 163.945 338.191 162.818 336.263 161.464C335.614 161.008 334.606 159.134 334.644 158.374C334.858 154.333 335.349 150.436 335.744 146.485C335.999 147.414 336.478 148.441 336.465 149.251C336.425 151.907 336.111 154.445 336.114 157.119C336.114 157.958 336.767 159.771 337.146 159.952C340.697 161.604 344.25 163.005 347.726 164.444C347.981 162.188 348.167 160.503 348.357 158.823C348.963 153.498 348.099 151.425 344.312 149.603C341.561 148.281 338.803 147.239 336.046 146.073Z" fill="black"/> +<path d="M365.883 130.778C366.878 129.552 367.758 128.47 368.638 127.388C368.504 127.074 368.37 126.755 368.237 126.441C367.459 126.603 366.682 126.76 365.827 126.937C365.672 125.666 365.529 124.495 365.383 123.328C365.165 123.19 364.944 123.055 364.727 122.917C364.397 124.309 364.064 125.704 363.806 126.79C362.843 126.539 361.941 126.31 361.043 126.081C361.702 127.432 362.358 128.782 363.07 130.245C362.177 131.524 361.273 132.816 360.368 134.108L360.9 135.109C361.758 134.617 362.613 134.123 363.545 133.59C363.847 135.065 364.092 136.269 364.338 137.473L364.857 137.707C365.09 136.491 365.327 135.281 365.569 134.026C366.468 135.088 367.211 135.97 367.957 136.849C368.106 136.624 368.255 136.403 368.404 136.177C367.618 134.496 366.834 132.816 365.883 130.778ZM404.925 150.351C406.228 151.585 407.232 152.538 408.233 153.489C408.348 153.177 408.463 152.866 408.575 152.553C407.297 150.692 406.019 148.836 404.658 146.858C405.466 145.545 406.153 144.436 406.837 143.321C406.678 143.025 406.517 142.733 406.355 142.441C405.575 142.707 404.794 142.977 404.048 143.234C403.523 141.584 403.038 140.061 402.55 138.536C402.348 138.572 402.146 138.604 401.943 138.64C402.009 139.955 402.071 141.27 402.136 142.595C401.241 141.841 400.451 141.169 399.658 140.5C399.565 140.74 399.472 140.984 399.379 141.223C400.271 142.852 401.16 144.48 402.108 146.219C401.104 147.83 400.171 149.326 399.239 150.827C399.447 151.177 399.658 151.528 399.867 151.873C400.806 151.141 401.741 150.403 402.683 149.662C403.193 150.947 403.582 151.927 404.201 153.491C404.53 152.061 404.723 151.221 404.925 150.351ZM382.109 133.217C381.938 133.409 381.77 133.598 381.602 133.792C382.283 135.708 382.967 137.625 383.623 139.468C382.18 140.853 380.915 142.068 379.649 143.283C379.811 143.68 379.97 144.077 380.131 144.469C381.219 143.976 382.308 143.483 383.486 142.946C383.7 144.377 383.893 145.678 384.195 147.705C384.838 146.529 385.18 145.9 385.51 145.303C386.402 146.439 387.167 147.412 388.507 149.122C386.237 141.351 386.237 141.351 388.441 138.024C387.493 136.606 386.545 135.188 385.423 133.512C385.112 134.48 384.767 135.554 384.418 136.631C383.647 135.492 382.88 134.355 382.109 133.217ZM350.553 132.318C350.808 132.223 351.066 132.128 351.321 132.033C350.419 129.71 348.716 126.954 348.821 125.177C348.949 123.026 350.603 121.699 352.064 119.224C350.879 119.424 350.255 119.39 349.732 119.653C349.145 119.946 348.65 120.503 347.842 121.174V117.097C347.659 116.945 347.472 116.795 347.286 116.642C346.872 117.61 346.459 118.579 346.275 119.011C345.016 118.609 344.015 118.29 343.017 117.973C343.033 118.353 343.048 118.733 343.064 119.118C345.862 121.805 345.862 121.805 344.024 127.971C344.83 127.983 345.492 127.994 346.082 128.004C346.4 129.046 346.667 129.923 347.167 131.563C347.572 129.977 347.792 129.11 348.066 128.031C349.036 129.702 349.794 131.008 350.553 132.318ZM335.403 131.59C335.176 124.933 334.788 118.531 334.797 112.297C334.803 107.548 335.372 107.199 338.724 108.674C344.584 111.249 350.447 114.311 356.301 116.625C377.013 124.821 397.722 132.857 418.43 140.808C424.803 143.251 431.167 145.193 437.537 147.429C442.875 149.3 444.206 151.872 444.349 159.956C444.418 163.816 444.732 167.764 444.906 171.669C445.018 174.182 444.324 175.308 442.546 174.648C439.617 173.56 436.692 172.717 433.766 171.515C418.073 165.067 402.376 158.426 386.682 152.059C372.014 146.108 357.349 140.422 342.681 134.6C340.287 133.652 337.89 132.621 335.403 131.59Z" fill="white"/> +<path d="M345.846 83.5267C345.234 84.5521 344.684 85.4739 344.273 86.1614C345.389 87.5626 346.247 88.6413 347.233 89.8783C347.466 89.1509 347.743 88.2931 347.939 87.6892C348.874 87.9954 349.621 88.2326 351.004 88.6811C349.816 86.8718 349.235 85.9843 347.724 83.6818C349.017 83.3361 349.994 83.0746 351.11 82.7752C349.428 81.2402 348.318 80.2296 347.628 79.5988C346.185 79.739 345.038 79.853 343.686 79.9875C344.509 81.3395 345.15 82.3856 345.846 83.5267ZM382.146 98.8021C381.499 99.8037 380.915 100.711 380.315 101.641C381.419 102.879 382.258 103.816 383.113 104.774C383.495 103.984 383.862 103.226 384.217 102.5C385.115 103.577 385.839 104.442 386.567 105.314C386.694 105.084 386.825 104.855 386.952 104.625C386.29 103.125 385.631 101.625 384.907 99.984C385.818 98.9146 386.648 97.941 387.478 96.9675C387.36 96.6407 387.242 96.314 387.123 95.9872C386.157 96.1999 385.193 96.4138 384.108 96.6542C384.018 95.0935 383.94 93.7631 383.862 92.4327C383.663 92.2928 383.467 92.159 383.268 92.0191C382.976 93.0765 382.687 94.1352 382.395 95.1926C381.708 94.6445 381.021 94.0964 380.334 93.5484C380.215 93.7246 380.1 93.9021 379.982 94.0783C380.694 95.6331 381.406 97.1878 382.146 98.8021ZM364.323 98.0158C364.761 96.3483 365.031 95.3202 365.311 94.2576C366.256 95.0084 367.021 95.6126 367.789 96.2229C367.885 96.0568 367.979 95.8943 368.075 95.7282C367.469 94.4328 366.863 93.1374 366.207 91.7399C366.937 90.6629 367.587 89.7057 368.237 88.7485C368.143 88.4847 368.047 88.2244 367.954 87.9654C367.164 87.9976 366.375 88.0346 365.582 88.0703C365.479 86.6673 365.383 85.358 365.286 84.0534C365.044 83.9194 364.801 83.7854 364.562 83.6527C364.276 85.0815 363.993 86.5069 363.751 87.7238C362.569 86.8991 361.559 86.198 360.548 85.492C360.514 85.8563 360.48 86.2206 360.449 86.5861C361.425 87.9534 362.404 89.3219 363.499 90.853C362.411 92.1891 361.459 93.3615 360.508 94.5339C360.642 94.824 360.775 95.1141 360.909 95.4042C361.643 95.2818 362.38 95.1654 363.116 95.0443C363.437 95.8332 363.757 96.6221 364.323 98.0158ZM347.808 74.5046C363.231 80.5004 378.655 86.3859 394.078 92.5398C401.322 95.4253 408.572 99.1708 415.809 101.9C423.541 104.816 431.258 106.92 438.986 109.517C442.484 110.693 444.405 113.324 444.331 118.562C444.287 121.471 444.296 124.579 443.659 127.012C442.291 132.242 442.568 133.358 438.013 131.672C426.831 127.539 415.645 123.078 404.459 118.775C398.857 116.617 393.251 114.774 387.652 112.326C371.874 105.422 356.103 98.2996 340.328 91.2146C336.809 89.6371 336.526 89.0792 336.367 83.8447C336.255 80.081 336.33 76.3801 336.23 72.6216C336.156 69.8649 337.042 69.317 338.742 70.0597C341.761 71.3772 344.78 72.5797 347.799 73.8301C347.802 74.0518 347.805 74.2782 347.808 74.5046Z" fill="white"/> +<path d="M385.376 185.048C393.055 188.229 400.734 191.376 408.414 194.619C409.775 195.197 411.152 196.055 412.496 196.995C414.787 198.59 416.189 201.341 416.428 204.875C416.68 208.591 415.536 211.092 413.245 211.044C409.216 210.956 405.081 211.059 400.968 209.484C391.321 205.795 381.655 201.303 372.005 196.788C369.857 195.783 367.556 193.786 365.663 191.464C363.045 188.253 362.952 183.611 365.144 181.515C367.043 179.701 369.222 179.094 371.912 180.29C376.401 182.288 380.89 184.047 385.38 185.907C385.376 185.618 385.376 185.335 385.376 185.048Z" fill="#FFEAD1"/> +<path d="M339.385 153.781C339.22 154.067 339.059 154.355 338.894 154.636C339.525 155.535 340.113 156.779 340.803 157.262C342.429 158.4 346.225 156.341 347.254 153.73C344.35 152.939 342.566 157.728 339.385 153.781ZM336.046 146.075C338.801 147.24 341.561 148.283 344.313 149.604C348.099 151.422 348.964 153.5 348.358 158.82C348.168 160.504 347.981 162.19 347.726 164.441C344.251 163.001 340.697 161.606 337.147 159.948C336.764 159.771 336.115 157.959 336.115 157.121C336.111 154.447 336.425 151.908 336.463 149.246C336.475 148.442 336 147.416 335.745 146.486C335.844 146.35 335.947 146.211 336.046 146.075Z" fill="white"/> +<path d="M350.553 132.317C349.795 131.011 349.036 129.701 348.066 128.03C347.792 129.109 347.572 129.976 347.167 131.562C346.667 129.922 346.4 129.045 346.082 128.003C345.495 127.995 344.83 127.982 344.024 127.97C345.862 121.804 345.862 121.804 343.064 119.117C343.048 118.737 343.033 118.352 343.017 117.972C344.015 118.289 345.016 118.608 346.275 119.015C346.459 118.578 346.872 117.609 347.286 116.641C347.472 116.794 347.659 116.948 347.842 117.096V121.173C348.65 120.502 349.145 119.945 349.735 119.653C350.255 119.389 350.88 119.423 352.064 119.228C350.603 121.703 348.949 123.025 348.821 125.181C348.716 126.953 350.419 129.709 351.321 132.032C351.066 132.127 350.808 132.222 350.553 132.317Z" fill="black"/> +<path d="M382.108 133.217C382.879 134.355 383.65 135.494 384.421 136.628C384.769 135.555 385.114 134.481 385.425 133.513C386.544 135.189 387.492 136.607 388.44 138.02C386.236 141.351 386.236 141.351 388.506 149.122C387.166 147.413 386.404 146.441 385.509 145.303C385.182 145.901 384.837 146.529 384.197 147.706C383.895 145.679 383.699 144.377 383.488 142.948C382.307 143.484 381.218 143.977 380.133 144.471C379.972 144.073 379.81 143.676 379.648 143.283C380.914 142.068 382.179 140.854 383.625 139.469C382.966 137.62 382.285 135.709 381.601 133.793C381.772 133.6 381.94 133.411 382.108 133.217Z" fill="black"/> +<path d="M404.924 150.35C404.722 151.224 404.529 152.064 404.203 153.49C403.584 151.931 403.192 150.945 402.686 149.667C401.744 150.407 400.805 151.139 399.869 151.877C399.657 151.526 399.449 151.176 399.238 150.825C400.17 149.329 401.106 147.829 402.11 146.219C401.162 144.485 400.27 142.855 399.378 141.221C399.471 140.982 399.564 140.743 399.657 140.504C400.45 141.172 401.243 141.841 402.135 142.598C402.073 141.269 402.008 139.958 401.946 138.644C402.148 138.608 402.35 138.572 402.552 138.536C403.037 140.059 403.522 141.582 404.047 143.232C404.793 142.976 405.574 142.71 406.354 142.439C406.516 142.731 406.677 143.028 406.839 143.32C406.152 144.434 405.465 145.549 404.657 146.857C406.018 148.834 407.296 150.695 408.577 152.553C408.462 152.864 408.347 153.181 408.235 153.494C407.231 152.541 406.23 151.59 404.924 150.35Z" fill="black"/> +<path d="M365.883 130.779C366.835 132.816 367.618 134.496 368.405 136.178C368.255 136.403 368.106 136.624 367.957 136.85C367.211 135.971 366.468 135.088 365.569 134.026C365.327 135.281 365.091 136.491 364.857 137.707L364.338 137.473C364.093 136.269 363.847 135.066 363.545 133.59C362.613 134.123 361.758 134.617 360.9 135.11L360.368 134.109C361.273 132.816 362.178 131.524 363.07 130.246C362.358 128.782 361.702 127.432 361.043 126.082C361.941 126.31 362.843 126.54 363.807 126.79C364.065 125.704 364.397 124.309 364.727 122.918C364.944 123.056 365.165 123.19 365.383 123.328C365.529 124.495 365.672 125.666 365.827 126.938C366.682 126.76 367.46 126.603 368.237 126.441C368.371 126.755 368.504 127.074 368.638 127.388C367.758 128.47 366.878 129.553 365.883 130.779Z" fill="black"/> +<path d="M364.324 98.0155C363.758 96.6219 363.438 95.8329 363.117 95.044C362.377 95.1639 361.644 95.2815 360.907 95.4027C360.776 95.1138 360.643 94.8237 360.509 94.5336C361.46 93.3612 362.412 92.1888 363.497 90.8514C362.402 89.3203 361.426 87.9531 360.447 86.5846C360.481 86.2203 360.515 85.856 360.546 85.4904C361.56 86.1977 362.57 86.8988 363.748 87.7222C363.991 86.5053 364.277 85.0813 364.56 83.6559C364.802 83.7851 365.045 83.9191 365.287 84.0531C365.384 85.3577 365.477 86.6658 365.58 88.0687C366.375 88.0343 367.165 87.9973 367.955 87.9652C368.048 88.2241 368.141 88.4831 368.238 88.7482C367.585 89.7041 366.935 90.6613 366.208 91.7396C366.864 93.1371 367.47 94.4325 368.076 95.7279C367.98 95.894 367.883 96.0553 367.79 96.2226C367.022 95.6123 366.257 95.0081 365.312 94.2574C365.032 95.3199 364.762 96.348 364.324 98.0155Z" fill="black"/> +<path d="M382.146 98.8006C381.406 97.1864 380.694 95.6316 379.982 94.0768C380.1 93.9006 380.218 93.7244 380.336 93.5482C381.023 94.0962 381.71 94.6443 382.398 95.1924C382.687 94.1337 382.979 93.0763 383.268 92.0176C383.467 92.1575 383.666 92.2926 383.862 92.4313C383.94 93.7617 384.017 95.092 384.111 96.654C385.192 96.4171 386.159 96.1997 387.123 95.9857C387.241 96.3125 387.359 96.6393 387.478 96.966C386.647 97.9396 385.817 98.9179 384.906 99.9873C385.631 101.624 386.293 103.124 386.952 104.624C386.825 104.854 386.697 105.083 386.567 105.312C385.842 104.447 385.115 103.575 384.216 102.499C383.865 103.23 383.498 103.984 383.116 104.774C382.261 103.816 381.421 102.879 380.318 101.641C380.918 100.711 381.499 99.8022 382.146 98.8006Z" fill="black"/> +<path d="M345.846 83.5266C345.15 82.3854 344.509 81.3393 343.686 79.9873C345.038 79.8529 346.185 79.7388 347.628 79.5986C348.318 80.2294 349.428 81.24 351.11 82.775C349.994 83.0744 349.017 83.3359 347.724 83.6816C349.235 85.9841 349.816 86.8716 351.004 88.6809C349.62 88.2324 348.874 87.9952 347.939 87.689C347.743 88.2929 347.466 89.1508 347.233 89.8781C346.247 88.6412 345.389 87.5624 344.273 86.1612C344.684 85.4737 345.234 84.5519 345.846 83.5266Z" fill="black"/> +<path d="M339.386 153.781C342.566 157.728 344.351 152.939 347.254 153.73C346.225 156.341 342.429 158.4 340.803 157.262C340.113 156.779 339.526 155.535 338.895 154.636C339.059 154.355 339.221 154.067 339.386 153.781Z" fill="black"/> +<path d="M478.42 297.709C381.077 253.462 184.544 267.29 105.729 279.734V499.227H226.766C226.766 499.227 715.933 529.719 718.707 499.227L718.753 498.724C721.492 468.618 722.446 458.139 678.914 482.993C569.886 545.239 600.099 353.018 478.42 297.709Z" fill="#67B7C5" stroke="black"/> +<path d="M517.781 131.771L517.815 131.423L518.143 131.793L518.153 131.787L518.837 131.336L518.455 131.13C518.542 130.903 518.627 130.678 518.712 130.454C519.04 129.582 519.359 128.737 519.746 127.939C520.59 126.216 521.814 125.333 523.3 125.075C524.793 124.816 526.579 125.183 528.561 126.025L528.561 126.025C530.415 126.812 532.065 127.927 533.442 129.385C534.816 130.839 535.946 132.662 536.741 134.906C536.975 135.573 537.135 136.254 537.287 137.009C537.337 137.26 537.387 137.523 537.44 137.796C537.541 138.326 537.65 138.894 537.781 139.485L537.94 140.2L538.464 140.077C539.728 139.78 540.971 139.712 542.151 140.081C543.308 140.443 544.425 141.228 545.48 142.707C547.037 144.893 548.525 147.212 548.96 149.964C535.565 145.098 522.446 140.332 509.251 135.533C508.878 132.656 509.782 130.933 511.323 129.745L511.324 129.745C512.16 129.097 513.114 129.24 514.189 129.726C514.738 129.973 515.312 130.307 515.918 130.673C516.031 130.742 516.146 130.812 516.262 130.882C516.756 131.183 517.271 131.496 517.781 131.771Z" fill="#EEEFFC" stroke="black"/> +<path d="M451.996 31.888L451.996 31.888L451.998 31.8879C454.176 31.7984 456.257 33.6373 458.228 36.4272C458.433 36.7187 458.613 37.1434 458.813 37.7242C458.874 37.9024 458.939 38.0989 459.007 38.3052C459.142 38.7179 459.29 39.1699 459.453 39.594L459.7 40.234L460.139 40.0648C460.878 39.7803 461.532 39.849 462.094 40.1905C462.651 40.5289 463.236 41.207 463.796 42.4068L463.796 42.4081C464.354 43.5985 464.715 44.8092 464.221 45.7995C461.202 45.1489 458.156 43.8998 455.068 42.5594C454.647 42.3767 454.225 42.1923 453.802 42.0075C451.226 40.881 448.618 39.7408 446.015 38.911C446.01 38.4451 446.023 38.0766 446.06 37.7839C446.11 37.3896 446.2 37.1682 446.325 37.0353C446.447 36.9045 446.633 36.8265 446.953 36.8133C447.28 36.7999 447.717 36.8549 448.317 36.9643L448.215 36.433L448.674 36.6295C448.672 34.9317 449.019 33.7704 449.597 33.0239C450.174 32.2784 450.998 31.9231 451.996 31.888Z" fill="#EEEFFC" stroke="black"/> +<path d="M391.933 15.3432C391.653 14.8432 391.333 14.3532 391.123 13.8232C390.783 12.9732 390.563 12.0732 390.223 11.2232C389.523 9.46322 388.453 8.20322 386.303 8.44322C385.973 8.48322 385.623 8.28322 384.963 8.11322C385.443 7.58322 385.703 7.06322 386.103 6.91322C388.983 5.83322 390.833 4.08322 390.533 0.743221C390.523 0.583221 390.663 0.40322 390.743 0.25322C390.783 0.18322 390.873 0.133223 391.233 -0.196777C391.593 0.373223 392.123 0.87322 392.243 1.45322C392.823 4.13322 394.583 5.48322 397.103 6.14322C397.953 6.36322 398.753 6.75322 399.593 7.60322C398.693 7.84322 397.803 8.10322 396.903 8.33322C393.743 9.15322 392.943 10.0932 392.723 13.2632C392.673 13.9332 392.543 14.5932 392.443 15.2632C392.273 15.2932 392.113 15.3132 391.943 15.3432H391.933ZM391.473 10.3732C393.003 9.17322 394.423 8.04322 395.653 7.08322C394.283 6.04322 392.863 4.97322 391.493 3.92322C390.543 5.00322 389.543 6.11322 388.673 7.10322C389.703 8.29322 390.633 9.38322 391.483 10.3732H391.473Z" fill="black"/> +<path d="M488.143 90.4632C485.696 92.3565 484.12 94.8332 483.413 97.8932C481.783 96.9732 482.243 95.0632 481.353 93.8932C480.443 92.6932 479.513 91.5632 477.543 91.2332C478.603 90.2732 480.013 89.4932 480.623 88.3032C481.223 87.1332 481.013 85.5532 481.393 84.0332C483.143 86.6332 484.703 89.3232 488.143 90.4632V90.4632ZM480.233 90.8332C481.233 91.9532 482.073 92.9032 482.933 93.8532C483.823 92.8232 484.693 91.8232 485.683 90.6832C484.583 89.8032 483.523 88.9732 482.393 88.0632C481.653 89.0232 480.993 89.8632 480.233 90.8332V90.8332Z" fill="black"/> +<path d="M501.443 199.263C502.553 199.213 503.813 200.413 503.783 201.493C503.763 202.333 502.463 203.433 501.443 203.483C500.363 203.533 499.393 202.543 499.393 201.383C499.393 200.203 500.243 199.323 501.443 199.273V199.263ZM501.223 202.833C501.793 202.333 502.433 202.033 502.493 201.653C502.553 201.273 502.013 200.813 501.743 200.393C501.353 200.693 500.733 200.933 500.633 201.313C500.533 201.673 500.953 202.193 501.233 202.843L501.223 202.833Z" fill="black"/> +<path d="M426.993 24.3431C426.963 26.0131 426.043 26.8331 424.783 26.7831C423.543 26.7431 422.893 26.0532 422.983 24.8632C423.063 23.7532 424.163 22.7332 425.273 23.0432C426.003 23.2432 426.573 24.0131 426.983 24.3431H426.993ZM424.883 23.7932C424.533 24.3332 424.143 24.7331 424.213 24.8531C424.403 25.1731 424.783 25.4031 425.133 25.5731C425.203 25.6131 425.653 25.1931 425.613 25.0831C425.493 24.7131 425.233 24.3832 424.873 23.7932H424.883Z" fill="black"/> +<path d="M337.763 8.61296C337.703 9.87296 336.643 10.893 335.443 10.833C334.253 10.773 333.493 9.98296 333.603 8.91296C333.713 7.76296 334.723 6.78296 335.743 6.82296C336.943 6.87296 337.813 7.64296 337.773 8.61296H337.763ZM335.603 7.64296C335.193 8.21296 334.803 8.57296 334.703 9.00296C334.663 9.18296 335.403 9.77296 335.523 9.71296C335.923 9.48296 336.293 9.08296 336.483 8.66296C336.543 8.52296 336.023 8.12296 335.603 7.64296Z" fill="black"/> +<path d="M391.473 10.3731C390.623 9.3831 389.683 8.2931 388.663 7.1031C389.543 6.1131 390.533 5.0031 391.483 3.9231C392.863 4.9631 394.273 6.0431 395.643 7.0831C394.423 8.0431 392.993 9.1731 391.463 10.3731H391.473Z" fill="#F0C3BA"/> +<path d="M480.243 90.8332C480.993 89.8632 481.653 89.0232 482.403 88.0632C483.543 88.9632 484.593 89.8032 485.693 90.6832C484.703 91.8232 483.833 92.8332 482.943 93.8532C482.093 92.9032 481.243 91.9532 480.243 90.8332V90.8332Z" fill="#EEEFFC"/> +<path d="M290.553 66.1831C287.573 63.6765 285.313 62.2731 283.773 61.9731C284.783 60.9431 285.873 60.1431 286.543 59.0731C287.203 58.0231 287.433 56.7031 287.963 55.2031C289.633 57.5231 291.013 59.8431 294.013 60.5531C291.393 61.5831 290.993 63.8631 290.563 66.1731L290.553 66.1831ZM291.193 60.8031C290.983 61.0131 290.773 61.2131 290.563 61.4231C289.813 60.3231 289.053 59.2331 288.263 58.0831C287.683 59.1531 287.163 60.1031 286.623 61.0931C287.693 61.8931 288.693 62.6331 289.693 63.3831C290.193 62.5231 290.683 61.6631 291.183 60.8031H291.193Z" fill="black"/> +<path d="M291.193 60.8031C290.693 61.6631 290.193 62.5231 289.703 63.3831C288.693 62.6331 287.693 61.8831 286.633 61.0931C287.173 60.1031 287.693 59.1531 288.273 58.0831C289.063 59.2331 289.813 60.3231 290.573 61.4231C290.783 61.2131 290.993 61.0131 291.203 60.8031H291.193Z" fill="white"/> +<path d="M501.223 202.843C500.943 202.193 500.523 201.683 500.623 201.313C500.733 200.933 501.343 200.693 501.733 200.393C502.013 200.813 502.543 201.283 502.483 201.653C502.423 202.033 501.793 202.333 501.213 202.833L501.223 202.843Z" fill="white"/> +<path d="M424.883 23.7932C425.233 24.3832 425.493 24.7032 425.623 25.0832C425.663 25.1932 425.213 25.6132 425.143 25.5732C424.793 25.4032 424.423 25.1632 424.223 24.8532C424.153 24.7332 424.543 24.3332 424.893 23.7932H424.883Z" fill="white"/> +<path d="M335.603 7.63318C336.023 8.10318 336.543 8.50318 336.483 8.65318C336.303 9.07318 335.923 9.47318 335.523 9.70318C335.403 9.77318 334.663 9.17318 334.703 8.99318C334.803 8.57318 335.193 8.21318 335.603 7.63318Z" fill="white"/> +<path d="M258.798 283.522C260.386 285.829 260.927 286.467 262.654 286.867C265.365 287.495 268.038 288.405 270.903 288.221C272.136 288.142 273.385 288.364 274.627 288.368C276.88 288.375 279.173 288.711 281.575 287.707C281.461 286.808 281.349 285.94 281.222 284.95C273.59 284.464 266.203 283.994 258.798 283.522ZM311.984 283.326C310.194 283.524 308.481 283.792 306.759 283.89C301.18 284.203 295.599 284.471 290.017 284.717C288.468 284.784 286.913 284.698 285.361 284.705C284.507 284.709 283.672 284.812 283.371 285.812C283.071 286.81 283.525 287.866 284.667 288.275C286.016 288.759 287.435 289.256 288.845 289.329C295.158 289.652 301.458 289.635 307.683 288.213C311.088 287.434 311.314 287.294 311.984 283.326ZM270.235 111.835C270.162 112.35 270.019 112.746 270.065 113.119C270.43 116.022 271.567 118.468 274.102 120.164C274.985 120.755 276.119 121.201 276.57 122.572C274.101 123.302 271.809 122.453 269.124 122.422C269.771 123.399 270.114 124.155 270.664 124.705C272.012 126.052 273.647 126.934 275.554 127.241C278.032 127.641 279.884 128.994 281.564 130.818C282.945 132.319 283.724 134.082 284.562 135.862C284.831 136.429 285.333 136.886 286.012 137.758C286.314 136.693 286.542 136.132 286.628 135.551C287.338 130.651 286.447 125.974 284.001 121.747C282.252 118.724 280.255 115.72 276.732 114.34C274.623 113.512 272.503 112.709 270.235 111.835ZM367.836 201.996C369.044 206.944 370.235 211.846 371.447 216.743C371.565 217.22 371.811 217.665 371.989 218.101C377.702 216.953 380.543 213.89 380.958 208.44C381.068 207 381.111 205.524 380.917 204.101C380.726 202.692 380.196 201.33 379.797 199.886C380.113 199.457 380.454 199.067 380.711 198.628C380.993 198.146 381.199 197.619 381.589 196.788C380.428 196.788 379.58 196.597 378.859 196.821C374.998 198.016 371.29 199.567 367.836 201.996ZM259.556 287.582C259.033 291.088 259.884 294.239 262.521 296.364C265.609 298.853 269.187 300.603 273.409 300.296C275.791 300.123 278.184 300.093 280.564 299.898C281.324 299.835 282.057 299.447 282.609 299.271C282.44 296.055 282.291 293.191 282.143 290.342C278.217 290.172 274.488 290.238 270.821 289.791C267.137 289.341 263.516 288.382 259.556 287.582ZM312.004 288.574C302.892 291.308 293.776 291.563 284.458 290.219C284.033 292.775 284.207 295.047 284.506 297.285C284.603 298.019 285.163 298.927 285.784 299.313C287.352 300.283 288.994 301.36 290.751 301.761C299.008 303.643 306.852 302.186 314.177 298.091C318.053 295.924 321.289 293.073 322.078 288.08C319.498 286.33 316.823 284.982 313.462 285.953C312.937 286.897 312.441 287.79 312.004 288.574ZM333.913 161.575C330.658 164.683 326.799 166.513 323.135 168.624C323.135 169.168 323.064 169.59 323.146 169.981C324.383 175.918 324.683 181.935 324.695 187.974C324.704 192.549 324.658 197.127 324.663 201.702C324.665 202.489 324.804 203.275 324.895 204.228C330.31 202.867 335.159 200.899 339.519 198.07C341.824 186.896 338.791 169.081 333.913 161.575ZM255.294 139.63C254.634 141.567 255.178 142.877 256.373 143.88C257.399 144.74 258.478 145.657 259.695 146.144C262.289 147.179 264.941 148.146 267.653 148.793C275.523 150.671 283.535 150.673 291.53 149.967C296.914 149.492 302.269 148.582 307.254 146.337C309.03 145.537 310.787 144.513 312.254 143.245C314.948 140.919 314.637 139.014 311.526 137.26C305.172 133.679 298.313 131.783 291.074 131.187C290.487 131.14 289.883 131.311 289.21 131.391C288.359 134.032 289.205 137.194 286.661 139.118C284.893 139.014 283.932 138.166 283.313 136.644C282.732 135.214 281.943 133.858 281.146 132.529C280.571 131.569 279.665 131.198 278.487 131.307C270.033 132.09 262.305 134.844 255.294 139.63ZM365.551 201.281C364.511 201.755 363.928 201.965 363.398 202.268C357.681 205.551 351.48 207.621 345.248 209.646C340.708 211.121 336.198 212.705 331.733 214.396C327.364 216.052 323.067 217.897 318.744 219.675C315.283 221.099 311.828 222.545 308.805 224.81C308.062 225.365 307.212 225.913 306.743 226.67C305.094 229.331 303.738 232.133 303.462 235.333C303.122 239.277 304.604 241.789 308.325 242.997C309.976 243.533 311.772 243.769 313.517 243.849C319.727 244.139 325.708 242.75 331.593 240.986C344.296 237.178 356.236 231.606 367.741 225.064C368.54 224.609 369.271 224.036 370.217 223.395C370.108 215.795 368.104 208.672 365.551 201.281ZM235.483 163.592C236.532 163.947 237.276 164.29 238.055 164.448C245.988 166.056 253.904 167.762 261.87 169.179C269.027 170.453 276.247 171.286 283.545 171.335C289.893 171.378 296.202 171.015 302.49 170.148C309.383 169.198 316.253 168.096 322.98 166.299C327.122 165.192 330.387 162.746 332.969 159.043C328.751 151.273 323.186 144.887 315.84 140.029C313.129 144.997 311.511 146.373 305.915 148.416C304.552 148.914 303.164 149.369 301.756 149.719C290.171 152.594 278.526 152.723 266.859 150.298C263.807 149.664 260.833 148.693 258.101 147.126C256.373 146.134 254.786 144.981 253.84 143.137C253.577 142.623 253.23 142.151 252.781 141.434C244.967 147.349 239.358 154.606 235.483 163.592ZM322.873 204.931C324.128 198.431 322.84 174.008 321.009 169.086C320.53 169.086 320.013 169.031 319.51 169.094C310.646 170.185 301.8 171.459 292.914 172.323C288.284 172.773 283.574 172.726 278.919 172.498C274.364 172.273 269.839 171.491 265.294 171.026C256.503 170.126 247.851 168.452 239.242 166.516C237.843 166.201 236.465 165.79 234.831 165.357C230.41 175.997 229.771 186.906 230.91 197.991C237.058 201.801 243.584 203.826 250.193 205.683C257.643 207.776 265.394 207.905 272.944 209.318C273.248 209.374 273.567 209.354 273.878 209.36C278.451 209.437 283.023 209.577 287.595 209.568C291.127 209.561 294.673 209.538 298.186 209.216C302.525 208.819 306.836 208.112 311.156 207.523C315.167 206.977 319.102 206.103 322.873 204.931ZM339.354 209.314C339.774 204.981 339.656 201.837 338.958 200.323C338.041 200.784 337.138 201.265 336.213 201.699C334.71 202.405 333.229 203.178 331.675 203.752C324.739 206.315 317.662 208.348 310.282 209.168C306.979 209.535 303.685 209.976 300.378 210.283C297.59 210.542 294.789 210.686 291.992 210.82C291.067 210.864 290.134 210.666 289.204 210.671C285.058 210.697 280.905 210.908 276.765 210.758C273.143 210.627 269.538 210.057 265.918 209.762C254.316 208.812 243.151 206.262 232.761 200.816C232.416 200.635 232.017 200.554 231.483 200.374C231.363 200.91 231.219 201.283 231.208 201.66C231.03 207.268 230.842 212.877 230.722 218.486C230.678 220.563 230.785 222.643 230.863 224.721C230.947 226.899 231.148 229.076 231.178 231.256C231.246 236.35 231.272 241.445 231.259 246.541C231.242 253.046 232.82 259.111 236.286 264.619C241.929 273.587 250.031 279.217 260.25 281.784C262.236 282.283 264.345 282.366 266.405 282.482C270.036 282.685 273.674 282.778 277.309 282.893C281.241 283.02 285.149 283.481 289.113 283.159C294.081 282.756 299.077 282.602 304.062 282.548C306.375 282.523 308.634 282.36 310.835 281.716C325.593 277.388 335.484 268.258 338.648 252.838C339.435 249.005 339.373 244.998 339.678 241.07C339.689 240.92 339.46 240.752 339.25 240.468C338.321 240.765 337.341 241.074 336.362 241.388C330.117 243.389 323.808 245.096 317.221 245.526C313.974 245.737 310.762 245.525 307.678 244.468C303.206 242.934 301.052 239.343 302.082 234.698C302.635 232.194 303.845 229.84 304.691 227.396C305.335 225.536 306.582 224.227 308.144 223.115C311.137 220.984 314.48 219.556 317.854 218.195C324.789 215.399 331.738 212.638 338.676 209.853C338.861 209.779 339 209.601 339.354 209.314ZM276.507 129.644C275.515 129.274 274.564 128.948 273.636 128.568C271.251 127.59 269.373 126.015 268.156 123.721C267.435 122.363 267.417 121.8 267.874 119.736C269.513 120.186 270.785 121.872 272.96 121.238C272.535 120.754 272.207 120.326 271.827 119.952C269.787 117.947 269.016 115.383 268.654 112.662C268.514 111.62 268.659 110.586 269.603 109.627C270.088 109.837 270.629 110.054 271.156 110.299C272.096 110.737 272.987 111.341 273.972 111.615C281.843 113.803 285.701 119.693 288.181 126.88C288.45 127.657 288.644 128.46 288.908 129.373C291.597 129.786 294.257 130.124 296.891 130.611C305.335 132.173 312.949 135.642 319.555 141.106C332.283 151.638 339.919 165.036 341.867 181.56C342.569 187.518 342.295 193.386 340.662 199.177C340.467 199.868 340.3 200.626 340.377 201.325C340.647 203.772 341.03 206.207 341.397 208.836C349.975 206.599 357.967 203.478 365.62 199.469C366.255 199.808 366.789 200.092 367.356 200.395C367.625 200.301 367.928 200.227 368.2 200.094C371.09 198.681 373.93 197.159 376.878 195.885C378.071 195.37 379.506 195.153 380.81 195.219C382.834 195.323 383.516 196.667 382.674 198.473C382.414 199.031 382.017 199.526 381.603 200.173C381.828 200.894 382.078 201.674 382.311 202.457C383.366 206.009 382.876 209.451 381.537 212.821C380.646 215.063 378.915 216.534 376.885 217.684C375.263 218.602 373.6 219.451 371.816 220.406C371.753 221.761 371.693 223.09 371.618 224.699C368.014 226.704 364.372 228.915 360.568 230.799C355.076 233.52 349.51 236.105 343.884 238.535C341.968 239.363 341.055 240.428 341.154 242.474C341.209 243.614 341.113 244.761 341.108 245.905C341.081 251.501 339.823 256.755 337.343 261.818C335.544 265.491 333.474 268.933 330.767 271.957C326.743 276.448 321.828 279.693 316.203 281.871C315.672 282.075 315.212 282.488 314.763 282.857C314.663 282.94 314.748 283.248 314.748 283.609C318 284.194 321.192 285.035 323.744 287.756C323.28 290.596 322.618 293.421 320.437 295.54C318.425 297.495 316.167 299.133 313.593 300.374C306.097 303.987 298.282 304.865 290.195 303.215C287.989 302.763 285.894 301.761 283.319 300.861C280.231 301.535 276.621 301.787 272.982 301.848C268.322 301.927 264.406 299.964 261.109 297.044C258.461 294.698 257.252 291.416 257.959 287.716C258.056 287.214 258.229 286.726 258.377 286.193C257.064 283.857 255.363 282.262 252.808 281.102C239.062 274.868 231.496 263.937 229.681 249.083C229.232 245.403 229.821 241.607 229.774 237.866C229.674 229.863 229.395 221.861 229.34 213.858C229.311 209.599 229.614 205.338 229.725 201.077C229.753 200.048 229.736 198.988 229.534 197.984C227.185 186.276 228.641 174.993 233.455 164.147C237.752 154.465 244.036 146.278 252.587 139.941C258.855 135.296 265.809 132.128 273.485 130.61C274.401 130.429 275.316 130.244 276.229 130.05C276.286 130.037 276.32 129.923 276.507 129.644Z" fill="black"/> +<path d="M339.354 209.314C339 209.601 338.86 209.78 338.676 209.853C331.737 212.637 324.788 215.399 317.854 218.195C314.48 219.556 311.137 220.983 308.144 223.115C306.581 224.227 305.335 225.535 304.691 227.396C303.844 229.84 302.635 232.194 302.082 234.698C301.053 239.343 303.206 242.934 307.678 244.468C310.762 245.525 313.975 245.737 317.221 245.526C323.807 245.096 330.117 243.389 336.363 241.387C337.341 241.074 338.321 240.765 339.25 240.468C339.459 240.752 339.689 240.92 339.678 241.07C339.373 244.998 339.434 249.005 338.648 252.837C335.485 268.258 325.593 277.388 310.834 281.716C308.634 282.361 306.375 282.523 304.062 282.548C299.076 282.602 294.081 282.756 289.113 283.159C285.149 283.481 281.241 283.02 277.309 282.895C273.674 282.777 270.036 282.685 266.405 282.482C264.345 282.366 262.236 282.283 260.251 281.784C250.032 279.218 241.93 273.587 236.287 264.619C232.821 259.111 231.241 253.045 231.259 246.541C231.272 241.445 231.246 236.35 231.178 231.256C231.148 229.077 230.947 226.899 230.864 224.721C230.785 222.643 230.678 220.563 230.722 218.487C230.841 212.877 231.03 207.268 231.207 201.66C231.22 201.283 231.364 200.91 231.482 200.374C232.017 200.554 232.416 200.635 232.761 200.816C243.151 206.262 254.315 208.812 265.918 209.762C269.538 210.057 273.143 210.627 276.765 210.759C280.905 210.909 285.058 210.697 289.204 210.671C290.134 210.665 291.067 210.864 291.992 210.82C294.789 210.686 297.59 210.542 300.378 210.282C303.685 209.976 306.979 209.535 310.282 209.169C317.663 208.348 324.738 206.315 331.675 203.752C333.229 203.178 334.71 202.405 336.213 201.699C337.137 201.265 338.041 200.784 338.958 200.323C339.656 201.836 339.774 204.981 339.354 209.314Z" fill="white"/> +<path d="M322.873 204.932C319.102 206.104 315.168 206.978 311.157 207.524C306.836 208.111 302.525 208.818 298.185 209.216C294.673 209.539 291.127 209.561 287.596 209.568C283.023 209.576 278.451 209.438 273.878 209.36C273.567 209.355 273.249 209.374 272.945 209.317C265.394 207.905 257.643 207.777 250.193 205.683C243.584 203.825 237.057 201.802 230.911 197.991C229.772 186.906 230.411 175.998 234.831 165.357C236.465 165.789 237.843 166.201 239.242 166.515C247.85 168.451 256.503 170.126 265.294 171.025C269.839 171.491 274.364 172.273 278.919 172.497C283.573 172.727 288.283 172.772 292.914 172.323C301.8 171.46 310.646 170.184 319.51 169.093C320.012 169.032 320.529 169.085 321.009 169.085C322.84 174.009 324.128 198.432 322.873 204.932Z" fill="white"/> +<path d="M235.483 163.592C239.358 154.605 244.966 147.348 252.78 141.434C253.23 142.15 253.576 142.623 253.84 143.137C254.786 144.982 256.373 146.133 258.101 147.126C260.832 148.694 263.806 149.664 266.858 150.298C278.525 152.724 290.17 152.593 301.755 149.719C303.164 149.37 304.551 148.914 305.915 148.416C311.511 146.373 313.129 144.996 315.84 140.028C323.185 144.887 328.749 151.274 332.967 159.043C330.387 162.745 327.121 165.192 322.979 166.298C316.251 168.095 309.383 169.199 302.488 170.149C296.201 171.015 289.892 171.378 283.544 171.334C276.247 171.286 269.027 170.454 261.87 169.179C253.903 167.761 245.988 166.056 238.055 164.449C237.274 164.291 236.532 163.948 235.483 163.592Z" fill="white"/> +<path d="M365.55 201.281C368.103 208.672 370.107 215.795 370.216 223.394C369.27 224.036 368.54 224.609 367.741 225.064C356.236 231.606 344.295 237.178 331.592 240.986C325.707 242.75 319.726 244.139 313.516 243.849C311.773 243.769 309.977 243.533 308.325 242.997C304.603 241.789 303.121 239.277 303.461 235.333C303.737 232.133 305.093 229.331 306.743 226.67C307.212 225.913 308.063 225.365 308.804 224.81C311.828 222.545 315.282 221.099 318.743 219.675C323.066 217.897 327.364 216.052 331.733 214.396C336.197 212.705 340.707 211.121 345.247 209.646C351.479 207.621 357.681 205.551 363.399 202.268C363.927 201.965 364.51 201.755 365.55 201.281Z" fill="white"/> +<path d="M255.293 139.63C262.304 134.844 270.033 132.09 278.487 131.308C279.664 131.199 280.571 131.569 281.146 132.53C281.942 133.858 282.732 135.214 283.313 136.644C283.932 138.166 284.894 139.014 286.66 139.119C289.205 137.195 288.358 134.032 289.21 131.391C289.882 131.312 290.487 131.139 291.073 131.187C298.312 131.783 305.173 133.68 311.526 137.261C314.636 139.014 314.948 140.92 312.253 143.246C310.786 144.513 309.03 145.537 307.254 146.337C302.27 148.583 296.913 149.492 291.529 149.967C283.534 150.673 275.522 150.671 267.653 148.793C264.941 148.146 262.289 147.179 259.695 146.144C258.477 145.658 257.398 144.74 256.373 143.879C255.177 142.877 254.634 141.567 255.293 139.63Z" fill="white"/> +<path d="M333.913 161.576C338.791 169.081 341.824 186.897 339.519 198.069C335.159 200.899 330.309 202.867 324.895 204.227C324.804 203.276 324.664 202.488 324.663 201.702C324.658 197.126 324.704 192.55 324.695 187.974C324.683 181.934 324.383 175.918 323.146 169.982C323.064 169.591 323.135 169.168 323.135 168.625C326.799 166.513 330.658 164.682 333.913 161.576Z" fill="#CAE4E4"/> +<path d="M312.004 288.575C312.441 287.79 312.937 286.897 313.462 285.953C316.823 284.983 319.498 286.33 322.077 288.079C321.289 293.073 318.053 295.925 314.177 298.091C306.852 302.187 299.007 303.644 290.75 301.76C288.994 301.36 287.351 300.284 285.784 299.313C285.161 298.927 284.603 298.02 284.504 297.284C284.207 295.046 284.033 292.776 284.458 290.218C293.774 291.563 302.891 291.307 312.004 288.575Z" fill="white"/> +<path d="M259.555 287.582C263.516 288.381 267.138 289.341 270.82 289.792C274.487 290.238 278.216 290.172 282.143 290.341C282.291 293.191 282.441 296.055 282.608 299.27C282.056 299.447 281.323 299.836 280.563 299.898C278.184 300.094 275.79 300.123 273.408 300.296C269.187 300.603 265.608 298.854 262.52 296.364C259.884 294.239 259.033 291.088 259.555 287.582Z" fill="white"/> +<path d="M367.835 201.996C371.29 199.567 374.998 198.017 378.859 196.821C379.58 196.597 380.428 196.788 381.589 196.788C381.198 197.619 380.993 198.146 380.711 198.628C380.454 199.067 380.113 199.457 379.797 199.886C380.195 201.33 380.724 202.692 380.916 204.101C381.11 205.524 381.066 207 380.957 208.44C380.543 213.89 377.701 216.953 371.987 218.101C371.811 217.665 371.565 217.22 371.446 216.743C370.235 211.847 369.044 206.944 367.835 201.996Z" fill="white"/> +<path d="M270.235 111.835C272.502 112.708 274.622 113.513 276.733 114.34C280.254 115.72 282.251 118.724 284 121.747C286.447 125.973 287.338 130.651 286.627 135.55C286.542 136.132 286.314 136.692 286.012 137.758C285.332 136.887 284.83 136.43 284.562 135.863C283.723 134.083 282.945 132.32 281.563 130.818C279.884 128.993 278.032 127.642 275.554 127.242C273.646 126.935 272.012 126.053 270.664 124.705C270.114 124.156 269.77 123.4 269.123 122.421C271.808 122.453 274.1 123.302 276.57 122.572C276.119 121.202 274.986 120.755 274.101 120.163C271.567 118.467 270.429 116.022 270.065 113.119C270.018 112.747 270.161 112.35 270.235 111.835Z" fill="white"/> +<path d="M311.984 283.326C311.314 287.294 311.089 287.435 307.683 288.213C301.458 289.634 295.158 289.651 288.846 289.329C287.435 289.257 286.017 288.759 284.666 288.276C283.525 287.867 283.072 286.81 283.372 285.812C283.673 284.812 284.507 284.71 285.361 284.705C286.913 284.698 288.468 284.785 290.018 284.716C295.599 284.472 301.18 284.203 306.758 283.89C308.481 283.793 310.194 283.524 311.984 283.326Z" fill="white"/> +<path d="M258.798 283.522C266.203 283.994 273.589 284.464 281.222 284.95C281.349 285.94 281.46 286.808 281.575 287.707C279.172 288.711 276.88 288.375 274.627 288.368C273.385 288.364 272.136 288.142 270.903 288.221C268.037 288.405 265.365 287.496 262.653 286.867C260.927 286.467 260.386 285.829 258.798 283.522Z" fill="white"/> +<path d="M221.908 245.253C122.036 183.082 89.1337 169.67 0.0472646 166.896L0.0472644 401.958L0.0472604 504.747L559.194 504.748C516.33 472.079 523.684 471.993 411.57 466.079C271.428 458.687 346.749 322.968 221.908 245.253Z" fill="#3DA3B4" stroke="black"/> +</g> +<defs> +<clipPath id="clip0_424_3"> +<rect width="720" height="499" fill="white"/> +</clipPath> +</defs> +</svg> diff --git a/frontend/app/types/account/account.js b/frontend/app/types/account/account.ts similarity index 57% rename from frontend/app/types/account/account.js rename to frontend/app/types/account/account.ts index bb1c058cd..00f4e30d4 100644 --- a/frontend/app/types/account/account.js +++ b/frontend/app/types/account/account.ts @@ -1,7 +1,27 @@ -import Member from 'Types/member'; -import Limit from './limit'; +import Member, { IMember } from 'Types/member'; +import Limit, { ILimits } from './limit'; import { DateTime } from 'luxon'; +// TODO types for mobx and all +export interface IAccount extends IMember { + changePassword?: any + limits: ILimits + banner: string + email: string + verifiedEmail: string + id: string + smtp: boolean + license: string + expirationDate?: DateTime + permissions: string[] + iceServers: string + hasPassword: boolean + apiKey: string + tenantKey: string + edition: string + optOut: string +} + export default Member.extend({ changePassword: undefined, limits: Limit(), diff --git a/frontend/app/types/account/limit.js b/frontend/app/types/account/limit.ts similarity index 61% rename from frontend/app/types/account/limit.js rename to frontend/app/types/account/limit.ts index f5274f7b0..7ded53b51 100644 --- a/frontend/app/types/account/limit.js +++ b/frontend/app/types/account/limit.ts @@ -1,6 +1,16 @@ import Record from 'Types/Record'; import { Map } from 'immutable'; +interface ILimitValue { + limit: number + remaining: number +} + +export interface ILimits { + teamMember: ILimitValue + sites: ILimitValue +} + const defaultValues = Map({ limit: 0, remaining: 0 }); const Limit = Record({ teamMember: defaultValues, diff --git a/frontend/app/types/address.js b/frontend/app/types/address.js deleted file mode 100644 index 8d8e2ccf2..000000000 --- a/frontend/app/types/address.js +++ /dev/null @@ -1,20 +0,0 @@ -import Record from 'Types/Record'; - -export default Record({ - line1: '', - postal_code: '', - city: '', - state: '', - country: '', -}, { - methods: { - validate() { - return true; - }, - toData() { - const js = this.toJS(); - delete js.key; - return js; - }, - }, -}); diff --git a/frontend/app/types/alert.js b/frontend/app/types/alert.js index 244047a45..e69de29bb 100644 --- a/frontend/app/types/alert.js +++ b/frontend/app/types/alert.js @@ -1,108 +0,0 @@ -import Record from 'Types/Record'; -import { notEmptyString, validateName, validateNumber, validateEmail } from 'App/validate'; -import { List, Map } from 'immutable'; -import { alertMetrics as metrics, alertConditions as conditions } from 'App/constants'; -// import Filter from './filter'; - -const metricsMap = {} -const conditionsMap = {} -metrics.forEach(m => { metricsMap[m.value] = m }); -conditions.forEach(c => { conditionsMap[c.value] = c }); - -export default Record({ - alertId: '', - projectId: undefined, - name: 'Untitled Alert', - description: '', - active: true, - currentPeriod: 15, - previousPeriod: 15, - detectionMethod: 'threshold', - change: 'change', - query: Map({ left: '', operator: '', right: ''}), - options: Map({ currentPeriod: 15, previousPeriod: 15 }), - createdAt: undefined, - - slack: false, - slackInput: [], - webhook: false, - webhookInput: [], - email: false, - emailInput: [], - hasNotification: false, - metric: '', - condition: '', -}, { - idKey: 'alertId', - methods: { - validate() { - return notEmptyString(this.name) && - this.query.left && this.query.right && validateNumber(this.query.right) && this.query.right > 0 && this.query.operator && - (this.slack ? this.slackInput.length > 0 : true) && - (this.email ? this.emailInput.length > 0 : true) && - (this.webhook ? this.webhookInput.length > 0 : true); - }, - toData() { - const js = this.toJS(); - - const options = { message: [] } - if (js.slack && js.slackInput) - options.message = options.message.concat(js.slackInput.map(i => ({ type: 'slack', value: i }))) - // options.message.push({ type: 'slack', value: js.slackInput }) - if (js.email && js.emailInput) - options.message = options.message.concat(js.emailInput.map(i => ({ type: 'email', value: i }))) - // options.message.push({ type: 'email', value: js.emailInput }) - if (js.webhook && js.webhookInput) - options.message = options.message.concat(js.webhookInput.map(i => ({ type: 'webhook', value: i }))) - // options.message.push({ type: 'webhook', value: js.webhookInput }) - - options.previousPeriod = js.previousPeriod - options.currentPeriod = js.currentPeriod - - js.detection_method = js.detectionMethod; - delete js.slack; - delete js.webhook; - delete js.email; - delete js.slackInput; - delete js.webhookInput; - delete js.emailInput; - delete js.hasNotification; - delete js.metric; - delete js.condition; - delete js.currentPeriod; - delete js.previousPeriod; - - return { ...js, options: options }; - }, - }, - fromJS: (item) => { - const options = item.options || { currentPeriod: 15, previousPeriod: 15, message: [] }; - const query = item.query || { left: '', operator: '', right: ''}; - - const slack = List(options.message).filter(i => i.type === 'slack'); - const email = List(options.message).filter(i => i.type === 'email'); - const webhook = List(options.message).filter(i => i.type === 'webhook'); - - return { - ...item, - metric: metricsMap[query.left], - condition: item.query ? conditionsMap[item.query.operator] : {}, - detectionMethod: item.detectionMethod || item.detection_method, - query: query, - options: options, - previousPeriod: options.previousPeriod, - currentPeriod: options.currentPeriod, - - slack: slack.size > 0, - slackInput: slack.map(i => parseInt(i.value)).toJS(), - - email: email.size > 0, - emailInput: email.map(i => i.value).toJS(), - - webhook: webhook.size > 0, - webhookInput: webhook.map(i => parseInt(i.value)).toJS(), - - hasNotification: !!slack || !!email || !!webhook - } - }, -}); diff --git a/frontend/app/types/alert.ts b/frontend/app/types/alert.ts new file mode 100644 index 000000000..124e393e2 --- /dev/null +++ b/frontend/app/types/alert.ts @@ -0,0 +1,203 @@ +import { notEmptyString, validateNumber } from 'App/validate'; +import { alertMetrics as metrics, alertConditions as conditions } from 'App/constants'; +import { makeAutoObservable } from 'mobx' + +const metricsMap = {} +const conditionsMap = {} +// @ts-ignore +metrics.forEach(m => { metricsMap[m.value] = m }); +// @ts-ignore +conditions.forEach(c => { conditionsMap[c.value] = c }); + +export interface IAlert { + alertId: string; + projectId?: string; + name: string; + description: string; + active: boolean; + currentPeriod: number; + previousPeriod: number; + detectionMethod: string; + detection_method?: string; + change: string; + seriesName: string; + query: { left: string, operator: string, right: string }; + options: { currentPeriod: number, previousPeriod: number, message: {type: string, value: string}[] }; + createdAt?: number; + slack: boolean; + slackInput: string[]; + webhook: boolean; + webhookInput: string[]; + email: boolean; + emailInput: string[]; + msteams: boolean; + msteamsInput: string[]; + hasNotification: boolean; + metric: { unit: any }; + condition: string; +} + +const getDefaults = () => ({ + alertId: '', + projectId: undefined, + name: 'Untitled Alert', + description: '', + active: true, + currentPeriod: 15, + previousPeriod: 15, + detectionMethod: 'threshold', + change: 'change', + query: { left: '', operator: '', right: '' }, + options: { currentPeriod: 15, previousPeriod: 15 }, + createdAt: undefined, + + slack: false, + slackInput: [], + webhook: false, + webhookInput: [], + email: false, + emailInput: [], + msteams: false, + msteamsInput: [], + hasNotification: false, + metric: '', + condition: '', +}) as unknown as IAlert + +export default class Alert { + alertId: IAlert["alertId"] + projectId?: IAlert["projectId"] + name: IAlert["name"] + description: IAlert["description"] + active: IAlert["active"] + currentPeriod: IAlert["currentPeriod"] + previousPeriod: IAlert["previousPeriod"] + detectionMethod: IAlert["detectionMethod"] + detection_method: IAlert["detection_method"] + change: IAlert["change"] + seriesName: IAlert["seriesName"] + query: IAlert["query"] + options: IAlert["options"] + createdAt?: IAlert["createdAt"] + slack: IAlert["slack"] + slackInput: IAlert["slackInput"] + webhook: IAlert["webhook"] + webhookInput: IAlert["webhookInput"] + email: IAlert["email"] + emailInput: IAlert["emailInput"] + msteams: IAlert["msteams"] + msteamsInput: IAlert["msteamsInput"] + hasNotification: IAlert["hasNotification"] + metric: IAlert["metric"] + condition: IAlert["condition"] + isExists = false + + constructor(item: Partial<IAlert> = {}, isExists: boolean) { + const defaults = getDefaults() + Object.assign(defaults, item) + + const options = defaults.options || { currentPeriod: 15, previousPeriod: 15, message: [] }; + const query = defaults.query || { left: '', operator: '', right: ''}; + + const slack = options.message?.filter(i => i.type === 'slack') || []; + const email = options.message?.filter(i => i.type === 'email') || []; + const webhook = options.message?.filter(i => i.type === 'webhook') || []; + const msteams = options.message?.filter(i => i.type === 'msteams') || []; + + Object.assign(this, { + ...defaults, + // @ts-ignore + metric: metricsMap[query.left], + alertId: String(defaults.alertId), + // @ts-ignore TODO + condition: defaults.query ? conditionsMap[defaults.query.operator] : {}, + detectionMethod: defaults.detectionMethod || defaults.detection_method, + query: query, + options: options, + previousPeriod: options.previousPeriod, + currentPeriod: options.currentPeriod, + + slack: slack.length > 0, + slackInput: slack.map(i => parseInt(i.value)), + + msteams: msteams.length > 0, + msteamsInput: msteams.map(i => parseInt(i.value)), + + email: email.length > 0, + emailInput: email.map(i => i.value), + + webhook: webhook.length > 0, + webhookInput: webhook.map(i => parseInt(i.value)), + + hasNotification: !!slack || !!email || !!webhook, + isExists, + }) + + makeAutoObservable(this) + } + + validate() { + return notEmptyString(this.name) && + this.query.left && this.query.right && validateNumber(this.query.right) && parseInt(this.query.right, 10) > 0 && this.query.operator && + (this.slack ? this.slackInput.length > 0 : true) && + (this.email ? this.emailInput.length > 0 : true) && + (this.msteams ? this.msteamsInput.length > 0 : true) && + (this.webhook ? this.webhookInput.length > 0 : true); + } + + + toData() { + const js = { ...this }; + + const options = { message: [], previousPeriod: 0, currentPeriod: 0 } + if (js.slack && js.slackInput) + // @ts-ignore + options.message = options.message.concat(js.slackInput.map(i => ({ type: 'slack', value: i }))) + if (js.email && js.emailInput) + // @ts-ignore + options.message = options.message.concat(js.emailInput.map(i => ({ type: 'email', value: i }))) + if (js.webhook && js.webhookInput) + // @ts-ignore + options.message = options.message.concat(js.webhookInput.map(i => ({ type: 'webhook', value: i }))) + if (js.msteams && js.msteamsInput) + // @ts-ignore + options.message = options.message.concat(js.msteamsInput.map(i => ({ type: 'msteams', value: i }))) + + options.previousPeriod = js.previousPeriod + options.currentPeriod = js.currentPeriod + + js.detection_method = js.detectionMethod; + // @ts-ignore + delete js.slack; + // @ts-ignore + delete js.webhook; + // @ts-ignore + delete js.email; + // @ts-ignore + delete js.slackInput; + // @ts-ignore + delete js.webhookInput; + // @ts-ignore + delete js.emailInput; + // @ts-ignore + delete js.msteams; + // @ts-ignore + delete js.msteamsInput; + // @ts-ignore + delete js.hasNotification; + // @ts-ignore + delete js.metric; + // @ts-ignore + delete js.condition; + // @ts-ignore + delete js.currentPeriod; + // @ts-ignore + delete js.previousPeriod; + + return { ...js, options: options }; + } + + exists() { + return this.isExists + } +} diff --git a/frontend/app/types/appTest.js b/frontend/app/types/appTest.js deleted file mode 100644 index 248b759b7..000000000 --- a/frontend/app/types/appTest.js +++ /dev/null @@ -1,87 +0,0 @@ -import { Record, List, Set } from 'immutable'; -import { validateName } from 'App/validate'; -import { DateTime } from 'luxon'; -import Run from './run'; -import Step from './step'; - -class Test extends Record({ - testId: undefined, - name: 'Unnamed Test', - steps: List(), - stepsCount: undefined, - framework: 'selenium', - sessionId: undefined, - generated: false, - tags: Set(), - runHistory: List(), - editedAt: undefined, - seqId: undefined, - seqChange: false, - uptime: 0, -}) { - // ???TODO - // idKey = "testId" - - exists() { - return this.testId !== undefined; - } - - validate() { - if (this.steps.size === 0) return false; - - return validateName(this.name, { - empty: false, - admissibleChars: ':-', - }); - } - - isComplete() { - return this.stepsCount === this.steps.size; - } - - // not the best code - toData() { - const js = this - .update('steps', steps => steps.map(step => step.toData())) - .toJS(); - - if (js.seqChange) { - const { testId, seqId } = js; - return { testId, seqId }; - } - - delete js.stepsCount; - delete js.seqChange; - - return js; - } - // not the best code -} - -const fromJS = (test = {}) => { - if (test instanceof Test) return test; - - const stepsLength = test.steps && test.steps.length; // - const editedAt = test.editedAt ? DateTime.fromMillis(test.editedAt) : undefined; - - const lastRun = Run(test.lastRun); - const runHistory = List(test.runHistory) // TODO: GOOD ENDPOINTS - .map(run => { - if (typeof run === 'string') { - return run === lastRun.runId - ? lastRun - : Run({ runId: run }) - } - return Run(run); - }); - - return new Test({ ...test, editedAt, uptime: parseInt(test.passed / test.count * 100) || 0 }) - .set('stepsCount', typeof test.stepsCount === 'number' - ? test.stepsCount - : stepsLength) // - .set('runHistory', runHistory) - .set('steps', List(test.steps).map(Step)) - .set('tags', test.tags && Set(test.tags.map(t => t.toLowerCase()))); -}; - -export default fromJS; diff --git a/frontend/app/types/dashboard/applicationActivity.js b/frontend/app/types/dashboard/applicationActivity.js deleted file mode 100644 index f24b063b8..000000000 --- a/frontend/app/types/dashboard/applicationActivity.js +++ /dev/null @@ -1,21 +0,0 @@ -import Record from 'Types/Record'; - -export default Record({ - avgPageLoad: 0, - avgPageLoadProgress: 0, - avgImgLoad: 0, - avgImgLoadProgress: 0, - avgReqLoad: 0, - avgReqLoadProgress: 0, -}, { - // fromJS: aa => ({ - // avgPageLoad: aa.avgDom, - // avgPageLoadProgress: aa.avgDomProgress, - // avgImgLoad: aa.avgLoad, - // avgImgLoadProgress: aa.avgLoadProgress, - // avgReqLoad: aa.avgFirstPixel, - // avgReqLoadProgress: aa.avgFirstPixelProgress, - // ...aa, - // }), -}); - diff --git a/frontend/app/types/dashboard/callWithErrors.js b/frontend/app/types/dashboard/callWithErrors.js deleted file mode 100644 index 3c3aefd7c..000000000 --- a/frontend/app/types/dashboard/callWithErrors.js +++ /dev/null @@ -1,13 +0,0 @@ -import Record from 'Types/Record'; - -export default Record({ - method: '', - urlHostpath: '', - allRequests: '', - '4xx': '', - '5xx': '' -}, { - // fromJS: pm => ({ - // ...pm, - // }), -}); \ No newline at end of file diff --git a/frontend/app/types/dashboard/crashes.js b/frontend/app/types/dashboard/crashes.js deleted file mode 100644 index 0986dddeb..000000000 --- a/frontend/app/types/dashboard/crashes.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const Crashes = Record({ - chart: [], - browsers: [] -}); - - -function fromJS(data = {}) { - if (data instanceof Crashes) return data; - return new Crashes(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/customMetric.js b/frontend/app/types/dashboard/customMetric.js deleted file mode 100644 index 9dd374b56..000000000 --- a/frontend/app/types/dashboard/customMetric.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const CustomMetric = Record({ - avg: undefined, - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof CustomMetric) return data; - return new CustomMetric(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/domBuildingTime copy.js b/frontend/app/types/dashboard/domBuildingTime copy.js deleted file mode 100644 index 258902f60..000000000 --- a/frontend/app/types/dashboard/domBuildingTime copy.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const DomBuildingTime = Record({ - avg: undefined, - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof DomBuildingTime) return data; - return new DomBuildingTime(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/domBuildingTime.js b/frontend/app/types/dashboard/domBuildingTime.js deleted file mode 100644 index 258902f60..000000000 --- a/frontend/app/types/dashboard/domBuildingTime.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const DomBuildingTime = Record({ - avg: undefined, - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof DomBuildingTime) return data; - return new DomBuildingTime(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/err.js b/frontend/app/types/dashboard/err.js deleted file mode 100644 index cd869b60a..000000000 --- a/frontend/app/types/dashboard/err.js +++ /dev/null @@ -1,23 +0,0 @@ -import Record from 'Types/Record'; -import { convertNumberRange } from 'App/utils'; - -export default Record({ - error: '', - count: undefined, - sessions: undefined, - firstOccurrenceAt: undefined, - lastOccurrenceAt: undefined, - startTimestamp: undefined, - endTimestamp: undefined, - chart: [], -}, { - fromJS: ({ chart = [], ...err }) => { - const oldMax = [ ...chart ].sort((a, b) => b.count - a.count)[ 0 ].count; - const formattedChart = chart.map(({ count, ...rest }) => - ({ count: convertNumberRange(oldMax, 0, 2, 20, count), ...rest })); - return { - ...err, - chart: formattedChart, - } - } -}); \ No newline at end of file diff --git a/frontend/app/types/dashboard/errors.js b/frontend/app/types/dashboard/errors.js deleted file mode 100644 index 8d56e4c33..000000000 --- a/frontend/app/types/dashboard/errors.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Record } from 'immutable'; - -const Errors = Record({ - count: undefined, - progress: undefined, - impactedSessions: undefined, - impactedSessionsProgress: undefined, - chart: [], -}); - -function fromJS(errors = {}) { - if (errors instanceof Errors) return errors; - return new Errors(errors); -} - -export default fromJS; diff --git a/frontend/app/types/dashboard/errorsByOrigin.js b/frontend/app/types/dashboard/errorsByOrigin.js deleted file mode 100644 index bdbceab60..000000000 --- a/frontend/app/types/dashboard/errorsByOrigin.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Record } from 'immutable'; - -const ErrorsByOrigin = Record({ - chart: [] -}); - - -function fromJS(data = {}) { - if (data instanceof ErrorsByOrigin) return data; - return new ErrorsByOrigin(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/errorsByType.js b/frontend/app/types/dashboard/errorsByType.js deleted file mode 100644 index ccedc566e..000000000 --- a/frontend/app/types/dashboard/errorsByType.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Record } from 'immutable'; - -const ErrorsByType = Record({ - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof ErrorsByType) return data; - return new ErrorsByType(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/helper.js b/frontend/app/types/dashboard/helper.ts similarity index 54% rename from frontend/app/types/dashboard/helper.js rename to frontend/app/types/dashboard/helper.ts index f6e819da8..97a7c17e8 100644 --- a/frontend/app/types/dashboard/helper.js +++ b/frontend/app/types/dashboard/helper.ts @@ -1,30 +1,9 @@ -export const getDayStartAndEndTimestamps = (date) => { - const start = moment(date).startOf('day').valueOf(); - const end = moment(date).endOf('day').valueOf(); - return { start, end }; -}; - -// const getPerformanceDensity = (period) => { -// switch (period) { -// case HALF_AN_HOUR: -// return 30; -// case WEEK: -// return 84; -// case MONTH: -// return 90; -// case DAY: -// return 48; -// default: -// return 48; -// } -// }; - const DAY = 1000 * 60 * 60 * 24; const WEEK = DAY * 8; -const startWithZero = num => (num < 10 ? `0${ num }` : `${ num }`); +const startWithZero = (num: number) => (num < 10 ? `0${ num }` : `${ num }`); const weekdays = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]; -// const months = [ "January", "February" ]; + export const getTimeString = (ts, period) => { const date = new Date(ts); const diff = period.endTimestamp - period.startTimestamp; @@ -41,11 +20,11 @@ export const getTimeString = (ts, period) => { export const getChartFormatter = period => (data = []) => data.map(({ timestamp, ...rest }) => ({ time: getTimeString(timestamp, period), ...rest, timestamp })); -export const getStartAndEndTimestampsByDensity = (current, start, end, density) => { +export const getStartAndEndTimestampsByDensity = (current: number, start: number, end: number, density: number) => { const diff = end - start; const step = Math.floor(diff / density); const currentIndex = Math.floor((current - start) / step); - const startTimestamp = parseInt(start + currentIndex * step); - const endTimestamp = parseInt(startTimestamp + step); + const startTimestamp = start + currentIndex * step; + const endTimestamp = startTimestamp + step; return { startTimestamp, endTimestamp }; }; \ No newline at end of file diff --git a/frontend/app/types/dashboard/image.js b/frontend/app/types/dashboard/image.js deleted file mode 100644 index 57f5f7507..000000000 --- a/frontend/app/types/dashboard/image.js +++ /dev/null @@ -1,16 +0,0 @@ -import Record from 'Types/Record'; - -const getName = (url = '') => url.split('/').filter(part => !!part).pop(); - -export default Record({ - avgDuration: undefined, - sessions: undefined, - chart: [], - url: '', - name: '', -}, { - fromJS: (image) => ({ - ...image, - name: getName(image.url), - }) -}); diff --git a/frontend/app/types/dashboard/index.js b/frontend/app/types/dashboard/index.js deleted file mode 100644 index c250d64d6..000000000 --- a/frontend/app/types/dashboard/index.js +++ /dev/null @@ -1,587 +0,0 @@ -import { Map, List } from 'immutable'; -import Session from 'Types/session'; -import { camelCased } from 'App/utils'; - -import { getChartFormatter } from './helper'; -import ProcessedSessions from './processedSessions'; -import DomBuildingTime from './domBuildingTime'; -import MemoryConsumption from './memoryConsumption'; -import ResponseTime from './responseTime'; -import ErrorsByType from './errorsByType'; -import OverviewWidget from './overviewWidget'; -import TopDomains from './topDomains'; -import SpeedLocation from './speedLocation'; -import SessionsPerBrowser from './sessionsPerBrowser'; -import ErrorsByOrigin from './errorsByOrigin'; -import SlowestResources from './slowestResources'; -import ResponseTimeDistribution from './responseTimeDistribution'; -import SessionsImpactedBySlowRequests from './sessionsImpactedBySlowRequests'; -import TimeToRender from './timeToRender'; -import SessionsImpactedByJSErrors from './sessionsImpactedByJSErrors'; -import ApplicationActivity from './applicationActivity'; -import TopMetrics from './topMetrics'; -import Errors from './errors'; -import UserActivity from './userActivity'; -import Performance from './performance'; -import Crashes from './crashes'; -import PageMetrics from './pageMetrics'; -import SlowestDomains from './slowestDomains'; -import ResourceLoadingTime from './resourceLoadingTime'; - -import Image from './image'; -import Resource from './resource'; -import Err from './err'; -import MissingResource from './missingResource'; - -export const WIDGET_LIST = [{ - key: "sessions", - name: "Processed Sessions", - description: 'Number of recorded user sessions.', - thumb: 'processed_sessions.png', - dataWrapper: (ps, period) => ProcessedSessions(ps) - .update("chart", getChartFormatter(period)), - }, { - key: "applicationActivity", - name: "Application Activity", - description: 'Average loading time of pages, images and browser requests.', - thumb: 'application_activity.png', - dataWrapper: ApplicationActivity, - }, { - key: "errors", - name: "Exceptions", - description: 'Number of errors and impacted user sessions.', - thumb: 'errors.png', - dataWrapper: (e, period) => Errors(e) - .update("chart", getChartFormatter(period)), - }, { - key: "userActivity", - name: "User Activity", - description: 'The average user feedback score, average number of visited pages per session and average session duration.', - thumb: 'user_activity.png', - dataWrapper: UserActivity, - }, { - key: "pageMetrics", - name: "Page Metrics", - description: "Average speed metrics across all pages: First Meaningful Pain and DOM Content Loaded.", - thumb: 'page_metrics.png', - dataWrapper: PageMetrics, - }, { - key: "performance", - name: "Performance", - description: "Compare the average loading times of your web app's resources (pages, images and browser requests)", - thumb: 'performance.png', - dataWrapper: (p, period) => Performance(p) - .update("chart", getChartFormatter(period)), - }, { - key: "slowestImages", - name: "Slowest Images", - description: 'List of images that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - dataWrapper: list => List(list).map(Image).sort((i1, i2) => { - if (i1.sessions < 1000) return i2.sessions -i1.sessions; - const sessionK1 = Math.trunc(i1.sessions/1000); - const sessionK2 = Math.trunc(i2.sessions/1000); - if (sessionK1 !== sessionK2) return sessionK2 - sessionK1; - return i2.avgDuration - i1.avgDuration; - }), - }, - // { - // key: "errorsTrend", - // name: "Most Impactful Errors", - // description: 'List of errors and exceptions, sorted by the number of impacted sessions.', - // thumb: 'most_Impactful_errors.png', - // dataWrapper: list => List(list).map(Err), - // }, - { - key: "sessionsFrustration", - name: "Recent Frustrations", - description: "List of recent sessions where users experienced some kind of frustrations, such as click rage.", - thumb: 'recent_frustrations.png', - dataWrapper: list => List(list).map(Session), - }, - { - key: "sessionsFeedback", - name: "Recent Negative Feedback", - description: "List of recent sessions where users reported an issue or a bad experience.", - thumb: 'negative_feedback.png', - dataWrapper: list => List(list).map(Session), - }, - { - key: "missingResources", - name: "Missing Resources", - description: "List of resources, such as images, that couldn't be loaded.", - thumb: 'missing_resources.png', - type: 'resources', - dataWrapper: list => List(list).map(MissingResource), - }, - // { - // key: "sessionsPerformance", - // name: "Recent Performance Issues", - // description: "", - // dataWrapper: list => List(list).map(Session), - // } - { - key: "slowestResources", - name: "Slowest Resources", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'resources', - dataWrapper: list => List(list).map(SlowestResources) - }, - { - key: "overview", - name: "Overview Metrics", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - dataWrapper: (p, period) => { - return List(p) - .map(item => OverviewWidget({ key: camelCased(item.key), ...item.data})) - .map(widget => widget.update("chart", getChartFormatter(period))) - } - // dataWrapper: (p, period) => List(p) - // .update("chart", getChartFormatter(period)) - }, - { - key: "speedLocation", - name: "Speed Index by Location", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: list => SpeedLocation(list) - }, - { - key: "slowestDomains", - name: "Slowest Domains", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - // dataWrapper: list => List(list).sort((a, b) => a.avg >= b.avg).map(SlowestDomains) - dataWrapper: list => SlowestDomains(list) - .update("partition", (partition) => List(partition).sort((a, b) => b.avg - a.avg)) - }, - { - key: "sessionsPerBrowser", - name: "Sessions per Browser", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: list => SessionsPerBrowser(list) - // .update("chart", (list) => List(list).sort((a, b) => a.count >= b.count)) - }, - { - key: "resourcesLoadingTime", - name: "Resource Fetch Time", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'resources', - dataWrapper: (list, period) => DomBuildingTime(list) - .update("chart", getChartFormatter(period)) - }, - { - key: "timeToRender", - name: "Time To Render", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => TimeToRender(p) - .update("chart", getChartFormatter(period)) - }, - { - key: "impactedSessionsBySlowPages", - name: "Sessions Impacted by Slow Requests", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => SessionsImpactedBySlowRequests({ chart: p}) - .update("chart", getChartFormatter(period)) - }, - { - key: "memoryConsumption", - name: "Memory Consumption", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => MemoryConsumption(p) - // .update("chart", list => list.map((i) => ({...i, avgUsedJsHeapSize: i.avgUsedJsHeapSize/1024/1024 }))) - .update("chart", getChartFormatter(period)) - }, - { - key: "cpu", - name: "CPU Load", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => MemoryConsumption(p) - .update("chart", getChartFormatter(period)), - }, - { - key: "fps", - name: "Framerate", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => MemoryConsumption(p) - .update("chart", getChartFormatter(period)), - }, - { - key: "crashes", - name: "Crashes", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (e, period) => Crashes(e) - .update("chart", getChartFormatter(period)) - }, - { - key: "resourceTypeVsResponseEnd", - name: "Resource Loaded vs Response End", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'resources', - dataWrapper: (p, period) => ErrorsByType({chart: p}) - .update("chart", getChartFormatter(period)) - }, - { - key: "resourcesCountByType", - name: "Breakdown of Loaded Resources", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'resources', - dataWrapper: (p, period) => ErrorsByType({chart: p}) - .update("chart", getChartFormatter(period)) - }, - { - key: "resourcesVsVisuallyComplete", - name: "Resource Loaded vs Visually Complete", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => ErrorsByType({chart: p}) - .update("chart", getChartFormatter(period)) - // .update('chart', (data) => { - // return data.map(i => ({...i, avgTimeToRender: i.avgTimeToRender / 100})); - // }) - }, - { - key: "pagesDomBuildtime", - name: "DOM Build Time", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => DomBuildingTime(p) - .update("chart", getChartFormatter(period)) - }, - { - key: "pagesResponseTime", - name: "Page Response Time", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => ResponseTime(p) - .update("chart", getChartFormatter(period)) - }, - { - key: "pagesResponseTimeDistribution", - name: "Page Response Time Distribution", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'performance', - dataWrapper: (p, period) => ResponseTimeDistribution(p) - .update("chart", getChartFormatter(period)) - .update("extremeValues", list => list.map(i => ({...i, time: 'Extreme Values'}))) - .update("percentiles", list => list.map(i => ({ ...i, responseTime: Math.round(i.responseTime)}))) - }, - { - key: "domainsErrors_4xx", - name: "Top Domains with 4xx Fetch Errors", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'errors', - dataWrapper: (p, period) => TopDomains({ chart: p}) - .update("chart", getChartFormatter(period)) - // .updateIn(["chart", "5xx"], getChartFormatter(period)) - }, - { - key: "domainsErrors_5xx", - name: "Top Domains with 5xx Fetch Errors", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'errors', - dataWrapper: (p, period) => TopDomains({ chart: p}) - .update("chart", getChartFormatter(period)) - // .updateIn(["chart", "5xx"], getChartFormatter(period)) - }, - { - key: "errorsPerDomains", - name: "Errors per Domain", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'errors', - // dataWrapper: list => List(list) - dataWrapper: list => List(list).sort((a, b) => b.errorsCount - a.errorsCount).take(5) - }, - { - key: "callsErrors", - name: "Fetch Calls with Errors", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'errors', - dataWrapper: list => List(list).sort((a, b) => b.allRequests - a.allRequests) - }, - { - key: "errorsPerType", - name: "Errors by Type", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'errors', - dataWrapper: (p, period) => ErrorsByType({chart: p}) - .update("chart", getChartFormatter(period)) - }, - { - key: "resourcesByParty", - name: "Errors by Origin", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'errors', - dataWrapper: (p, period) => ErrorsByOrigin({chart: p}) - .update("chart", getChartFormatter(period)) - }, - { - key: "impactedSessionsByJsErrors", - name: "Sessions Affected by JS Errors", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'errors', - dataWrapper: (p, period) => SessionsImpactedByJSErrors(p) - .update("chart", getChartFormatter(period)) - }, - { - key: "busiestTimeOfDay", - name: "Busiest Time of the Day", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - dataWrapper: list => List(list) - }, - { - key: "topMetrics", - name: "Top Metrics", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - dataWrapper: TopMetrics - }, - - // Overview Widgets - { - key: 'countSessions', - name: 'Captured Sessions', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'New vs Returning', - type: 'overview', - tooltipLabel: "Count", - dataWrapper: list => List(list) - }, - { - key: 'avgTimeToRender', - name: 'Time To Render', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => TimeToRender(list) - }, - { - key: 'avgTimeToInteractive', - name: 'Time To Interactive', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgPageLoadTime', - name: 'Page Load Time', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgImageLoadTime', - name: 'Image Load Time', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgRequestLoadTime', - name: 'Request Load Time', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgDomContentLoadStart', - name: 'DOM Content Loaded', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgPagesDomBuildtime', - name: 'DOM Build Time', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: (list, period) => DomBuildingTime(list) - .update("chart", getChartFormatter(period)) - }, - { - key: 'avgSessionDuration', - name: 'Session Duration', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'min', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgVisitedPages', - name: 'No. of Visited Pages', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgPagesResponseTime', - name: 'Page Response Time', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => ResponseTime(list) - }, - { - key: 'avgTillFirstBit', - name: 'Time Till First Byte', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => ResponseTime(list) - }, - // { - // key: 'avgResponseTime', - // name: 'Response Time', - // description: 'Lorem ipsum...', - // thumb: 'na.png', - // subtext: 'test', - // unit: 'ms', - // type: 'overview', - // dataWrapper: list => List(list) - // }, - // { - // key: 'requestsCount', - // name: 'Request Count', - // description: 'Lorem ipsum...', - // thumb: 'na.png', - // subtext: 'test', - // // unit: 'ms', - // type: 'overview', - // dataWrapper: list => List(list) - // }, - { - key: 'avgFirstContentfulPixel', - name: 'First Meaningful Paint', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgFirstPaint', - name: 'First Paint', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'ms', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgUsedJsHeapSize', - name: 'Memory Consumption', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - unit: 'mb', - type: 'overview', - dataWrapper: list => List(list) - }, - { - key: 'avgCpu', - name: 'CPU Load', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - type: 'overview', - unit: '%', - dataWrapper: list => List(list) - }, - { - key: 'avgFps', - name: 'Framerate', - description: 'Lorem ipsum...', - thumb: 'na.png', - subtext: 'test', - type: 'overview', - dataWrapper: list => List(list) - }, - -]; - -export const WIDGET_KEYS = WIDGET_LIST.map(({ key }) => key); - -const WIDGET_MAP = {}; -WIDGET_LIST.forEach(w => { WIDGET_MAP[ w.key ] = w; }); - -const OVERVIEW_WIDGET_MAP = {}; -WIDGET_LIST.filter(w => w.type === 'overview').forEach(w => { OVERVIEW_WIDGET_MAP[ w.key ] = w; }); - -export { - WIDGET_MAP, - OVERVIEW_WIDGET_MAP, - ProcessedSessions, - ApplicationActivity, - Errors, - UserActivity, - Performance, - PageMetrics, - Image, - Err, - SlowestDomains, - ResourceLoadingTime -}; diff --git a/frontend/app/types/dashboard/memoryConsumption.js b/frontend/app/types/dashboard/memoryConsumption.js deleted file mode 100644 index f585ac4fd..000000000 --- a/frontend/app/types/dashboard/memoryConsumption.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Record } from 'immutable'; - -const MemoryConsumption = Record({ - avgFps: undefined, - avgUsedJsHeapSize: undefined, - avgCpu: undefined, - chart: [], -}); - -function fromJS(data = {}) { - const size = data.avgUsedJsHeapSize && data.avgUsedJsHeapSize / 1024 / 1024 - if (data instanceof MemoryConsumption) return data; - return new MemoryConsumption({...data, avgUsedJsHeapSize: size}); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/missingResource.js b/frontend/app/types/dashboard/missingResource.js deleted file mode 100644 index 8ca975973..000000000 --- a/frontend/app/types/dashboard/missingResource.js +++ /dev/null @@ -1,26 +0,0 @@ -import Record from 'Types/Record'; -import { convertNumberRange, getResourceName } from 'App/utils'; - - -export default Record({ - url: '', - name: '', - count: undefined, - sessions: undefined, - startedAt: undefined, - endedAt: undefined, - startTimestamp: undefined, - endTimestamp: undefined, - chart: [], -}, { - fromJS: ({ chart = [], ...missingResource }) => { - const oldMax = [ ...chart ].sort((a, b) => b.count - a.count)[ 0 ].count; - const formattedChart = chart.map(({ count, ...rest }) => - ({ count: convertNumberRange(oldMax, 0, 2, 20, count), ...rest })); - return { - ...missingResource, - chart: formattedChart, - name: getResourceName(missingResource.url), - } - } -}); \ No newline at end of file diff --git a/frontend/app/types/dashboard/overviewWidget.js b/frontend/app/types/dashboard/overviewWidget.js deleted file mode 100644 index 6133c5946..000000000 --- a/frontend/app/types/dashboard/overviewWidget.js +++ /dev/null @@ -1,20 +0,0 @@ -import { Record } from 'immutable'; - -const OverviewWidget = Record({ - key: undefined, - value: undefined, - progress: undefined, - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof OverviewWidget) return data; - - if (data.key === "avgSessionDuration") { - data.value = data.value / 100000 - } - return new OverviewWidget(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/pageMetrics.js b/frontend/app/types/dashboard/pageMetrics.js deleted file mode 100644 index 9c92d9486..000000000 --- a/frontend/app/types/dashboard/pageMetrics.js +++ /dev/null @@ -1,14 +0,0 @@ -import Record from 'Types/Record'; - -export default Record({ - avgLoad: 0, - avgLoadProgress: 0, - avgFirstContentfulPixel: 0, - avgFirstContentfulPixelProgress: 0, -}, { - fromJS: pm => ({ - ...pm, - avgFirstContentfulPixel: pm.avgFirstContentfulPixel || pm.avgFirstPixel, - avgFirstContentfulPixelProgress: pm.avgFirstContentfulPixelProgress || pm.avgFirstPixelProgress, - }), -}); \ No newline at end of file diff --git a/frontend/app/types/dashboard/performance.js b/frontend/app/types/dashboard/performance.js deleted file mode 100644 index 88932f22b..000000000 --- a/frontend/app/types/dashboard/performance.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Record } from 'immutable'; - -const Performance = Record({ - chart: [], -}); - - -function fromJS(performance = {}) { - if (performance instanceof Performance) return performance; - return new Performance(performance); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/processedSessions.js b/frontend/app/types/dashboard/processedSessions.js deleted file mode 100644 index 2f719648f..000000000 --- a/frontend/app/types/dashboard/processedSessions.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const ProcessedSessions = Record({ - count: undefined, - progress: undefined, - chart: [], -}); - -function fromJS(processedSessions = {}) { - if (processedSessions instanceof ProcessedSessions) return processedSessions; - return new ProcessedSessions(processedSessions); -} - -export default fromJS; diff --git a/frontend/app/types/dashboard/resource.js b/frontend/app/types/dashboard/resource.js deleted file mode 100644 index 341ef8e47..000000000 --- a/frontend/app/types/dashboard/resource.js +++ /dev/null @@ -1,16 +0,0 @@ -import Record from 'Types/Record'; - -const getName = (url = '') => url.split('/').filter(part => !!part).pop(); - -export default Record({ - avgDuration: undefined, - sessions: undefined, - chart: [], - url: '', - name: '', -}, { - fromJS: (resource) => ({ - ...resource, - name: getName(resource.url), - }) -}); diff --git a/frontend/app/types/dashboard/resourceLoadingTime.js b/frontend/app/types/dashboard/resourceLoadingTime.js deleted file mode 100644 index cc23bdae9..000000000 --- a/frontend/app/types/dashboard/resourceLoadingTime.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Record } from 'immutable'; - -const ResourceLoadingTime = Record({ - avg: undefined, - timestamp: undefined -}); - -function fromJS(resourceLoadingTime = {}) { - if (resourceLoadingTime instanceof ResourceLoadingTime) return resourceLoadingTime; - return new ResourceLoadingTime(resourceLoadingTime); -} - -export default fromJS; diff --git a/frontend/app/types/dashboard/responseTime.js b/frontend/app/types/dashboard/responseTime.js deleted file mode 100644 index 961eda42b..000000000 --- a/frontend/app/types/dashboard/responseTime.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const ResponseTime = Record({ - avg: undefined, - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof ResponseTime) return data; - return new ResponseTime(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/responseTimeDistribution.js b/frontend/app/types/dashboard/responseTimeDistribution.js deleted file mode 100644 index a53f3555f..000000000 --- a/frontend/app/types/dashboard/responseTimeDistribution.js +++ /dev/null @@ -1,17 +0,0 @@ -import { Record } from 'immutable'; - -const ResponseTimeDistribution = Record({ - chart: [], - avg: undefined, - percentiles: [], - extremeValues: [], - total: undefined -}); - - -function fromJS(data = {}) { - if (data instanceof ResponseTimeDistribution) return data; - return new ResponseTimeDistribution(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/sessionsImpactedByJSErrors.js b/frontend/app/types/dashboard/sessionsImpactedByJSErrors.js deleted file mode 100644 index c0368744d..000000000 --- a/frontend/app/types/dashboard/sessionsImpactedByJSErrors.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const SessionsImpactedByJSErrors = Record({ - errorsCount: undefined, - chart: [] -}); - - -function fromJS(data = {}) { - if (data instanceof SessionsImpactedByJSErrors) return data; - return new SessionsImpactedByJSErrors(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/sessionsImpactedBySlowRequests.js b/frontend/app/types/dashboard/sessionsImpactedBySlowRequests.js deleted file mode 100644 index 4d5d5fc61..000000000 --- a/frontend/app/types/dashboard/sessionsImpactedBySlowRequests.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const SessionsImpactedBySlowRequests = Record({ - avg: undefined, - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof SessionsImpactedBySlowRequests) return data; - return new SessionsImpactedBySlowRequests(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/sessionsPerBrowser.js b/frontend/app/types/dashboard/sessionsPerBrowser.js deleted file mode 100644 index cd8662b13..000000000 --- a/frontend/app/types/dashboard/sessionsPerBrowser.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Record } from 'immutable'; - -const SessionsPerBrowser = Record({ - count: undefined, - chart: [], -}); - -function fromJS(sessionsPerBrowser = {}) { - if (sessionsPerBrowser instanceof SessionsPerBrowser) return sessionsPerBrowser; - return new SessionsPerBrowser({...sessionsPerBrowser, avg: Math.round(sessionsPerBrowser.avg)}); -} - -export default fromJS; diff --git a/frontend/app/types/dashboard/slowestDomains.js b/frontend/app/types/dashboard/slowestDomains.js deleted file mode 100644 index fc15ed831..000000000 --- a/frontend/app/types/dashboard/slowestDomains.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Record } from 'immutable'; - -const SlowestDomains = Record({ - partition: [], - avg: undefined, -}); - -function fromJS(slowestDomains = {}) { - if (slowestDomains instanceof SlowestDomains) return slowestDomains; - return new SlowestDomains({...slowestDomains, avg: Math.round(slowestDomains.avg)}); -} - -export default fromJS; diff --git a/frontend/app/types/dashboard/slowestResources.js b/frontend/app/types/dashboard/slowestResources.js deleted file mode 100644 index 54e00984b..000000000 --- a/frontend/app/types/dashboard/slowestResources.js +++ /dev/null @@ -1,21 +0,0 @@ -import Record from 'Types/Record'; -import { fileType, fileName } from 'App/utils'; - -const validTypes = ['jpg', 'jpeg', 'js', 'css', 'woff', 'css', 'png', 'gif', 'svg'] - -export default Record({ - avg: 0, - url: '', - type: '', - name: '', - chart: [], -}, { - fromJS: pm => { - const type = fileType(pm.url).toLowerCase(); - return { - ...pm, - // type: validTypes.includes(type) ? type : 'n/a', - // fileName: fileName(pm.url) - } - }, -}); \ No newline at end of file diff --git a/frontend/app/types/dashboard/speedLocation.js b/frontend/app/types/dashboard/speedLocation.js deleted file mode 100644 index f1774eba0..000000000 --- a/frontend/app/types/dashboard/speedLocation.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const SpeedLocation = Record({ - avg: undefined, - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof SpeedLocation) return data; - return new SpeedLocation(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/timeToRender.js b/frontend/app/types/dashboard/timeToRender.js deleted file mode 100644 index e07521498..000000000 --- a/frontend/app/types/dashboard/timeToRender.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Record } from 'immutable'; - -const TimeToRender = Record({ - avg: undefined, - chart: [], -}); - -function fromJS(data = {}) { - if (data instanceof TimeToRender) return data; - return new TimeToRender(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/topDomains.js b/frontend/app/types/dashboard/topDomains.js deleted file mode 100644 index fa92db397..000000000 --- a/frontend/app/types/dashboard/topDomains.js +++ /dev/null @@ -1,12 +0,0 @@ -import { Record } from 'immutable'; - -const TopDomains = Record({ - chart: [] -}); - -function fromJS(data = {}) { - if (data instanceof TopDomains) return data; - return new TopDomains(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/dashboard/topMetrics.js b/frontend/app/types/dashboard/topMetrics.js deleted file mode 100644 index 221f65d6c..000000000 --- a/frontend/app/types/dashboard/topMetrics.js +++ /dev/null @@ -1,19 +0,0 @@ -import Record from 'Types/Record'; - -export default Record({ - avgResponseTime: 0, - requestsCount: 0, - avgTimeTilFirstBite: 0, - avgDomCompleteTime: 0 -}, { - // fromJS: aa => ({ - // avgPageLoad: aa.avgDom, - // avgPageLoadProgress: aa.avgDomProgress, - // avgImgLoad: aa.avgLoad, - // avgImgLoadProgress: aa.avgLoadProgress, - // avgReqLoad: aa.avgFirstPixel, - // avgReqLoadProgress: aa.avgFirstPixelProgress, - // ...aa, - // }), -}); - diff --git a/frontend/app/types/dashboard/userActivity.js b/frontend/app/types/dashboard/userActivity.js deleted file mode 100644 index 788349337..000000000 --- a/frontend/app/types/dashboard/userActivity.js +++ /dev/null @@ -1,10 +0,0 @@ -import Record from 'Types/Record'; - -export default Record({ - avgVisitedPages: undefined, - avgVisitedPagesProgress: undefined, - avgDuration: undefined, - avgDurationProgress: undefined, - avgEmotionalRating: undefined, - avgEmotionalRatingProgress: undefined, -}); \ No newline at end of file diff --git a/frontend/app/types/errorInfo.js b/frontend/app/types/errorInfo.js index b3bb48e1d..28f3b2708 100644 --- a/frontend/app/types/errorInfo.js +++ b/frontend/app/types/errorInfo.js @@ -1,4 +1,3 @@ -import { List } from 'immutable'; import Record from './Record'; import Session from './session'; @@ -37,12 +36,12 @@ const ErrorInfo = Record({ chart30: [], tags: [], customTags: [], - lastHydratedSession: Session(), + lastHydratedSession: new Session(), disabled: false, }, { fromJS: ({ stack, lastHydratedSession, ...other }) => ({ ...other, - lastHydratedSession: Session(lastHydratedSession), + lastHydratedSession: new Session(lastHydratedSession), stack0InfoString: getStck0InfoString(stack || []), }) }); diff --git a/frontend/app/types/filter/customFilter.js b/frontend/app/types/filter/customFilter.js index 59e47d985..29557da21 100644 --- a/frontend/app/types/filter/customFilter.js +++ b/frontend/app/types/filter/customFilter.js @@ -1,6 +1,5 @@ import Record from 'Types/Record'; import Target from 'Types/target'; -import { camelCased } from 'App/utils'; import { getEventIcon } from 'Types/filter'; const CLICK = 'CLICK'; diff --git a/frontend/app/types/filter/filter.js b/frontend/app/types/filter/filter.js index 864613678..3ae206d45 100644 --- a/frontend/app/types/filter/filter.js +++ b/frontend/app/types/filter/filter.js @@ -5,7 +5,6 @@ import { TYPES } from 'Types/filter/event'; import { DATE_RANGE_VALUES, CUSTOM_RANGE, - dateRangeValues, getDateRangeFromValue } from 'App/dateRange'; import Event from './event'; diff --git a/frontend/app/types/filter/filterType.ts b/frontend/app/types/filter/filterType.ts index 9d2748e2e..842b0df8e 100644 --- a/frontend/app/types/filter/filterType.ts +++ b/frontend/app/types/filter/filterType.ts @@ -143,12 +143,19 @@ export enum IssueType { MEMORY = 'memory', CPU = 'cpu', SLOW_RESOURCE = 'slow_resource', - SLOW_PAGE_LOAD = 'slow_page_load', + SLOW_PAGE_LOAD = 'slow_pageLoad', CRASH = 'crash', CUSTOM = 'custom', JS_EXCEPTION = 'js_exception', } +export enum IssueCategory { + RESOURCES = 'resources', + NETWORK = 'network', + RAGE = 'rage', + ERRORS = 'errors' +} + export enum FilterType { STRING = 'STRING', ISSUE = 'ISSUE', @@ -165,59 +172,115 @@ export enum FilterType { } export enum FilterKey { - ERROR = 'ERROR', - MISSING_RESOURCE = 'MISSING_RESOURCE', - SLOW_SESSION = 'SLOW_SESSION', - CLICK_RAGE = 'CLICK_RAGE', - CLICK = 'CLICK', - INPUT = 'INPUT', - LOCATION = 'LOCATION', - VIEW = 'VIEW', - CONSOLE = 'CONSOLE', - METADATA = 'METADATA', - CUSTOM = 'CUSTOM', - URL = 'URL', - USER_BROWSER = 'USERBROWSER', - USER_OS = 'USEROS', - USER_DEVICE = 'USERDEVICE', - PLATFORM = 'PLATFORM', - DURATION = 'DURATION', - REFERRER = 'REFERRER', - USER_COUNTRY = 'USERCOUNTRY', - JOURNEY = 'JOURNEY', - REQUEST = 'REQUEST', - GRAPHQL = 'GRAPHQL', - STATEACTION = 'STATEACTION', - REVID = 'REVID', - USERANONYMOUSID = 'USERANONYMOUSID', - USERID = 'USERID', - ISSUE = 'ISSUE', - EVENTS_COUNT = 'EVENTS_COUNT', - UTM_SOURCE = 'UTM_SOURCE', - UTM_MEDIUM = 'UTM_MEDIUM', - UTM_CAMPAIGN = 'UTM_CAMPAIGN', + ERROR = 'error', + MISSING_RESOURCE = 'missingResource', + SLOW_SESSION = 'slowSession', + CLICK_RAGE = 'clickRage', + CLICK = 'click', + INPUT = 'input', + LOCATION = 'location', + VIEW = 'view', + CONSOLE = 'console', + METADATA = 'metadata', + CUSTOM = 'custom', + URL = 'url', + USER_BROWSER = 'userBrowser', + USER_OS = 'userOs', + USER_DEVICE = 'userDevice', + PLATFORM = 'platform', + DURATION = 'duration', + REFERRER = 'referrer', + USER_COUNTRY = 'userCountry', + JOURNEY = 'journey', + REQUEST = 'request', + GRAPHQL = 'graphql', + STATEACTION = 'stateAction', + REVID = 'revId', + USERANONYMOUSID = 'userAnonymousId', + USERID = 'userId', + ISSUE = 'issue', + EVENTS_COUNT = 'eventsCount', + UTM_SOURCE = 'utmSource', + UTM_MEDIUM = 'utmMedium', + UTM_CAMPAIGN = 'utmCampaign', - DOM_COMPLETE = 'DOM_COMPLETE', - LARGEST_CONTENTFUL_PAINT_TIME = 'LARGEST_CONTENTFUL_PAINT_TIME', - TIME_BETWEEN_EVENTS = 'TIME_BETWEEN_EVENTS', - TTFB = 'TTFB', - AVG_CPU_LOAD = 'AVG_CPU_LOAD', - AVG_MEMORY_USAGE = 'AVG_MEMORY_USAGE', - FETCH_FAILED = 'FETCH_FAILED', + DOM_COMPLETE = 'domComplete', + LARGEST_CONTENTFUL_PAINT_TIME = 'largestContentfulPaintTime', + TIME_BETWEEN_EVENTS = 'timeBetweenEvents', + TTFB = 'ttfb', + AVG_CPU_LOAD = 'avgCpuLoad', + AVG_MEMORY_USAGE = 'avgMemoryUsage', + FETCH_FAILED = 'fetchFailed', - FETCH = 'FETCH', - FETCH_URL = 'FETCH_URL', - FETCH_STATUS_CODE = 'FETCH_STATUS_CODE', - FETCH_METHOD = 'FETCH_METHOD', - FETCH_DURATION = 'FETCH_DURATION', - FETCH_REQUEST_BODY = 'FETCH_REQUEST_BODY', - FETCH_RESPONSE_BODY = 'FETCH_RESPONSE_BODY', + FETCH = 'fetch', + FETCH_URL = 'fetchUrl', + FETCH_STATUS_CODE = 'fetchStatusCode', + FETCH_METHOD = 'fetchMethod', + FETCH_DURATION = 'fetchDuration', + FETCH_REQUEST_BODY = 'fetchRequestBody', + FETCH_RESPONSE_BODY = 'fetchResponseBody', - GRAPHQL_NAME = 'GRAPHQL_NAME', - GRAPHQL_METHOD = 'GRAPHQL_METHOD', - GRAPHQL_REQUEST_BODY = 'GRAPHQL_REQUEST_BODY', - GRAPHQL_RESPONSE_BODY = 'GRAPHQL_RESPONSE_BODY', + GRAPHQL_NAME = 'graphqlName', + GRAPHQL_METHOD = 'graphqlMethod', + GRAPHQL_REQUEST_BODY = 'graphqlRequestBody', + GRAPHQL_RESPONSE_BODY = 'graphqlResponseBody', - SESSIONS = 'SESSIONS', - ERRORS = 'js_exception', + SESSIONS = 'sessions', + ERRORS = 'jsException', + + RESOURCES_COUNT_BY_TYPE = 'resourcesCountByType', + RESOURCES_LOADING_TIME = 'resourcesLoadingTime', + AVG_CPU = 'avgCpu', + AVG_DOM_CONTENT_LOADED = 'avgDomContentLoaded', + AVG_DOM_CONTENT_LOAD_START = 'avgDomContentLoadStart', + AVG_FIRST_CONTENTFUL_PIXEL = 'avgFirstContentfulPixel', + AVG_FIRST_PAINT = 'avgFirstPaint', + AVG_FPS = 'avgFps', + AVG_IMAGE_LOAD_TIME = 'avgImageLoadTime', + AVG_PAGE_LOAD_TIME = 'avgPageLoadTime', + AVG_PAGES_DOM_BUILD_TIME = 'avgPagesDomBuildtime', + AVG_PAGES_RESPONSE_TIME = 'avgPagesResponseTime', + AVG_REQUEST_LOADT_IME = 'avgRequestLoadTime', + AVG_RESPONSE_TIME = 'avgResponseTime', + AVG_SESSION_DURATION = 'avgSessionDuration', + AVG_TILL_FIRST_BYTE = 'avgTillFirstByte', + AVG_TIME_TO_INTERACTIVE = 'avgTimeToInteractive', + AVG_TIME_TO_RENDER = 'avgTimeToRender', + AVG_USED_JS_HEAP_SIZE = 'avgUsedJsHeapSize', + AVG_VISITED_PAGES = 'avgVisitedPages', + COUNT_REQUESTS = 'countRequests', + COUNT_SESSIONS = 'countSessions', + + // Errors + RESOURCES_BY_PARTY = 'resourcesByParty', + ERRORS_PER_DOMAINS = 'errorsPerDomains', + ERRORS_PER_TYPE = 'errorsPerType', + CALLS_ERRORS = 'callsErrors', + DOMAINS_ERRORS_4XX = 'domainsErrors4xx', + DOMAINS_ERRORS_5XX = 'domainsErrors5xx', + IMPACTED_SESSIONS_BY_JS_ERRORS = 'impactedSessionsByJsErrors', + + // Performance + CPU = 'cpu', + CRASHES = 'crashes', + FPS = 'fps', + PAGES_DOM_BUILD_TIME = 'pagesDomBuildtime', + MEMORY_CONSUMPTION = 'memoryConsumption', + PAGES_RESPONSE_TIME = 'pagesResponseTime', + PAGES_RESPONSE_TIME_DISTRIBUTION = 'pagesResponseTimeDistribution', + RESOURCES_VS_VISUALLY_COMPLETE = 'resourcesVsVisuallyComplete', + SESSIONS_PER_BROWSER = 'sessionsPerBrowser', + SLOWEST_DOMAINS = 'slowestDomains', + SPEED_LOCATION = 'speedLocation', + TIME_TO_RENDER = 'timeToRender', + IMPACTED_SESSIONS_BY_SLOW_PAGES = 'impactedSessionsBySlowPages', + + // Resources + BREAKDOWN_OF_LOADED_RESOURCES = 'resourcesCountByType', + MISSING_RESOURCES = 'missingResources', + RESOURCE_TYPE_VS_RESPONSE_END = 'resourceTypeVsResponseEnd', + RESOURCE_FETCH_TIME = 'resourcesLoadingTime', + SLOWEST_RESOURCES = 'slowestResources', + + CLICKMAP_URL = 'clickMapUrl', } diff --git a/frontend/app/types/filter/index.js b/frontend/app/types/filter/index.js index 386ea96a0..57ffd3fc9 100644 --- a/frontend/app/types/filter/index.js +++ b/frontend/app/types/filter/index.js @@ -170,7 +170,6 @@ export const defaultOperator = (filter) => { case TYPES.USER_COUNTRY: case TYPES.METADATA: case 'metadata': - case TYPES.CUSTOM: case TYPES.LOCATION: case TYPES.VIEW: return 'is'; diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index 8abd3f5fc..0fcc8e668 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -15,21 +15,21 @@ export const filters = [ { key: FilterKey.FETCH, type: FilterType.SUB_FILTERS, category: FilterCategory.JAVASCRIPT, operator: 'is', label: 'Network Request', filters: [ { key: FilterKey.FETCH_URL, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with URL', placeholder: 'Enter path or URL', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' }, { key: FilterKey.FETCH_STATUS_CODE, type: FilterType.NUMBER_MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with status code', placeholder: 'Enter status code', operator: '=', operatorOptions: filterOptions.customOperators, icon: 'filters/fetch' }, - { key: FilterKey.FETCH_METHOD, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.PERFORMANCE, label: 'with method', operator: 'is', placeholder: 'Select method type', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch', options: filterOptions.methodOptions }, + { key: FilterKey.FETCH_METHOD, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.PERFORMANCE, label: 'with method', operator: 'is', placeholder: 'Select method type', operatorOptions: filterOptions.stringOperatorsLimited, icon: 'filters/fetch', options: filterOptions.methodOptions }, { key: FilterKey.FETCH_DURATION, type: FilterType.NUMBER, category: FilterCategory.PERFORMANCE, label: 'with duration (ms)', placeholder: 'E.g. 12', operator: '=', operatorOptions: filterOptions.customOperators, icon: 'filters/fetch' }, { key: FilterKey.FETCH_REQUEST_BODY, type: FilterType.STRING, category: FilterCategory.PERFORMANCE, label: 'with request body', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' }, { key: FilterKey.FETCH_RESPONSE_BODY, type: FilterType.STRING, category: FilterCategory.PERFORMANCE, label: 'with response body', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' }, ], icon: 'filters/fetch', isEvent: true }, { key: FilterKey.GRAPHQL, type: FilterType.SUB_FILTERS, category: FilterCategory.JAVASCRIPT, label: 'GraphQL', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/graphql', isEvent: true, filters: [ { key: FilterKey.GRAPHQL_NAME, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with name', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' }, - { key: FilterKey.GRAPHQL_METHOD, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.PERFORMANCE, label: 'with method', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch', options: filterOptions.methodOptions }, + { key: FilterKey.GRAPHQL_METHOD, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.PERFORMANCE, label: 'with method', operator: 'is', operatorOptions: filterOptions.stringOperatorsLimited, icon: 'filters/fetch', options: filterOptions.methodOptions }, { key: FilterKey.GRAPHQL_REQUEST_BODY, type: FilterType.STRING, category: FilterCategory.PERFORMANCE, label: 'with request body', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' }, { key: FilterKey.GRAPHQL_RESPONSE_BODY, type: FilterType.STRING, category: FilterCategory.PERFORMANCE, label: 'with response body', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' }, ]}, { key: FilterKey.STATEACTION, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'State Action', placeholder: 'E.g. 12', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/state-action', isEvent: true }, { key: FilterKey.ERROR, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Error Message', placeholder: 'E.g. Uncaught SyntaxError', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/error', isEvent: true }, // { key: FilterKey.METADATA, type: FilterType.MULTIPLE, category: FilterCategory.METADATA, label: 'Metadata', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/metadata', isEvent: true }, - + // FILTERS { key: FilterKey.USER_OS, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'User OS', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/os' }, { key: FilterKey.USER_BROWSER, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'User Browser', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/browser' }, @@ -42,7 +42,7 @@ export const filters = [ // { key: FilterKey.CONSOLE, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Console', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/console' }, { key: FilterKey.USERID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User Id', placeholder: 'E.g. Alex, or alex@domain.com, or EMP123', operator: 'is', operatorOptions: filterOptions.stringOperators.concat([{ label: 'is undefined', value: 'isUndefined'}]), icon: 'filters/userid' }, { key: FilterKey.USERANONYMOUSID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User AnonymousId', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' }, - + // PERFORMANCE { key: FilterKey.DOM_COMPLETE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'DOM Complete', placeholder: 'Enter path', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/dom-complete', isEvent: true, hasSource: true, sourceOperator: '>=', sourcePlaceholder: 'E.g. 12', sourceUnit: 'ms', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, { key: FilterKey.LARGEST_CONTENTFUL_PAINT_TIME, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Largest Contentful Paint', placeholder: 'Enter path', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/lcpt', isEvent: true, hasSource: true, sourceOperator: '>=', sourcePlaceholder: 'E.g. 12', sourceUnit: 'ms', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, @@ -53,6 +53,14 @@ export const filters = [ { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', placeholder: 'Select an issue', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/click', options: filterOptions.issueOptions }, ]; +export const eventKeys = filters.filter((i) => i.isEvent).map(i => i.key); + +export const clickmapFilter = { + key: FilterKey.LOCATION, + type: FilterType.MULTIPLE, + category: FilterCategory.INTERACTIONS, + label: 'Visited URL', placeholder: 'Enter URL or path', operator: filterOptions.pageUrlOperators[0].value, operatorOptions: filterOptions.pageUrlOperators, icon: 'filters/location', isEvent: true } + const mapFilters = (list) => { return list.reduce((acc, filter) => { acc[filter.key] = filter; @@ -97,12 +105,12 @@ export const clearMetaFilters = () => { /** * Add a new filter to the filter list - * @param {*} category - * @param {*} key - * @param {*} type - * @param {*} operator - * @param {*} operatorOptions - * @param {*} icon + * @param {*} category + * @param {*} key + * @param {*} type + * @param {*} operator + * @param {*} operatorOptions + * @param {*} icon */ export const addElementToFiltersMap = ( category = FilterCategory.METADATA, @@ -143,15 +151,15 @@ export default Record({ value: [""], source: [""], category: '', - + custom: '', // target: Target(), level: '', - + hasNoValue: false, isFilter: false, actualValue: '', - + hasSource: false, source: [""], sourceType: '', @@ -161,7 +169,7 @@ export default Record({ sourceOperatorOptions: [], operator: '', - operatorOptions: [], + operatorOptions: [], operatorDisabled: false, isEvent: false, index: 0, @@ -187,6 +195,14 @@ export default Record({ _filter = filtersMap[type]; } } + + if (!_filter) { + _filter = { + key: filter.key, + type: "MULTIPLE", + } + } + return { ..._filter, ...filter, @@ -199,8 +215,8 @@ export default Record({ /** * Group filters by category - * @param {*} filtersMap - * @returns + * @param {*} filtersMap + * @returns */ export const generateFilterOptions = (map) => { const filterSection = {}; @@ -229,4 +245,4 @@ export const generateLiveFilterOptions = (map) => { } }); return filterSection; -} \ No newline at end of file +} diff --git a/frontend/app/types/filter/savedFilter.js b/frontend/app/types/filter/savedFilter.js index f10b0686b..006f22047 100644 --- a/frontend/app/types/filter/savedFilter.js +++ b/frontend/app/types/filter/savedFilter.js @@ -1,7 +1,6 @@ import Record from 'Types/Record'; import Filter from './filter'; -import { List } from 'immutable'; -import { notEmptyString, validateName } from 'App/validate'; +import { notEmptyString } from 'App/validate'; export default Record({ searchId: undefined, @@ -11,7 +10,6 @@ export default Record({ filter: Filter(), createdAt: undefined, count: 0, - watchdogs: List(), isPublic: false, }, { idKey: 'searchId', diff --git a/frontend/app/types/funnel.js b/frontend/app/types/funnel.js index d06e518b5..24d4e02a8 100644 --- a/frontend/app/types/funnel.js +++ b/frontend/app/types/funnel.js @@ -1,6 +1,5 @@ import Record from 'Types/Record'; import Filter from 'Types/filter'; -import { truncate } from 'App/utils'; // import { validateURL, validateName } from 'App/validate'; const getRedableName = ({ type, value, operator }) => { diff --git a/frontend/app/types/member.js b/frontend/app/types/member.ts similarity index 51% rename from frontend/app/types/member.js rename to frontend/app/types/member.ts index 8e15397f6..0a88d51ba 100644 --- a/frontend/app/types/member.js +++ b/frontend/app/types/member.ts @@ -2,6 +2,34 @@ import Record from 'Types/Record'; import { DateTime } from 'luxon'; import { validateEmail, validateName } from 'App/validate'; +export interface IMember { + id: string + name: string + email: string + createdAt: DateTime + admin: boolean + superAdmin: boolean + joined: boolean + expiredInvitation: boolean + roleId: string + roleName: string + invitationLink: string +} + +export interface IMemberApiRes { + userId: string + name: string + email: string + createdAt: string + admin: boolean + superAdmin: boolean + joined: boolean + expiredInvitation: boolean + roleId: string + roleName: string + invitationLink: string +} + export default Record({ id: undefined, name: '', @@ -28,9 +56,9 @@ export default Record({ return js; }, }, - fromJS: ({ createdAt, ...rest }) => ({ + fromJS: ({ createdAt, ...rest }: IMemberApiRes) => ({ ...rest, - createdAt: createdAt && DateTime.fromISO(createdAt || 0), + createdAt: createdAt && DateTime.fromISO(createdAt || '0'), id: rest.userId, }), }); diff --git a/frontend/app/types/run/index.js b/frontend/app/types/run/index.js deleted file mode 100644 index 658043461..000000000 --- a/frontend/app/types/run/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import fromJS from './run'; - -export * from './run'; -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/run/run.js b/frontend/app/types/run/run.js deleted file mode 100644 index b084a25fe..000000000 --- a/frontend/app/types/run/run.js +++ /dev/null @@ -1,187 +0,0 @@ -import { Record, List, Map } from 'immutable'; -import { DateTime } from 'luxon'; -import Environment from 'Types/environment'; -import stepFromJS from './step'; -import seleniumStepFromJS from './seleniumStep'; -import Resource from '../session/resource'; -import Log from '../session/log'; - -export const NOT_FETCHED = undefined; -export const QUEUED = 'queued'; -export const INITIALIZING = 'initializing'; -export const RUNNING = 'running'; -export const COMPLETED = 'completed'; -export const PASSED = 'passed'; -export const FAILED = 'failed'; -export const STOPPED = 'stopped'; -export const CRASHED = 'crashed'; -export const EXPIRED = 'expired'; - -export const STATUS = { - NOT_FETCHED, - QUEUED, - INITIALIZING, - RUNNING, - COMPLETED, - PASSED, - FAILED, - STOPPED, - CRASHED, - EXPIRED, -} - -class Run extends Record({ - runId: undefined, - testId: undefined, - name: '', - tags: List(), - environment: Environment(), - scheduled: false, - schedulerId: undefined, - browser: undefined, - sessionId: undefined, - startedAt: undefined, - url_video: undefined, - finishedAt: undefined, - steps: List(), - resources: [], - logs: [], - seleniumSteps: List(), - url_browser_logs: undefined, - url_logs: undefined, - url_selenium_project: undefined, - sourceCode: undefined, - screenshotUrl: undefined, - clientId: undefined, - state: NOT_FETCHED, - baseRunId: undefined, - lastExecutedString: undefined, - durationString: undefined, - hour: undefined, // TODO: fine API - day: undefined, - location: undefined, - deviceType: undefined, - advancedOptions: undefined, - harfile: undefined, - lighthouseHtmlFile: undefined, - resultsFile: undefined, - lighthouseJsonFile: undefined, - totalStepsCount: undefined, - auditsPerformance: Map(), - auditsAd: Map(), - transferredSize: undefined, - resourcesSize: undefined, - domBuildingTime: undefined, - domContentLoadedTime: undefined, - loadTime: undefined, - starter: undefined, - // { - // "id": '', - // "title": '', - // "description": '', - // "score": 0, - // "scoreDisplayMode": '', - // "numericValue": 0, - // "numericUnit": '', - // "displayValue": '' - // } -}) { - idKey = 'runId'; - isRunning() { - return this.state === RUNNING; - } - isQueued() { - return this.state === QUEUED; - } - isPassed() { - return this.state === PASSED; - } -} - -// eslint-disable-next-line complexity -function fromJS(run = {}) { - if (run instanceof Run) return run; - - const startedAt = run.startedAt && DateTime.fromMillis(run.startedAt); - const finishedAt = run.finishedAt && DateTime.fromMillis(run.finishedAt); - let durationString; - let lastExecutedString; - if (run.state === 'running') { - durationString = 'Running...'; - lastExecutedString = 'Now'; - } else if (startedAt && finishedAt) { - const _duration = Math.floor(finishedAt - startedAt); - if (_duration > 10000) { - const min = Math.floor(_duration / 60000); - durationString = `${ min < 1 ? 1 : min } min`; - } else { - durationString = `${ Math.floor(_duration / 1000) } secs`; - } - const diff = startedAt.diffNow([ 'days', 'hours', 'minutes', 'seconds' ]).negate(); - if (diff.days > 0) { - lastExecutedString = `${ Math.round(diff.days) } day${ diff.days > 1 ? 's' : '' } ago`; - } else if (diff.hours > 0) { - lastExecutedString = `${ Math.round(diff.hours) } hrs ago`; - } else if (diff.minutes > 0) { - lastExecutedString = `${ Math.round(diff.minutes) } min ago`; - } else { - lastExecutedString = `${ Math.round(diff.seconds) } sec ago`; - } - } - - const steps = List(run.steps).map(stepFromJS); - const seleniumSteps = List(run.seleniumSteps).map(seleniumStepFromJS); - const tags = List(run.tags); - const environment = Environment(run.environment); - - let resources = List(run.network) - .map(i => Resource({ - ...i, - // success: 1, - // time: i.timestamp, - // type: 'xhr', - // headerSize: 1200, - // timings: {}, - })); - const firstResourceTime = resources.map(r => r.time).reduce((a,b)=>Math.min(a,b), Number.MAX_SAFE_INTEGER); - resources = resources - .map(r => r.set("time", r.time - firstResourceTime)) - .sort((r1, r2) => r1.time - r2.time).toArray() - - const logs = List(run.console).map(Log); - const screenshotUrl = run.screenshot_url || - seleniumSteps.find(({ screenshotUrl }) => !!screenshotUrl, null, {}).screenshotUrl; - - const state = run.state === 'completed' ? PASSED : run.state; - const networkOverview = run.networkOverview || {}; - - return new Run({ - ...run, - startedAt, - finishedAt, - durationString, - lastExecutedString, - steps, - resources, - logs, - seleniumSteps, - tags, - environment, - screenshotUrl, - state, - deviceType: run.device || run.deviceType, - auditsPerformance: run.lighthouseJson && run.lighthouseJson.performance, - auditsAd: run.lighthouseJson && run.lighthouseJson.ad, - transferredSize: networkOverview.transferredSize, - resourcesSize: networkOverview.resourcesSize, - domBuildingTime: networkOverview.domBuildingTime, - domContentLoadedTime: networkOverview.domContentLoadedTime, - loadTime: networkOverview.loadTime, - }); -} - -Run.prototype.exists = function () { - return this.runId !== undefined; -}; - -export default fromJS; diff --git a/frontend/app/types/run/seleniumStep.js b/frontend/app/types/run/seleniumStep.js deleted file mode 100644 index 5178240c7..000000000 --- a/frontend/app/types/run/seleniumStep.js +++ /dev/null @@ -1,29 +0,0 @@ -import { Record, List } from 'immutable'; -import { DateTime, Duration } from 'luxon'; - -const Step = Record({ - duration: undefined, - startedAt: undefined, - label: undefined, - input: undefined, - info: undefined, - order: undefined, - screenshotUrl: undefined, - steps: List(), -}); - -function fromJS(step = {}) { - const startedAt = step.startedAt && DateTime.fromMillis(step.startedAt * 1000); - const duration = step.executionTime && Duration.fromMillis(step.executionTime); - const steps = List(step.steps).map(Step); - const screenshotUrl = step.screenshot_url; - return new Step({ - ...step, - steps, - startedAt, - duration, - screenshotUrl, - }); -}; - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/run/step.js b/frontend/app/types/run/step.js deleted file mode 100644 index 5358c0985..000000000 --- a/frontend/app/types/run/step.js +++ /dev/null @@ -1,31 +0,0 @@ -import { Record, List } from 'immutable'; -import { DateTime, Duration } from 'luxon'; - -const Step = Record({ - duration: undefined, - startedAt: undefined, - label: undefined, - input: undefined, - info: undefined, - order: undefined, - status: undefined, - title: undefined, - screenshotUrl: undefined, - steps: List(), -}); - -function fromJS(step = {}) { - const startedAt = step.startedAt && DateTime.fromMillis(step.startedAt); - const duration = step.executionTime && Duration.fromMillis(step.executionTime); - const steps = List(step.steps).map(Step); - const screenshotUrl = step.screenshot; - return new Step({ - ...step, - steps, - startedAt, - duration, - screenshotUrl, - }); -}; - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/schedule.js b/frontend/app/types/schedule.js deleted file mode 100644 index 87b38eb6d..000000000 --- a/frontend/app/types/schedule.js +++ /dev/null @@ -1,228 +0,0 @@ -import { Record, List, Map } from 'immutable'; -import { DateTime } from 'luxon'; -import { - CHANNEL, - DAYS, - HOURS, - EMAIL, - SLACK, - WEBHOOK -} from 'App/constants/schedule'; -// import runFromJS from './run'; -import { validateEmail } from 'App/validate'; - -export const DEFAULT_ENV_VALUE = '_'; -const Schedule = Record({ - minutes: 30, - hour: 0, - day: -2, - testId: '', - sourceCode: '', - name: '', - nextExecutionTime: undefined, - numberOFExecutions: undefined, - schedulerId: undefined, - environmentId: DEFAULT_ENV_VALUE, - device: 'desktop', - locations: [], - - advancedOptions: false, - headers: [{}], - cookies: [{}], - basicAuth: {}, - network: 'wifi', - bypassCSP: false, - - slack: false, - slackInput: [], - webhook: false, - webhookInput: [], - email: false, - emailInput: [], - hasNotification: false, - options: Map({ message: [], device: 'desktop' }), - - extraCaps: {}, - - validateEvery() { - if (this.day > -2) return true; - return this.minutes >= 5 && this.minutes <= 1440; - }, - validateWebhookEmail() { - if (this.channel !== EMAIL) return true; - return validateEmail(this.webhookEmail); - }, - validateWebhook() { - if (this.channel !== WEBHOOK) return true; - return this.webhookId !== ''; - } -}); - -function fromJS(schedule = {}) { - if (schedule instanceof Schedule) return schedule; - const options = schedule.options || { message: [] }; - const extraCaps = options.extraCaps || { }; - - let channel = ''; - if (schedule.webhookEmail) { - channel = EMAIL; - } else if (schedule.webhookId && schedule.webhook) { - channel = schedule.webhook.type === 'slack' ? SLACK : WEBHOOK; - } - - const nextExecutionTime = schedule.nextExecutionTime ? - DateTime.fromMillis(schedule.nextExecutionTime) : undefined; - - - let { day, minutes } = schedule; - let hour; - if (day !== -2) { - const utcOffset = new Date().getTimezoneOffset(); - minutes = minutes - utcOffset - minutes = minutes >= 1440 ? (minutes - 1440) : minutes; - hour = Math.floor(minutes / 60); - } - // if (day !== -2) { - // const utcOffset = new Date().getTimezoneOffset(); - // const hourOffset = Math.floor(utcOffset / 60); - // const minuteOffset = utcOffset - 60*hourOffset; - - // minutes -= minuteOffset; - // hour -= hourOffset; - // if (day !== -1) { - // const dayOffset = Math.floor(hour/24); // +/-1 - // day = (day + dayOffset + 7) % 7; - // } - // hour = (hour + 24) % 24; - // } - - const slack = List(options.message).filter(i => i.type === 'slack'); - const email = List(options.message).filter(i => i.type === 'email'); - const webhook = List(options.message).filter(i => i.type === 'webhook'); - - const headers = extraCaps.headers ? Object.keys(extraCaps.headers).map(k => ({ name: k, value: extraCaps.headers[k] })) : [{}]; - const cookies = extraCaps.cookies ? Object.keys(extraCaps.cookies).map(k => ({ name: k, value: extraCaps.cookies[k] })) : [{}]; - - return new Schedule({ - ...schedule, - day, - minutes, - hour, - channel, - nextExecutionTime, - device: options.device, - options, - advancedOptions: !!options.extraCaps, - bypassCSP: options.bypassCSP, - network: options.network, - headers, - cookies, - basicAuth: extraCaps.basicAuth, - - slack: slack.size > 0, - slackInput: slack.map(i => parseInt(i.value)).toJS(), - - email: email.size > 0, - emailInput: email.map(i => i.value).toJS(), - - webhook: webhook.size > 0, - webhookInput: webhook.map(i => parseInt(i.value)).toJS(), - - hasNotification: !!slack || !!email || !!webhook - }); -} - -function getObjetctFromArr(arr) { - const obj = {} - for (var i = 0; i < arr.length; i++) { - const temp = arr[i]; - obj[temp.name] = temp.value - } - return obj; -} - -Schedule.prototype.toData = function toData() { - const { - name, schedulerId, environmentId, device, options, bypassCSP, network, headers, cookies, basicAuth - } = this; - - const js = this.toJS(); - options.device = device; - options.bypassCSP = bypassCSP; - options.network = network; - - options.extraCaps = { - headers: getObjetctFromArr(headers), - cookies: getObjetctFromArr(cookies), - basicAuth - }; - - if (js.slack && js.slackInput) - options.message = js.slackInput.map(i => ({ type: 'slack', value: i })) - if (js.email && js.emailInput) - options.message = options.message.concat(js.emailInput.map(i => ({ type: 'email', value: i }))) - if (js.webhook && js.webhookInput) - options.message = options.message.concat(js.webhookInput.map(i => ({ type: 'webhook', value: i }))) - - let day = this.day; - let hour = undefined; - let minutes = this.minutes; - if (day !== -2) { - const utcOffset = new Date().getTimezoneOffset(); - minutes = (this.hour * 60) + utcOffset; - // minutes += utcOffset; - minutes = minutes < 0 ? minutes + 1440 : minutes; - } - // if (day !== -2) { - // const utcOffset = new Date().getTimezoneOffset(); - // const hourOffset = Math.floor(utcOffset / 60); - // const minuteOffset = utcOffset - 60*hourOffset; - - // minutes = minuteOffset; - // hour = this.hour + hourOffset; - // if (day !== -1) { - // const dayOffset = Math.floor(hour/24); // +/-1 - // day = (day + dayOffset + 7) % 7; - // } - // hour = (hour + 24) % 24; - // } - - delete js.slack; - delete js.webhook; - delete js.email; - delete js.slackInput; - delete js.webhookInput; - delete js.emailInput; - delete js.hasNotification; - delete js.headers; - delete js.cookies; - - delete js.device; - delete js.extraCaps; - - // return { - // day, hour, name, minutes, schedulerId, environment, - // }; - return { ...js, day, hour, name, minutes, schedulerId, environmentId, options: options }; -}; - -Schedule.prototype.exists = function exists() { - return this.schedulerId !== undefined; -}; - -Schedule.prototype.valid = function validate() { - return this.validateEvery; -}; - -Schedule.prototype.getInterval = function getInterval() { - const DAY = List(DAYS).filter(item => item.value === this.day).first(); - - if (DAY.value === -2) { - return DAY.text + ' ' + this.minutes + ' Minutes'; // Every 30 minutes - } - - const HOUR = List(HOURS).filter(item => item.value === this.hour).first(); - return DAY.text + ' ' + HOUR.text; // Everyday/Sunday 2 AM; -}; - -export default fromJS; diff --git a/frontend/app/types/session/activity.js b/frontend/app/types/session/activity.js deleted file mode 100644 index bb9be45fa..000000000 --- a/frontend/app/types/session/activity.js +++ /dev/null @@ -1,53 +0,0 @@ -import Record from 'Types/Record'; -import { DateTime } from 'luxon'; - -const ASSIGN = 'assign'; -const MESSAGE = 'message'; -const OPEN = 'open'; -const CLOSE = 'close'; - -export const TYPES = { ASSIGN, MESSAGE, OPEN, CLOSE }; - -const Activity = Record({ - id: undefined, - type: '', - author: '', - // thread_id: undefined, - createdAt: undefined, - // assigned_to: undefined, - // user_id: undefined, - message: '', - user: '' -}) - -// const Assign = Activity.extend({ -// type: ASSIGN, -// }) - -// const Message = Activity.extend({ -// type: MESSAGE, -// }) - -// const Open = Activity.extend({ -// type: OPEN, -// }) - -// const Close = Activity.extend({ -// type: CLOSE, -// }) - -// const Open = Activity.extend({ -// type: OPEN, -// }) - -export default function(activity = {}) { - // if (activity.type === ASSIGN) return Assign(activity); - // if (activity.type === MESSAGE) return Message(activity); - // if (activity.type === OPEN) return Open(activity); - // if (activity.type === CLOSE) return Close(activity); - return Activity({ - ...activity, - createdAt: activity.createdAt ? DateTime.fromMillis(activity.createdAt, {}).toUTC() : undefined, - }); -} - diff --git a/frontend/app/types/session/activity.ts b/frontend/app/types/session/activity.ts new file mode 100644 index 000000000..97e5e2a5c --- /dev/null +++ b/frontend/app/types/session/activity.ts @@ -0,0 +1,49 @@ +import { DateTime } from 'luxon'; + +const ASSIGN = 'assign'; +const MESSAGE = 'message'; +const OPEN = 'open'; +const CLOSE = 'close'; + +export const TYPES = { ASSIGN, MESSAGE, OPEN, CLOSE } as const; + + +type TypeKeys = keyof typeof TYPES +type TypeValues = typeof TYPES[TypeKeys] + + +export interface IActivity { + id: string; + type: TypeValues; + author: string; + createdAt: number; + message: string; + user: string; +} + +export default class Activity { + id: IActivity["id"]; + type: IActivity["type"]; + author: IActivity["author"]; + createdAt?: DateTime; + message: IActivity["message"]; + user: IActivity["user"]; + + constructor(activity?: IActivity) { + if (activity) { + Object.assign(this, { + ...activity, + createdAt: activity.createdAt ? DateTime.fromMillis(activity.createdAt, {}).toUTC() : undefined, + }) + } else { + Object.assign(this, { + id: undefined, + type: '', + author: '', + createdAt: undefined, + message: '', + user: '' + }) + } + } +} diff --git a/frontend/app/types/session/assignment.js b/frontend/app/types/session/assignment.js deleted file mode 100644 index 0c599c85c..000000000 --- a/frontend/app/types/session/assignment.js +++ /dev/null @@ -1,47 +0,0 @@ -import Record from 'Types/Record'; -import Activity from './activity'; -import { List } from 'immutable'; -import { DateTime } from 'luxon'; -import { validateName, notEmptyString } from 'App/validate'; - -export default Record({ - id: undefined, - title: '', - timestamp: undefined, - creatorId: undefined, - sessionId: undefined, - projectId: '', - siteId: undefined, - activities: List(), - closed: false, - assignee: '', - commentsCount: undefined, - issueType: '', - description: '', - iconUrl: '' -}, { - fromJS: (assignment) => ({ - ...assignment, - timestamp: assignment.createdAt ? DateTime.fromISO(assignment.createdAt) : undefined, - activities: assignment.comments ? List(assignment.comments).map(activity => { - if (assignment.users) { - activity.user = assignment.users.filter(user => user.id === activity.author).first(); - } - return Activity(activity) - }) : List() - }), - methods: { - validate: function() { - return !!this.projectId && !!this.issueType && - notEmptyString(this.title) && notEmptyString(this.description) - }, - toCreate: function() { - return { - title: this.title, - description: this.description, - assignee: this.assignee, - issueType: this.issueType - } - } - } -}) diff --git a/frontend/app/types/session/assignment.ts b/frontend/app/types/session/assignment.ts new file mode 100644 index 000000000..a16361ee8 --- /dev/null +++ b/frontend/app/types/session/assignment.ts @@ -0,0 +1,77 @@ +import Activity, { IActivity } from './activity'; +import { DateTime } from 'luxon'; +import { notEmptyString } from 'App/validate'; + +interface IAssignment { + id: string; + title: string; + timestamp: number; + creatorId: string; + sessionId: string; + projectId: string; + siteId: string; + activities: []; + closed: boolean; + assignee: string; + commentsCount: number; + issueType: string; + description: string; + iconUrl: string; + createdAt?: string; + comments: IActivity[] + users: { id: string }[] +} + +export default class Assignment { + id: IAssignment["id"]; + title: IAssignment["title"] = ''; + timestamp: IAssignment["timestamp"]; + creatorId: IAssignment["creatorId"]; + sessionId: IAssignment["sessionId"]; + projectId: IAssignment["projectId"] = ''; + siteId: IAssignment["siteId"]; + activities: IAssignment["activities"]; + closed: IAssignment["closed"]; + assignee: IAssignment["assignee"] = ''; + commentsCount: IAssignment["commentsCount"]; + issueType: IAssignment["issueType"] = ''; + description: IAssignment["description"] = ''; + iconUrl: IAssignment["iconUrl"] = ''; + + constructor(assignment?: IAssignment) { + if (assignment) { + Object.assign(this, { + ...assignment, + timestamp: assignment.createdAt ? DateTime.fromISO(assignment.createdAt) : undefined, + activities: assignment.comments ? assignment.comments.map(activity => { + if (assignment.users) { + // @ts-ignore ??? + activity.user = assignment.users.filter(user => user.id === activity.author)[0]; + } + return new Activity(activity) + }) : [] + }) + } + } + + toJS() { + return this + } + + validate() { + return !!this.projectId && !!this.issueType && notEmptyString(this.title) && notEmptyString(this.description) + } + + get isValid() { + return !!this.projectId && !!this.issueType && notEmptyString(this.title) && notEmptyString(this.description) + } + + toCreate() { + return { + title: this.title, + description: this.description, + assignee: this.assignee, + issueType: this.issueType + } + } +} \ No newline at end of file diff --git a/frontend/app/types/session/author.js b/frontend/app/types/session/author.js deleted file mode 100644 index 12edc3c1e..000000000 --- a/frontend/app/types/session/author.js +++ /dev/null @@ -1,11 +0,0 @@ -import Record from 'Types/Record'; - -export default Record({ - id: undefined, - avatarUrls: undefined, - name: undefined, -}, { - fromJS: author => ({ - ...author, - }) -}) diff --git a/frontend/app/types/session/customField.js b/frontend/app/types/session/customField.js deleted file mode 100644 index 79f26b914..000000000 --- a/frontend/app/types/session/customField.js +++ /dev/null @@ -1,5 +0,0 @@ -import CustomField from 'Types/customField'; - -export default CustomField.extend({ - value: undefined, -}); \ No newline at end of file diff --git a/frontend/app/types/session/error.js b/frontend/app/types/session/error.js deleted file mode 100644 index 9b0fcb278..000000000 --- a/frontend/app/types/session/error.js +++ /dev/null @@ -1,32 +0,0 @@ -import Record from 'Types/Record'; - - -function getStck0InfoString(stack) { - const stack0 = stack[0]; - if (!stack0) return ""; - let s = stack0.function || ""; - if (stack0.url) { - s += ` (${stack0.url})`; - } - return s; -} - - -export default Record({ - sessionId: undefined, - messageId: undefined, - timestamp: undefined, - errorId: undefined, - projectId: undefined, - source: undefined, - name: undefined, - message: undefined, - time: undefined, - function: '?', -}, { - fromJS: ({ stack, ...rest }) => ({ - ...rest, - stack0InfoString: getStck0InfoString(stack || []), - function: (stack && stack[0] && stack[0].function) || '?', - }), -}); diff --git a/frontend/app/types/session/error.ts b/frontend/app/types/session/error.ts new file mode 100644 index 000000000..45bd759ef --- /dev/null +++ b/frontend/app/types/session/error.ts @@ -0,0 +1,46 @@ +function getStck0InfoString(stack: Stack) { + const stack0 = stack[0]; + if (!stack0) return ""; + let s = stack0.function || ""; + if (stack0.url) { + s += ` (${stack0.url})`; + } + return s; +} + +type Stack = { function: string; url: string }[] + +export interface IError { + sessionId: string + messageId: string + timestamp: number + errorId: string + projectId: string + source: string + name: string + message: string + time: number + function: string + stack: Stack +} + +export default class Error { + sessionId: IError["sessionId"]; + messageId: IError["messageId"]; + timestamp: IError["timestamp"]; + errorId: IError["errorId"]; + projectId: IError["projectId"]; + source: IError["source"]; + name: IError["name"]; + message: IError["message"]; + time: IError["time"]; + function: IError["function"]; + + constructor({ stack, ...rest }: IError) { + Object.assign(this, { + ...rest, + stack0InfoString: getStck0InfoString(stack || []), + function: (stack && stack[0] && stack[0].function) || '?', + }) + } +} diff --git a/frontend/app/types/session/errorStack.js b/frontend/app/types/session/errorStack.js deleted file mode 100644 index b92610229..000000000 --- a/frontend/app/types/session/errorStack.js +++ /dev/null @@ -1,13 +0,0 @@ -import Record from 'Types/Record'; - -export default Record({ - // url: undefined, - absPath: undefined, - filename: undefined, - // args: [], - function: undefined, - lineNo: undefined, - colNo: undefined, - offset: 0, - context: undefined -}); diff --git a/frontend/app/types/session/errorStack.ts b/frontend/app/types/session/errorStack.ts new file mode 100644 index 000000000..912c102c7 --- /dev/null +++ b/frontend/app/types/session/errorStack.ts @@ -0,0 +1,26 @@ +export interface IErrorStack { + absPath?: string, + filename?: string, + function?: string, + lineNo?: number, + colNo?: number, + offset?: number, + context?: string +} + +export default class ErrorStack { + absPath: IErrorStack["absPath"] + filename: IErrorStack["filename"] + function: IErrorStack["function"] + lineNo: IErrorStack["lineNo"] + colNo: IErrorStack["colNo"] + offset: IErrorStack["offset"] + context: IErrorStack["context"] + + constructor(es: IErrorStack) { + Object.assign(this, { + ...es, + offset: es.offset || 0, + }) + } +} \ No newline at end of file diff --git a/frontend/app/types/session/event.js b/frontend/app/types/session/event.js deleted file mode 100644 index 537de1724..000000000 --- a/frontend/app/types/session/event.js +++ /dev/null @@ -1,91 +0,0 @@ -import Record from 'Types/Record'; -import Target from 'Types/target'; - -const CONSOLE = 'CONSOLE'; -const CLICK = 'CLICK'; -const INPUT = 'INPUT'; -const LOCATION = 'LOCATION'; -const CUSTOM = 'CUSTOM'; -const CLICKRAGE = 'CLICKRAGE'; -const IOS_VIEW = 'VIEW'; -export const TYPES = { CONSOLE, CLICK, INPUT, LOCATION, CUSTOM, CLICKRAGE, IOS_VIEW}; - - -const Event = Record({ - time: 0, - label: '' -}, { - fromJS: event => ({ - ...event, - target: Target(event.target || { path: event.targetPath }), - }) -}) - -const Console = Event.extend({ - type: CONSOLE, - subtype: '', // level ??? - value: '', -},{ - name: 'Console' -}) - -const Click = Event.extend({ - type: CLICK, - targetContent: '', - target: Target(), - count: undefined -}, { - name: 'Click' -}); - -const Input = Event.extend({ - type: INPUT, - target: Target(), - value: '', -},{ - name: 'Input' -}); - -const View = Event.extend({ - type: IOS_VIEW, - name: '', -},{ - name: 'View' -}) - -const Location = Event.extend({ - type: LOCATION, - url: '', - host: '', - pageLoad: false, - fcpTime: undefined, - //fpTime: undefined, - loadTime: undefined, - domContentLoadedTime: undefined, - domBuildingTime: undefined, - speedIndex: undefined, - visuallyComplete: undefined, - timeToInteractive: undefined, - referrer: '', -}, { - fromJS: event => ({ - ...event, - //fpTime: event.firstPaintTime, - fcpTime: event.firstContentfulPaintTime || event.firstPaintTime, - }), - name: 'Location' -}); - -const TYPE_CONSTRUCTOR_MAP = { - [CONSOLE]: Console, - [CLICK]: Click, - [INPUT]: Input, - [LOCATION]: Location, - [CLICKRAGE]: Click, - [IOS_VIEW]: View, -} - -export default function(event = {}) { - return (TYPE_CONSTRUCTOR_MAP[event.type] || Event)(event); -} - diff --git a/frontend/app/types/session/event.ts b/frontend/app/types/session/event.ts new file mode 100644 index 000000000..bb901a6b1 --- /dev/null +++ b/frontend/app/types/session/event.ts @@ -0,0 +1,161 @@ +const CONSOLE = 'CONSOLE'; +const CLICK = 'CLICK'; +const INPUT = 'INPUT'; +const LOCATION = 'LOCATION'; +const CUSTOM = 'CUSTOM'; +const CLICKRAGE = 'CLICKRAGE'; +const IOS_VIEW = 'VIEW'; +export const TYPES = { CONSOLE, CLICK, INPUT, LOCATION, CUSTOM, CLICKRAGE, IOS_VIEW}; + +interface IEvent { + time: number; + timestamp: number; + type: typeof CONSOLE | typeof CLICK | typeof INPUT | typeof LOCATION | typeof CUSTOM | typeof CLICKRAGE; + name: string; + key: number; + label: string; + targetPath: string; + target: { + path: string; + label: string; + } +} +interface ConsoleEvent extends IEvent { + subtype: string + value: string +} +interface ClickEvent extends IEvent { + targetContent: string; + count: number; +} +interface InputEvent extends IEvent { + value: string; +} + +interface LocationEvent extends IEvent { + url: string; + host: string; + pageLoad: boolean; + fcpTime: number; + loadTime: number; + domContentLoadedTime: number; + domBuildingTime: number; + speedIndex: number; + visuallyComplete: number; + timeToInteractive: number; + referrer: string; + firstContentfulPaintTime: number; + firstPaintTime: number; +} + +export type EventData = ConsoleEvent | ClickEvent | InputEvent | LocationEvent | IEvent; + +class Event { + key: IEvent["key"] + time: IEvent["time"]; + label: IEvent["label"]; + target: IEvent["target"]; + + + constructor(event: IEvent) { + Object.assign(this, { + time: event.time, + label: event.label, + key: event.key, + target: { + path: event.target?.path || event.targetPath, + label: event.target?.label + } + }) + } +} + +class Console extends Event { + readonly type = CONSOLE; + readonly name = 'Console' + subtype: string; + value: string; + + constructor(evt: ConsoleEvent) { + super(evt); + this.subtype = evt.subtype + this.value = evt.value + } +} + +export class Click extends Event { + readonly type: typeof CLICKRAGE | typeof CLICK = CLICK; + readonly name = 'Click' + targetContent = ''; + count: number + + constructor(evt: ClickEvent, isClickRage?: boolean) { + super(evt); + this.targetContent = evt.targetContent + this.count = evt.count + if (isClickRage) { + this.type = CLICKRAGE + } + } +} + +class Input extends Event { + readonly type = INPUT; + readonly name = 'Input' + value = '' + + constructor(evt: InputEvent) { + super(evt); + this.value = evt.value + } +} + + +export class Location extends Event { + readonly name = 'Location'; + readonly type = LOCATION; + url: LocationEvent["url"] + host: LocationEvent["host"]; + fcpTime: LocationEvent["fcpTime"]; + loadTime: LocationEvent["loadTime"]; + domContentLoadedTime: LocationEvent["domContentLoadedTime"]; + domBuildingTime: LocationEvent["domBuildingTime"]; + speedIndex: LocationEvent["speedIndex"]; + visuallyComplete: LocationEvent["visuallyComplete"]; + timeToInteractive: LocationEvent["timeToInteractive"]; + referrer: LocationEvent["referrer"]; + + constructor(evt: LocationEvent) { + super(evt); + Object.assign(this, { + ...evt, + fcpTime: evt.firstContentfulPaintTime || evt.firstPaintTime + }); + } +} + +export type InjectedEvent = Console | Click | Input | Location; + +export default function(event: EventData) { + if (event.type && event.type === CONSOLE) { + return new Console(event as ConsoleEvent) + } + if (event.type && event.type === CLICK) { + return new Click(event as ClickEvent) + } + if (event.type && event.type === INPUT) { + return new Input(event as InputEvent) + } + if (event.type && event.type === LOCATION) { + return new Location(event as LocationEvent) + } + if (event.type && event.type === CLICKRAGE) { + return new Click(event as ClickEvent, true) + } + // not used right now? + // if (event.type === CUSTOM || !event.type) { + // return new Event(event) + // } + console.error(`Unknown event type: ${event.type}`) +} + diff --git a/frontend/app/types/session/issue.js b/frontend/app/types/session/issue.js deleted file mode 100644 index 308bf32be..000000000 --- a/frontend/app/types/session/issue.js +++ /dev/null @@ -1,46 +0,0 @@ -import Record from 'Types/Record'; -import { List } from 'immutable'; -import Watchdog from 'Types/watchdog' -export const issues_types = List([ - { 'type': 'all', 'visible': true, 'order': 0, 'name': 'All', 'icon': '' }, - { 'type': 'js_exception', 'visible': true, 'order': 1, 'name': 'Errors', 'icon': 'funnel/exclamation-circle' }, - { 'type': 'bad_request', 'visible': true, 'order': 2, 'name': 'Bad Requests', 'icon': 'funnel/file-medical-alt' }, - { 'type': 'click_rage', 'visible': true, 'order': 3, 'name': 'Click Rage', 'icon': 'funnel/emoji-angry' }, - { 'type': 'crash', 'visible': true, 'order': 4, 'name': 'Crashes', 'icon': 'funnel/file-earmark-break' }, - // { 'type': 'memory', 'visible': true, 'order': 4, 'name': 'High Memory', 'icon': 'funnel/sd-card' }, - // { 'type': 'vault', 'visible': true, 'order': 5, 'name': 'Vault', 'icon': 'safe' }, - // { 'type': 'bookmark', 'visible': true, 'order': 5, 'name': 'Bookmarks', 'icon': 'safe' }, - // { 'type': 'bad_request', 'visible': true, 'order': 1, 'name': 'Bad Requests', 'icon': 'funnel/file-medical-alt' }, - // { 'type': 'missing_resource', 'visible': true, 'order': 2, 'name': 'Missing Images', 'icon': 'funnel/image' }, - // { 'type': 'dead_click', 'visible': true, 'order': 4, 'name': 'Dead Clicks', 'icon': 'funnel/dizzy' }, - // { 'type': 'cpu', 'visible': true, 'order': 6, 'name': 'High CPU', 'icon': 'funnel/cpu' }, - // { 'type': 'custom', 'visible': false, 'order': 8, 'name': 'Custom', 'icon': 'funnel/exclamation-circle' } -]).map(Watchdog) - -export const issues_types_map = {} -issues_types.forEach(i => { - issues_types_map[i.type] = { type: i.type, visible: i.visible, order: i.order, name: i.name, } -}); - -export default Record({ - issueId: undefined, - name: '', - visible: true, - sessionId: undefined, - time: undefined, - seqIndex: undefined, - payload: {}, - projectId: undefined, - type: '', - contextString: '', - context: '', - icon: 'info' -}, { - idKey: 'issueId', - fromJS: ({ type, ...rest }) => ({ - ...rest, - type, - icon: issues_types_map[type]?.icon, - name: issues_types_map[type]?.name, - }), -}); diff --git a/frontend/app/types/session/issue.ts b/frontend/app/types/session/issue.ts new file mode 100644 index 000000000..68ab64001 --- /dev/null +++ b/frontend/app/types/session/issue.ts @@ -0,0 +1,85 @@ +import Record from 'Types/Record'; + +const types = { + ALL: 'all', + JS_EXCEPTION: 'js_exception', + BAD_REQUEST: 'bad_request', + CRASH: 'crash', + CLICK_RAGE: 'click_rage' +} as const + +type TypeKeys = keyof typeof types +type TypeValues = typeof types[TypeKeys] + +type IssueType = { + [issueTypeKey in TypeValues]: { type: issueTypeKey; visible: boolean; order: number; name: string; icon: string }; +}; + +export const issues_types = [ + { 'type': types.ALL, 'visible': true, 'order': 0, 'name': 'All', 'icon': '' }, + { 'type': types.JS_EXCEPTION, 'visible': true, 'order': 1, 'name': 'Errors', 'icon': 'funnel/exclamation-circle' }, + { 'type': types.BAD_REQUEST, 'visible': true, 'order': 2, 'name': 'Bad Requests', 'icon': 'funnel/file-medical-alt' }, + { 'type': types.CLICK_RAGE, 'visible': true, 'order': 3, 'name': 'Click Rage', 'icon': 'funnel/emoji-angry' }, + { 'type': types.CRASH, 'visible': true, 'order': 4, 'name': 'Crashes', 'icon': 'funnel/file-earmark-break' }, + // { 'type': 'memory', 'visible': true, 'order': 4, 'name': 'High Memory', 'icon': 'funnel/sd-card' }, + // { 'type': 'vault', 'visible': true, 'order': 5, 'name': 'Vault', 'icon': 'safe' }, + // { 'type': 'bookmark', 'visible': true, 'order': 5, 'name': 'Bookmarks', 'icon': 'safe' }, + // { 'type': 'bad_request', 'visible': true, 'order': 1, 'name': 'Bad Requests', 'icon': 'funnel/file-medical-alt' }, + // { 'type': 'missing_resource', 'visible': true, 'order': 2, 'name': 'Missing Images', 'icon': 'funnel/image' }, + // { 'type': 'dead_click', 'visible': true, 'order': 4, 'name': 'Dead Clicks', 'icon': 'funnel/dizzy' }, + // { 'type': 'cpu', 'visible': true, 'order': 6, 'name': 'High CPU', 'icon': 'funnel/cpu' }, + // { 'type': 'custom', 'visible': false, 'order': 8, 'name': 'Custom', 'icon': 'funnel/exclamation-circle' } +] as const + +const issues_types_map = <IssueType>{} +issues_types.forEach((i) => { + Object.assign(issues_types_map, { + [i.type]: { + type: i.type, + visible: i.visible, + order: i.order, + name: i.name, + icon: i.icon, + } + }) +}); + +export interface IIssue { + issueId: string + name: string + visible: boolean + sessionId: string + time: number + payload: Record<string, any> + projectId: string + type: TypeValues + contextString: string + context: string + icon: string + timestamp: number + startedAt: number +} + +export default class Issue { + issueId: IIssue["issueId"] + name: IIssue["name"] + visible: IIssue["visible"] + sessionId: IIssue["sessionId"] + time: IIssue["time"] + payload: IIssue["payload"] + projectId: IIssue["projectId"] + type: IIssue["type"] + contextString: IIssue["contextString"] + context: IIssue["context"] + icon: IIssue["icon"] + key: number + + constructor({ type, ...rest }: IIssue & { key: number }) { + Object.assign(this, { + ...rest, + type, + icon: issues_types_map[type]?.icon, + name: issues_types_map[type]?.name + }) + } +} \ No newline at end of file diff --git a/frontend/app/types/session/log.js b/frontend/app/types/session/log.js deleted file mode 100644 index c1dbe0ef7..000000000 --- a/frontend/app/types/session/log.js +++ /dev/null @@ -1,39 +0,0 @@ -import Record from 'Types/Record'; - -export const INFO = 'info'; -export const LOG = 'log'; -export const WARNING = 'warning'; -export const WARN = 'warn'; -export const ERROR = 'error'; -export const EXCEPTION = 'exception'; -export const LEVEL = { - INFO, - LOG, - WARNING, - WARN, - ERROR, - EXCEPTION, -} - -export function isRed(log) { - return log.level === EXCEPTION || log.level === ERROR; -} - -export default Record({ - level: '', - value: '', - time: undefined, - index: undefined, - errorId: undefined, -}, { - methods: { - isRed() { - return isRed(this); - }, - isYellow() { - return this.level === WARNING || WARN; - } - } -}); - - diff --git a/frontend/app/types/session/profile.js b/frontend/app/types/session/profile.js deleted file mode 100644 index 98b9bc345..000000000 --- a/frontend/app/types/session/profile.js +++ /dev/null @@ -1,20 +0,0 @@ -import { List } from 'immutable'; -import Record from 'Types/Record'; - -export default Record({ - name: '', - args: List(), - result: undefined, - time: undefined, - index: undefined, - duration: undefined, -}, { - fromJS: ({ start_time, end_time, args, ...profile }) => ({ - ...profile, - args: List(args), - time: Math.round(start_time), - duration: Math.round(end_time - start_time || 0), - }), -}); - - diff --git a/frontend/app/types/session/reduxAction.js b/frontend/app/types/session/reduxAction.js deleted file mode 100644 index e03ab9ce9..000000000 --- a/frontend/app/types/session/reduxAction.js +++ /dev/null @@ -1,12 +0,0 @@ -import Record from 'Types/Record'; - -export default Record({ - time: undefined, - index: undefined, - action: {}, - state: undefined, - diff: [], - duration: undefined, -}); - - diff --git a/frontend/app/types/session/resource.js b/frontend/app/types/session/resource.js deleted file mode 100644 index 399db9337..000000000 --- a/frontend/app/types/session/resource.js +++ /dev/null @@ -1,119 +0,0 @@ -import { List } from 'immutable'; -import Record from 'Types/Record'; -import { getResourceName } from 'App/utils'; - -const XHR = 'xhr'; -const JS = 'script'; -const CSS = 'css'; -const IMG = 'img'; -const MEDIA = 'media'; -const OTHER = 'other'; -// -const FETCH = 'tracked_fetch'; -// -// const IMG_EXTENTIONS = [ "png", "gif", "jpg", "jpeg", "svg" ]; -// const MEDIA_EXTENTIONS = [ 'mp4', 'mkv', 'ogg', 'webm', 'avi', 'mp3' ]; -// -// function getResourceType(type, initiator, url) { -// if (type === 'xmlhttprequest') return XHR; // bad? -// if (type !== undefined) return type; -// if (initiator === 'xmlhttprequest' || initiator === 'fetch') return XHR; -// if (initiator === 'img') return IMG; -// const pathnameSplit = new URL(url).pathname.split('.'); -// if (pathnameSplit.length > 1) { -// const extention = pathnameSplit.pop(); -// if (extention === 'css') return CSS; -// if (extention === 'js') return JS; -// if (IMG_EXTENTIONS.includes(extention)) return IMG -// if (MEDIA_EXTENTIONS.includes(extention)) return MEDIA; -// } -// return OTHER; -// } - -const TYPES_MAP = { - "stylesheet": CSS, - "fetch": XHR, -} - -function getResourceStatus(status, success) { - if (status != null) return String(status); - if (typeof success === 'boolean' || typeof success === 'number') { - return !!success - ? '2xx-3xx' - : '4xx-5xx'; - } - return '2xx-3xx'; -} - -function getResourceSuccess(success, status) { - if (success != null) { return !!success } - if (status != null) { return status < 400 } - return true -} - -export const TYPES = { - XHR, - JS, - CSS, - IMG, - MEDIA, - OTHER, - FETCH, -} - -const YELLOW_BOUND = 10; -const RED_BOUND = 80; - -export function isRed(r) { - return !r.success || r.score >= RED_BOUND; -} -export function isYellow(r) { - return r.score < RED_BOUND && r.score >= YELLOW_BOUND; -} - -export default Record({ - type: OTHER, - url: '', - name: '', - status: '2xx-3xx', - duration: 0, - index: undefined, - time: undefined, - ttfb: 0, - timewidth: 0, - success: true, - score: 0, - // initiator: "other", - // pagePath: "", - method: '', - payload:'', - response: '', - headerSize: 0, - encodedBodySize: 0, - decodedBodySize: 0, - responseBodySize: 0, - timings: List(), -}, { - fromJS: ({ responseBody, response, type, initiator, status, success, time, datetime, timestamp, timings, ...resource }) => ({ - ...resource, - type: TYPES_MAP[type] || type, - name: getResourceName(resource.url), - status: getResourceStatus(status, success), - success: getResourceSuccess(success, status), - time: typeof time === 'number' ? time : datetime || timestamp, - response: responseBody || response, - ttfb: timings && timings.ttfb, - timewidth: timings && timings.timewidth, - timings, - }), - name: 'Resource', - methods: { - isRed() { - return isRed(this); - }, - isYellow() { - return isYellow(this); - } - } -}); - diff --git a/frontend/app/types/session/session.ts b/frontend/app/types/session/session.ts index 961b34864..3b254ae4b 100644 --- a/frontend/app/types/session/session.ts +++ b/frontend/app/types/session/session.ts @@ -1,15 +1,13 @@ -import Record from 'Types/Record'; -import { List, Map } from 'immutable'; import { Duration } from 'luxon'; -import SessionEvent, { TYPES } from './event'; -import Log from './log'; +import SessionEvent, { TYPES, EventData, InjectedEvent } from './event'; import StackEvent from './stackEvent'; -import Resource from './resource'; -import SessionError from './error'; -import Issue from './issue'; +import SessionError, { IError } from './error'; +import Issue, { IIssue } from './issue'; +import { Note } from 'App/services/NotesService' const HASH_MOD = 1610612741; const HASH_P = 53; + function hashString(s: string): number { let mul = 1; let hash = 0; @@ -20,172 +18,244 @@ function hashString(s: string): number { return hash; } -export default Record( - { - sessionId: '', - pageTitle: '', - active: false, - siteId: '', - projectKey: '', - peerId: '', - live: false, - startedAt: 0, - duration: 0, - events: List(), - logs: List(), - stackEvents: List(), - resources: List(), - missedResources: List(), - metadata: Map(), - favorite: false, - filterId: '', - messagesUrl: '', - domURL: [], - devtoolsURL: [], - mobsUrl: [], // @depricated - userBrowser: '', - userBrowserVersion: '?', - userCountry: '', - userDevice: '', - userDeviceType: '', - isMobile: false, - userOs: '', - userOsVersion: '', - userId: '', - userAnonymousId: '', - userUuid: undefined, - userDisplayName: '', - userNumericHash: 0, - viewed: false, - consoleLogCount: '?', - eventsCount: '?', - pagesCount: '?', - clickRage: undefined, - clickRageTime: undefined, - resourcesScore: 0, - consoleError: undefined, - resourceError: undefined, - returningLocation: undefined, - returningLocationTime: undefined, - errorsCount: 0, - watchdogs: [], - issueTypes: [], - issues: [], - userDeviceHeapSize: 0, - userDeviceMemorySize: 0, - errors: List(), - crashes: [], - socket: null, - isIOS: false, - revId: '', - userSessionsCount: 0, - agentIds: [], - isCallActive: false, - agentToken: '', - notes: [], - notesWithEvents: [], - fileKey: '', - }, - { - fromJS: ({ +export interface ISession { + sessionId: string, + pageTitle: string, + active: boolean, + siteId: string, + projectKey: string, + peerId: string, + live: boolean, + startedAt: number, + duration: number, + events: InjectedEvent[], + stackEvents: StackEvent[], + metadata: [], + favorite: boolean, + filterId?: string, + domURL: string[], + devtoolsURL: string[], + /** + * @deprecated + */ + mobsUrl: string[], + userBrowser: string, + userBrowserVersion: string, + userCountry: string, + userDevice: string, + userDeviceType: string, + isMobile: boolean, + userOs: string, + userOsVersion: string, + userId: string, + userAnonymousId: string, + userUuid: string, + userDisplayName: string, + userNumericHash: number, + viewed: boolean, + consoleLogCount: number, + eventsCount: number, + pagesCount: number, + errorsCount: number, + issueTypes: string[], + issues: [], + referrer: string | null, + userDeviceHeapSize: number, + userDeviceMemorySize: number, + errors: SessionError[], + crashes?: [], + socket: string, + isIOS: boolean, + revId: string | null, + agentIds?: string[], + isCallActive?: boolean, + agentToken: string, + notes: Note[], + notesWithEvents: Array<Note | InjectedEvent>, + fileKey: string, + platform: string, + projectId: string, + startTs: number, + timestamp: number, + backendErrors: number, + consoleErrors: number, + sessionID?: string, + userID: string, + userUUID: string, + userEvents: any[], +} + +const emptyValues = { + startTs: 0, + timestamp: 0, + backendErrors: 0, + consoleErrors: 0, + sessionID: '', + projectId: '', + errors: [], + stackEvents: [], + issues: [], + sessionId: '', + domURL: [], + devtoolsURL: [], + mobsUrl: [], + notes: [], + metadata: {}, + startedAt: 0, +} + +export default class Session { + sessionId: ISession["sessionId"] + pageTitle: ISession["pageTitle"] + active: ISession["active"] + siteId: ISession["siteId"] + projectKey: ISession["projectKey"] + peerId: ISession["peerId"] + live: ISession["live"] + startedAt: ISession["startedAt"] + duration: ISession["duration"] + events: ISession["events"] + stackEvents: ISession["stackEvents"] + metadata: ISession["metadata"] + favorite: ISession["favorite"] + filterId?: ISession["filterId"] + domURL: ISession["domURL"] + devtoolsURL: ISession["devtoolsURL"] + /** + * @deprecated + */ + mobsUrl: ISession["mobsUrl"] + userBrowser: ISession["userBrowser"] + userBrowserVersion: ISession["userBrowserVersion"] + userCountry: ISession["userCountry"] + userDevice: ISession["userDevice"] + userDeviceType: ISession["userDeviceType"] + isMobile: ISession["isMobile"] + userOs: ISession["userOs"] + userOsVersion: ISession["userOsVersion"] + userId: ISession["userId"] + userAnonymousId: ISession["userAnonymousId"] + userUuid: ISession["userUuid"] + userDisplayName: ISession["userDisplayName"] + userNumericHash: ISession["userNumericHash"] + viewed: ISession["viewed"] + consoleLogCount: ISession["consoleLogCount"] + eventsCount: ISession["eventsCount"] + pagesCount: ISession["pagesCount"] + errorsCount: ISession["errorsCount"] + issueTypes: ISession["issueTypes"] + issues: ISession["issues"] + referrer: ISession["referrer"] + userDeviceHeapSize: ISession["userDeviceHeapSize"] + userDeviceMemorySize: ISession["userDeviceMemorySize"] + errors: ISession["errors"] + crashes?: ISession["crashes"] + socket: ISession["socket"] + isIOS: ISession["isIOS"] + revId: ISession["revId"] + agentIds?: ISession["agentIds"] + isCallActive?: ISession["isCallActive"] + agentToken: ISession["agentToken"] + notes: ISession["notes"] + notesWithEvents: ISession["notesWithEvents"] + fileKey: ISession["fileKey"] + + constructor(plainSession?: ISession) { + const sessionData = plainSession || (emptyValues as unknown as ISession) + const { startTs = 0, timestamp = 0, backendErrors = 0, consoleErrors = 0, - projectId, - errors, + sessionID = '', + projectId = '', + errors = [], stackEvents = [], issues = [], - sessionId, - sessionID, + sessionId = '', domURL = [], devtoolsURL = [], mobsUrl = [], notes = [], ...session - }) => { - const duration = Duration.fromMillis(session.duration < 1000 ? 1000 : session.duration); - const durationSeconds = duration.valueOf(); - const startedAt = +startTs || +timestamp; + } = sessionData + const duration = Duration.fromMillis(session.duration < 1000 ? 1000 : session.duration); + const durationSeconds = duration.valueOf(); + const startedAt = +startTs || +timestamp; - const userDevice = session.userDevice || session.userDeviceType || 'Other'; - const userDeviceType = session.userDeviceType || 'other'; - const isMobile = ['console', 'mobile', 'tablet'].includes(userDeviceType); + const userDevice = session.userDevice || session.userDeviceType || 'Other'; + const userDeviceType = session.userDeviceType || 'other'; + const isMobile = ['console', 'mobile', 'tablet'].includes(userDeviceType); - const events = List(session.events) - .map((e) => SessionEvent({ ...e, time: e.timestamp - startedAt })) - .filter(({ type, time }) => type !== TYPES.CONSOLE && time <= durationSeconds); + const events: InjectedEvent[] = [] + const rawEvents: (EventData & { key: number })[] = [] - let resources = List(session.resources).map(Resource); - // this code shoud die. - const firstResourceTime = resources - .map((r) => r.time) - .reduce((a, b) => Math.min(a, b), Number.MAX_SAFE_INTEGER); - resources = resources - .map((r) => r.set('time', r.time - firstResourceTime)) - .sort((r1, r2) => r1.time - r2.time); - const missedResources = resources.filter(({ success }) => !success); - const logs = List(session.logs).map(Log); + if (session.events?.length) { + (session.events as EventData[]).forEach((event: EventData, k) => { + const time = event.timestamp - startedAt + if (event.type !== TYPES.CONSOLE && time <= durationSeconds) { + const EventClass = SessionEvent({ ...event, time, key: k }) + if (EventClass) { + events.push(EventClass); + } + rawEvents.push({ ...event, time, key: k }); + } + }) + } - const stackEventsList = List(stackEvents) - .concat(List(session.userEvents)) - .sortBy((se) => se.timestamp) - .map((se) => StackEvent({ ...se, time: se.timestamp - startedAt })); - const exceptions = List(errors).map(SessionError); + const stackEventsList: StackEvent[] = [] + if (stackEvents?.length || session.userEvents?.length) { + const mergedArrays = [...stackEvents, ...session.userEvents] + .sort((a, b) => a.timestamp - b.timestamp) + .map((se) => new StackEvent({ ...se, time: se.timestamp - startedAt })) + stackEventsList.push(...mergedArrays); + } - const issuesList = List(issues).map((e) => Issue({ ...e, time: e.timestamp - startedAt })); + const exceptions = (errors as IError[]).map(e => new SessionError(e)) || []; - const rawEvents = !session.events - ? [] - : // @ts-ignore - session.events - .map((evt) => ({ ...evt, time: evt.timestamp - startedAt })) - .filter(({ type, time }) => type !== TYPES.CONSOLE && time <= durationSeconds) || []; - const rawNotes = notes; - const notesWithEvents = [...rawEvents, ...rawNotes].sort((a, b) => { - const aTs = a.time || a.timestamp; - const bTs = b.time || b.timestamp; + const issuesList = (issues as IIssue[]).map( + (i, k) => new Issue({ ...i, time: i.timestamp - startedAt, key: k })) || []; - return aTs - bTs; - }); + const rawNotes = notes; + const notesWithEvents = [...rawEvents, ...rawNotes].sort((a, b) => { + // @ts-ignore just in case + const aTs = a.timestamp || a.time; + // @ts-ignore + const bTs = b.timestamp || b.time; - return { - ...session, - isIOS: session.platform === 'ios', - watchdogs: session.watchdogs || [], - errors: exceptions, - siteId: projectId, - events, - logs, - stackEvents: stackEventsList, - resources, - missedResources, - userDevice, - userDeviceType, - isMobile, - startedAt, - duration, - userNumericHash: hashString( - session.userId || - session.userAnonymousId || - session.userUuid || - session.userID || - session.userUUID || - '' - ), - userDisplayName: - session.userId || session.userAnonymousId || session.userID || 'Anonymous User', - firstResourceTime, - issues: issuesList, - sessionId: sessionId || sessionID, - userId: session.userId || session.userID, - mobsUrl: Array.isArray(mobsUrl) ? mobsUrl : [mobsUrl], - domURL, - devtoolsURL, - notes, - notesWithEvents: List(notesWithEvents), - }; - }, - idKey: 'sessionId', + return aTs - bTs; + }) || []; + + Object.assign(this, { + ...session, + isIOS: session.platform === 'ios', + errors: exceptions, + siteId: projectId, + events, + stackEvents: stackEventsList, + userDevice, + userDeviceType, + isMobile, + startedAt, + duration, + userNumericHash: hashString( + session.userId || + session.userAnonymousId || + session.userUuid || + session.userID || + session.userUUID || + '' + ), + userDisplayName: + session.userId || session.userAnonymousId || session.userID || 'Anonymous User', + issues: issuesList, + sessionId: sessionId || sessionID, + userId: session.userId || session.userID, + mobsUrl: Array.isArray(mobsUrl) ? mobsUrl : [mobsUrl], + domURL, + devtoolsURL, + notes, + notesWithEvents: notesWithEvents, + }) } -); +} \ No newline at end of file diff --git a/frontend/app/types/session/stackEvent.js b/frontend/app/types/session/stackEvent.js deleted file mode 100644 index 762407916..000000000 --- a/frontend/app/types/session/stackEvent.js +++ /dev/null @@ -1,59 +0,0 @@ -import Record from 'Types/Record'; - -export const OPENREPLAY = 'openreplay'; -export const SENTRY = 'sentry'; -export const DATADOG = 'datadog'; -export const STACKDRIVER = 'stackdriver'; -export const ROLLBAR = 'rollbar'; -export const NEWRELIC = 'newrelic'; -export const BUGSNAG = 'bugsnag'; -export const CLOUDWATCH = 'cloudwatch'; -export const ELASTICSEARCH = 'elasticsearch'; -export const SUMOLOGIC = 'sumologic'; - -export const typeList = [ OPENREPLAY, SENTRY, DATADOG, STACKDRIVER, ROLLBAR, BUGSNAG, CLOUDWATCH, ELASTICSEARCH, SUMOLOGIC ]; - -export function isRed(event) { - if (!event.payload) return false; - switch(event.source) { - case SENTRY: - return event.payload['event.type'] === 'error'; - case DATADOG: - return true; - case STACKDRIVER: - return false; - case ROLLBAR: - return true; - case NEWRELIC: - return true; - case BUGSNAG: - return true; - case CLOUDWATCH: - return true; - case SUMOLOGIC: - return false; - default: - return event.level==='error'; - } -} - -export default Record({ - time: undefined, - index: undefined, - name: '', - message: "", - payload: null, - source: null, - level: "", -}, { - fromJS: ue => ({ - ...ue, - source: ue.source || OPENREPLAY, - }), - methods: { - isRed() { - return isRed(this); - } - } -}); - diff --git a/frontend/app/types/session/stackEvent.ts b/frontend/app/types/session/stackEvent.ts new file mode 100644 index 000000000..8ce375fc2 --- /dev/null +++ b/frontend/app/types/session/stackEvent.ts @@ -0,0 +1,67 @@ +export const OPENREPLAY = 'openreplay'; +export const SENTRY = 'sentry'; +export const DATADOG = 'datadog'; +export const STACKDRIVER = 'stackdriver'; +export const ROLLBAR = 'rollbar'; +export const NEWRELIC = 'newrelic'; +export const BUGSNAG = 'bugsnag'; +export const CLOUDWATCH = 'cloudwatch'; +export const ELASTICSEARCH = 'elasticsearch'; +export const SUMOLOGIC = 'sumologic'; + +export const typeList = [OPENREPLAY, SENTRY, DATADOG, STACKDRIVER, ROLLBAR, BUGSNAG, CLOUDWATCH, ELASTICSEARCH, SUMOLOGIC]; + +export function isRed(event: IStackEvent) { + if (!event.payload) return false; + switch (event.source) { + case SENTRY: + return event.payload['event.type'] === 'error'; + case DATADOG: + return true; + case STACKDRIVER: + return false; + case ROLLBAR: + return true; + case NEWRELIC: + return true; + case BUGSNAG: + return true; + case CLOUDWATCH: + return true; + case SUMOLOGIC: + return false; + default: + return event.level === 'error'; + } +} + +export interface IStackEvent { + time: number; + timestamp: number; + index: number; + name: string; + message: string; + payload: any; + source: any; + level: string; + + isRed: boolean; +} + +export default class StackEvent { + time: IStackEvent["time"] + index: IStackEvent["index"]; + name: IStackEvent["name"]; + message: IStackEvent["message"]; + payload: IStackEvent["payload"]; + source: IStackEvent["source"]; + level: IStackEvent["level"]; + + constructor(evt: IStackEvent) { + const event = { ...evt, source: evt.source || OPENREPLAY } + Object.assign(this, { + ...event, + isRed: isRed(event), + }); + } +} diff --git a/frontend/app/types/step.js b/frontend/app/types/step.js deleted file mode 100644 index 438b403d8..000000000 --- a/frontend/app/types/step.js +++ /dev/null @@ -1,152 +0,0 @@ -import { Record, List, Set, isImmutable } from 'immutable'; -import { TYPES as EVENT_TYPES } from 'Types/session/event'; - -export const CUSTOM = 'custom'; -export const CLICK = 'click'; -export const INPUT = 'input'; -export const NAVIGATE = 'navigate'; -export const TEST = 'test'; - -export const TYPES = { - CLICK, - INPUT, - CUSTOM, - NAVIGATE, - TEST, -}; - - -const Step = defaultValues => class extends Record({ - key: undefined, - name: '', - imported: false, - isDisabled: false, - importTestId: undefined, - ...defaultValues, -}) { - hasTarget() { - return this.type === CLICK || this.type === INPUT; - } - - isTest() { - return this.type === TEST; - } - - getEventType() { - switch (this.type) { - case INPUT: - return EVENT_TYPES.INPUT; - case CLICK: - return EVENT_TYPES.CLICK; - case NAVIGATE: - return EVENT_TYPES.LOCATION; - default: - return null; - } - } - - validate() { - const selectorsOK = this.selectors && this.selectors.size > 0; - const valueOK = this.value && this.value.trim().length > 0; - switch (this.type) { - case INPUT: - return selectorsOK; - case CLICK: - return selectorsOK; - case NAVIGATE: - return valueOK; - case CUSTOM: - // if (this.name.length === 0) return false; - /* if (window.JSHINT) { - window.JSHINT(this.code, { esversion: 6 }); - const noErrors = window.JSHINT.errors.every(({ code }) => code && code.startsWith('W')); - return noErrors; - } */ - return this.code && this.code.length > 0; - default: - return true; - } - } - - toData() { - const { - value, - ...step - } = this.toJS(); - delete step.key; - return { - values: value && [ value ], - ...step, - }; - } -}; - -const Custom = Step({ - type: CUSTOM, - code: '', - framework: 'any', - template: '', -}); - -const Click = Step({ - type: CLICK, - selectors: List(), - customSelector: true, -}); - -const Input = Step({ - type: INPUT, - selectors: List(), - value: '', - customSelector: true, -}); - -const Navigate = Step({ - type: NAVIGATE, - value: '', -}); - -const TestAsStep = Step({ - type: TEST, - testId: '', - name: '', - stepsCount: '', - steps: List(), -}); - -const EmptyStep = Step(); - -let uniqueKey = 0xff; -function nextKey() { - uniqueKey += 1; - return `${ uniqueKey }`; -} - -function fromJS(initStep = {}) { - // TODO: more clear - if (initStep.importTestId) return new TestAsStep(initStep).set('steps', List(initStep.steps ? initStep.steps : initStep.test.steps).map(fromJS)); - // todo: ? - if (isImmutable(initStep)) return initStep.set('key', nextKey()); - - const values = initStep.values && initStep.values.length > 0 && initStep.values[ 0 ]; - - // bad code - const step = { - ...initStep, - selectors: Set(initStep.selectors).toList(), // to List not nrcrssary. TODO: check - value: initStep.value ? [initStep.value] : values, - key: nextKey(), - isDisabled: initStep.disabled - }; - // bad code - - if (step.type === CUSTOM) return new Custom(step); - if (step.type === CLICK) return new Click(step); - if (step.type === INPUT) return new Input(step); - if (step.type === NAVIGATE) return new Navigate(step); - - return new EmptyStep(); - // throw new Error(`Unknown step type: ${step.type}`); -} - -export default fromJS; diff --git a/frontend/app/types/synthetics/domBuildingTime.js b/frontend/app/types/synthetics/domBuildingTime.js deleted file mode 100644 index 258902f60..000000000 --- a/frontend/app/types/synthetics/domBuildingTime.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const DomBuildingTime = Record({ - avg: undefined, - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof DomBuildingTime) return data; - return new DomBuildingTime(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/synthetics/index.js b/frontend/app/types/synthetics/index.js deleted file mode 100644 index 1c693ae88..000000000 --- a/frontend/app/types/synthetics/index.js +++ /dev/null @@ -1,32 +0,0 @@ -import { Map, List } from 'immutable'; -import Session from 'Types/session'; -import { camelCased } from 'App/utils'; - -import { getChartFormatter } from './helper'; -import DomBuildingTime from './domBuildingTime'; -import ResourceLoadingTime from './resourceLoadingTime'; - -export const WIDGET_LIST = [ - { - key: "resourcesLoadingTime", - name: "Resource Fetch Time", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'resources', - dataWrapper: (list, period) => DomBuildingTime(list) - .update("chart", getChartFormatter(period)) - }, -]; - -export const WIDGET_KEYS = WIDGET_LIST.map(({ key }) => key); - -const WIDGET_MAP = {}; -WIDGET_LIST.forEach(w => { WIDGET_MAP[ w.key ] = w; }); - -const OVERVIEW_WIDGET_MAP = {}; -WIDGET_LIST.filter(w => w.type === 'overview').forEach(w => { OVERVIEW_WIDGET_MAP[ w.key ] = w; }); - -export { - WIDGET_MAP, - OVERVIEW_WIDGET_MAP -}; diff --git a/frontend/app/types/ts/search.ts b/frontend/app/types/ts/search.ts index 1c45bbe56..432f9e3f7 100644 --- a/frontend/app/types/ts/search.ts +++ b/frontend/app/types/ts/search.ts @@ -1,5 +1,3 @@ -import { List } from 'immutable'; - export interface SavedSearch { count: number; createdAt: number; @@ -10,5 +8,4 @@ export interface SavedSearch { projectId: number; searchId: number; userId: number; - watchdogs: List<any> } diff --git a/frontend/app/types/webhook.js b/frontend/app/types/webhook.js deleted file mode 100644 index 5024411f4..000000000 --- a/frontend/app/types/webhook.js +++ /dev/null @@ -1,22 +0,0 @@ -import Record from 'Types/Record'; -import { validateName, validateURL } from 'App/validate'; - -export default Record({ - webhookId: undefined, - type: undefined, - name: '', - endpoint: '', - authHeader: '', -}, { - idKey: 'webhookId', - methods: { - validate() { - return !!this.name && validateName(this.name) && !!this.endpoint && validateURL(this.endpoint); - }, - toData() { - const js = this.toJS(); - delete js.key; - return js; - }, - }, -}); diff --git a/frontend/app/types/webhook.ts b/frontend/app/types/webhook.ts new file mode 100644 index 000000000..a8771a623 --- /dev/null +++ b/frontend/app/types/webhook.ts @@ -0,0 +1,36 @@ +import { validateName, validateURL } from 'App/validate'; +import { makeAutoObservable } from 'mobx' + +export interface IWebhook { + webhookId: string + type: string + name: string + endpoint: string + authHeader: string +} + +export default class Webhook { + webhookId: IWebhook["webhookId"] + type: IWebhook["type"] + name: IWebhook["name"] = '' + endpoint: IWebhook["endpoint"] = '' + authHeader: IWebhook["authHeader"] = '' + + constructor(data: Partial<IWebhook> = {}) { + Object.assign(this, data) + + makeAutoObservable(this) + } + + toData() { + return { ...this }; + } + + validate() { + return !!this.name && validateName(this.name) && !!this.endpoint && validateURL(this.endpoint); + } + + exists() { + return !!this.webhookId + } +} \ No newline at end of file diff --git a/frontend/app/utils.ts b/frontend/app/utils.ts deleted file mode 100644 index e3cd50134..000000000 --- a/frontend/app/utils.ts +++ /dev/null @@ -1,382 +0,0 @@ -import JSBI from 'jsbi'; -import chroma from 'chroma-js'; -import * as htmlToImage from 'html-to-image'; -import { SESSION_FILTER } from 'App/constants/storageKeys'; - -export function debounce(callback, wait, context = this) { - let timeout = null; - let callbackArgs = null; - - const later = () => callback.apply(context, callbackArgs); - - return function (...args) { - callbackArgs = args; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; -} - -export function getResourceName(url = '') { - return url - .split('/') - .filter((s) => s !== '') - .pop(); -} - -/* eslint-disable no-mixed-operators */ -export function randomInt(a, b) { - const min = (b ? a : 0) - 0.5; - const max = b || a || Number.MAX_SAFE_INTEGER; - const rand = min - 0.5 + Math.random() * (max - min + 1); - return Math.round(rand); -} - -export const fileNameFormat = (str = '', ext = '') => { - const name = str.replace(/[^a-zA-Z0-9]/g, '_'); - return `${name}${ext}`; -}; - -export const toUnderscore = (s) => - s - .split(/(?=[A-Z])/) - .join('_') - .toLowerCase(); - -export const getUniqueFilter = (keys) => (item, i, list) => - !list.some((item2, j) => j < i && keys.every((key) => item[key] === item2[key] && item[key] !== undefined)); - -export const numberWithCommas = (x) => (x ? x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : 0); - -export const numberCompact = (x) => (x >= 1000 ? x / 1000 + 'k' : x); - -export const cutURL = (url, prefix = '.../') => `${prefix + url.split('/').slice(3).join('/')}`; - -export const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - -export function getRE(string: string, options: string) { - let re; - try { - re = new RegExp(string, options); - } catch (e) { - re = new RegExp(escapeRegExp(string), options); - } - return re; -} - -export const filterList = <T extends Record<string, any>>( - list: T[], - searchQuery: string, - testKeys: string[], - searchCb?: (listItem: T, query: RegExp -) => boolean): T[] => { - if (searchQuery === '') return list; - const filterRE = getRE(searchQuery, 'i'); - let _list = list.filter((listItem: T) => { - return testKeys.some((key) => filterRE.test(listItem[key]) || searchCb?.(listItem, filterRE)); - }); - return _list; - } - -export const getStateColor = (state) => { - switch (state) { - case 'passed': - return 'green'; - case 'failed': - return 'red'; - default: - return 'gray-medium'; - } -}; - -export const convertNumberRange = (oldMax, oldMin, newMin, newMax, currentValue) => { - let newValue; - let newRange; - const oldRange = oldMax - oldMin; - - if (oldRange === 0) { - newValue = newMin; - } else { - newRange = newMax - newMin; - newValue = ((currentValue - oldMin) * newRange) / oldRange + newMin; - } - return newValue; -}; - -export const prorata = ({ parts, elements, startDivisorFn, divisorFn }) => { - const byElement = Object.entries(elements).reduce( - (ack, [element, numElements]) => ({ - ...ack, - [element]: { parts: 0, elements: numElements, divisor: startDivisorFn(numElements) }, - }), - {} - ); - - while (parts > 0) { - const element = Object.entries(byElement).reduce((a, [k, v]) => (a.divisor > v.divisor ? a : v), { divisor: 0 }); - // eslint-disable-next-line no-plusplus - element.parts++; - element.divisor = divisorFn(element.elements, element.parts); - // eslint-disable-next-line no-plusplus - parts--; - } - return Object.entries(byElement).reduce((a, [k, v]) => ({ ...a, [k]: v.parts }), {}); -}; - -export const titleCase = (str) => { - str = str.toLowerCase(); - str = str.split('_'); - for (var i = 0; i < str.length; i++) { - str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); - } - - return str.join(' '); -}; - -export const confirm = (message, callback) => { - const sure = window.confirm(message); - if (!sure) return; - callback(); -}; - -const KB = 1 << 10; -const MB = KB << 10; -const GB = MB << 10; -export function formatBytes(bt: number): string { - if (bt > GB) { - return `${Math.trunc((bt / GB) * 1e2) / 1e2}GB`; - } - if (bt > MB) { - return `${Math.trunc((bt / MB) * 1e2) / 1e2}MB`; - } - if (bt > KB) { - return `${Math.trunc((bt / KB) * 1e2) / 1e2}KB`; - } - return `${bt}B`; -} - -export function percentOf(part: number, whole: number): number { - return whole > 0 ? (part * 100) / whole : 0; -} - -export function fileType(url: string) { - const filename = url.split(/[#?]/) - if (!filename || filename.length == 0) return '' - const parts = filename[0].split('.') - if (!parts || parts.length == 0) return '' - return parts.pop().trim(); -} - -export function fileName(url: string) { - if (url) { - var m = url.toString().match(/.*\/(.+?)\./); - if (m && m.length > 1) { - return `${m[1]}.${fileType(url)}`; - } - } - return ''; -} - -export const camelCased = (val) => - val.replace(/_([a-z])/g, function (g) { - return g[1].toUpperCase(); - }); - -export function capitalize(s: string) { - if (s.length === 0) return s; - return s[0].toUpperCase() + s.slice(1); -} - -export const titleize = (str) => { - let upper = true; - let newStr = ''; - for (let i = 0, l = str.length; i < l; i++) { - // Note that you can also check for all kinds of spaces with - // str[i].match(/\s/) - if (str[i] == ' ') { - upper = true; - newStr += str[i]; - continue; - } - if (str[i] == '_') { - upper = true; - newStr += ' '; - continue; - } - newStr += upper ? str[i].toUpperCase() : str[i].toLowerCase(); - upper = false; - } - return newStr; -}; - -/** - * (BigInt('2783377641436327') * BigInt(id) % BigInt('4503599627370496') + BigInt('4503599627370496')).toString() - * Replacing the above line of BigInt with JSBI since the BigInt not supportiing the some of the browser (i.e Safari (< 14), Opera). - */ -export const hashProjectID = (id) => { - if (!!id) { - return JSBI.add( - JSBI.remainder(JSBI.multiply(JSBI.BigInt('2783377641436327'), JSBI.BigInt(id)), JSBI.BigInt('4503599627370496')), - JSBI.BigInt('4503599627370496') - ).toString(); - } - return ''; -}; - -export const colorScale = (values, colors) => { - return chroma.scale(colors); -}; - -export const truncate = (input, max = 10) => (input.length > max ? `${input.substring(0, max)}...` : input); - -export const iceServerConfigFromString = (str) => { - if (!str || typeof str !== 'string' || str.length === 0) { - return null; - } - - return str.split('|').map(function (c) { - let server = null; - const arr = c.split(','); - - if (!!arr[0] !== '') { - server = {}; - server.urls = arr[0]; - if (!!arr[1]) { - server.username = arr[1]; - if (!!arr[2]) { - server.credential = arr[2]; - } - } - return server; - } - }); -}; - -export const isGreaterOrEqualVersion = (version, compareTo) => { - const [major, minor, patch] = version.split('-')[0].split('.'); - const [majorC, minorC, patchC] = compareTo.split('-')[0].split('.'); - return major > majorC || (major === majorC && minor > minorC) || (major === majorC && minor === minorC && patch >= patchC); -}; - -export const sliceListPerPage = <T extends Array<any>>(list: T, page: number, perPage = 10): T => { - const start = page * perPage; - const end = start + perPage; - return list.slice(start, end) as T; -}; - -export const positionOfTheNumber = (min, max, value, length) => { - const interval = (max - min) / length; - const position = Math.round((value - min) / interval); - return position; -}; - -export const convertElementToImage = async (el: HTMLElement) => { - // const fontEmbedCss = await htmlToImage.getFontEmbedCSS(el); - const image = await htmlToImage.toJpeg(el, { - pixelRatio: 2, - // fontEmbedCss, - filter: function (node) { - return node.id !== 'no-print'; - }, - }); - return image; -}; - -export const unserscoreToSpaceAndCapitalize = (str) => { - return str.replace(/_/g, ' ').replace(/\w\S*/g, (txt) => { - return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); - }); -}; - -export const convertToCSV = (headers, objArray) => { - var array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray; - var str = ''; - const headersMap = headers.reduce((acc, curr) => { - acc[curr.key] = curr; - return acc; - }, {}); - - str += headers.map((h) => h.label).join(',') + '\r\n'; - - for (var i = 0; i < array.length; i++) { - var line = ''; - for (var index in headersMap) { - if (line !== '') line += ','; - line += array[i][index]; - } - str += line + '\r\n'; - } - - return str; -}; - -export const exportCSVFile = (headers, items, fileTitle) => { - var jsonObject = JSON.stringify(items); - var csv = convertToCSV(headers, jsonObject); - var exportedFilenmae = fileTitle + '.csv' || 'export.csv'; - - var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); - if (navigator.msSaveBlob) { - // IE 10+ - navigator.msSaveBlob(blob, exportedFilenmae); - } else { - var link = document.createElement('a'); - if (link.download !== undefined) { - var url = URL.createObjectURL(blob); - link.setAttribute('href', url); - link.setAttribute('download', exportedFilenmae); - link.style.visibility = 'hidden'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } - } -}; - -export const fetchErrorCheck = async (response: any) => { - if (!response.ok) { - return Promise.reject(response); - } - return response.json(); -}; - -export const cleanSessionFilters = (data: any) => { - const { filters, ...rest } = data; - const _fitlers = filters.filter((f: any) => { - if (f.operator === 'isAny' || f.operator === 'onAny') { - return true; - } // ignore filter with isAny/onAny operator - if (Array.isArray(f.filters) && f.filters.length > 0) { - return true; - } // ignore subfilters - - return f.value !== '' && Array.isArray(f.value) && f.value.length > 0; - }); - return { ...rest, filters: _fitlers }; -}; - -export const getSessionFilter = () => { - return JSON.parse(localStorage.getItem(SESSION_FILTER)); -}; - -export const setSessionFilter = (filter: any) => { - localStorage.setItem(SESSION_FILTER, JSON.stringify(filter)); -}; - -export const compareJsonObjects = (obj1: any, obj2: any) => { - return JSON.stringify(obj1) === JSON.stringify(obj2); -}; - -export const getInitials = (name: any) => { - const names = name.split(' '); - return names.slice(0, 2).map((n: any) => n[0]).join(''); -} -export function getTimelinePosition(value: any, scale: any) { - const pos = value * scale; - return pos > 100 ? 100 : pos; -} - -export function millisToMinutesAndSeconds(millis: any) { - const minutes = Math.floor(millis / 60000); - const seconds: any = ((millis % 60000) / 1000).toFixed(0); - return minutes + 'm' + (seconds < 10 ? '0' : '') + seconds + 's'; -} diff --git a/frontend/app/utils/index.ts b/frontend/app/utils/index.ts new file mode 100644 index 000000000..47b40aa70 --- /dev/null +++ b/frontend/app/utils/index.ts @@ -0,0 +1,428 @@ +// @ts-nocheck +import JSBI from 'jsbi'; +import chroma from 'chroma-js'; +import * as htmlToImage from 'html-to-image'; +import { SESSION_FILTER } from 'App/constants/storageKeys'; + +export function debounce(callback, wait, context = this) { + let timeout = null; + let callbackArgs = null; + + const later = () => callback.apply(context, callbackArgs); + + return function (...args) { + callbackArgs = args; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} + +/* eslint-disable no-mixed-operators */ +export function randomInt(a, b) { + const min = (b ? a : 0) - 0.5; + const max = b || a || Number.MAX_SAFE_INTEGER; + const rand = min - 0.5 + Math.random() * (max - min + 1); + return Math.round(rand); +} + +export const fileNameFormat = (str = '', ext = '') => { + const name = str.replace(/[^a-zA-Z0-9]/g, '_'); + return `${name}${ext}`; +}; + +export const toUnderscore = (s) => + s + .split(/(?=[A-Z])/) + .join('_') + .toLowerCase(); + +export const getUniqueFilter = (keys) => (item, i, list) => + !list.some( + (item2, j) => j < i && keys.every((key) => item[key] === item2[key] && item[key] !== undefined) + ); + +export const numberWithCommas = (x) => (x ? x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : 0); + +export const numberCompact = (x) => (x >= 1000 ? x / 1000 + 'k' : x); + +export const cutURL = (url, prefix = '.../') => `${prefix + url.split('/').slice(3).join('/')}`; + +export const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + +export function getRE(string: string, options: string) { + let re; + try { + re = new RegExp(string, options); + } catch (e) { + re = new RegExp(escapeRegExp(string), options); + } + return re; +} + +export const filterList = <T extends Record<string, any>>( + list: T[], + searchQuery: string, + testKeys: string[], + searchCb?: (listItem: T, query: RegExp) => boolean +): T[] => { + if (searchQuery === '') return list; + const filterRE = getRE(searchQuery, 'i'); + let _list = list.filter((listItem: T) => { + return testKeys.some((key) => filterRE.test(listItem[key])) || searchCb?.(listItem, filterRE); + }); + return _list; +}; + +export const getStateColor = (state) => { + switch (state) { + case 'passed': + return 'green'; + case 'failed': + return 'red'; + default: + return 'gray-medium'; + } +}; + +export const convertNumberRange = (oldMax, oldMin, newMin, newMax, currentValue) => { + let newValue; + let newRange; + const oldRange = oldMax - oldMin; + + if (oldRange === 0) { + newValue = newMin; + } else { + newRange = newMax - newMin; + newValue = ((currentValue - oldMin) * newRange) / oldRange + newMin; + } + return newValue; +}; + +export const prorata = ({ parts, elements, startDivisorFn, divisorFn }) => { + const byElement = Object.entries(elements).reduce( + (ack, [element, numElements]) => ({ + ...ack, + [element]: { parts: 0, elements: numElements, divisor: startDivisorFn(numElements) }, + }), + {} + ); + + while (parts > 0) { + const element = Object.entries(byElement).reduce( + (a, [k, v]) => (a.divisor > v.divisor ? a : v), + { divisor: 0 } + ); + // eslint-disable-next-line no-plusplus + element.parts++; + element.divisor = divisorFn(element.elements, element.parts); + // eslint-disable-next-line no-plusplus + parts--; + } + return Object.entries(byElement).reduce((a, [k, v]) => ({ ...a, [k]: v.parts }), {}); +}; + +export const titleCase = (str) => { + str = str.toLowerCase(); + str = str.split('_'); + for (var i = 0; i < str.length; i++) { + str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); + } + + return str.join(' '); +}; + +export const confirm = (message, callback) => { + const sure = window.confirm(message); + if (!sure) return; + callback(); +}; + +const KB = 1 << 10; +const MB = KB << 10; +const GB = MB << 10; +export function formatBytes(bt: number): string { + if (bt > GB) { + return `${Math.trunc((bt / GB) * 1e2) / 1e2}GB`; + } + if (bt > MB) { + return `${Math.trunc((bt / MB) * 1e2) / 1e2}MB`; + } + if (bt > KB) { + return `${Math.trunc((bt / KB) * 1e2) / 1e2}KB`; + } + return `${bt}B`; +} + +export function percentOf(part: number, whole: number): number { + return whole > 0 ? (part * 100) / whole : 0; +} + +export function fileType(url: string) { + const filename = url.split(/[#?]/); + if (!filename || filename.length == 0) return ''; + const parts = filename[0].split('.'); + if (!parts || parts.length == 0) return ''; + return parts.pop().trim(); +} + +export function fileName(url: string) { + if (url) { + var m = url.toString().match(/.*\/(.+?)\./); + if (m && m.length > 1) { + return `${m[1]}.${fileType(url)}`; + } + } + return ''; +} + +export const camelCased = (val) => + val.replace(/_([a-z])/g, function (g) { + return g[1].toUpperCase(); + }); + +export function capitalize(s: string) { + if (s.length === 0) return s; + return s[0].toUpperCase() + s.slice(1); +} + +export const titleize = (str) => { + let upper = true; + let newStr = ''; + for (let i = 0, l = str.length; i < l; i++) { + // Note that you can also check for all kinds of spaces with + // str[i].match(/\s/) + if (str[i] == ' ') { + upper = true; + newStr += str[i]; + continue; + } + if (str[i] == '_') { + upper = true; + newStr += ' '; + continue; + } + newStr += upper ? str[i].toUpperCase() : str[i].toLowerCase(); + upper = false; + } + return newStr; +}; + +/** + * (BigInt('2783377641436327') * BigInt(id) % BigInt('4503599627370496') + BigInt('4503599627370496')).toString() + * Replacing the above line of BigInt with JSBI since the BigInt not supportiing the some of the browser (i.e Safari (< 14), Opera). + */ +export const hashProjectID = (id) => { + if (!!id) { + return JSBI.add( + JSBI.remainder( + JSBI.multiply(JSBI.BigInt('2783377641436327'), JSBI.BigInt(id)), + JSBI.BigInt('4503599627370496') + ), + JSBI.BigInt('4503599627370496') + ).toString(); + } + return ''; +}; + +export const colorScale = (values, colors) => { + return chroma.scale(colors); +}; + +export const truncate = (input, max = 10) => + input.length > max ? `${input.substring(0, max)}...` : input; + +export const iceServerConfigFromString = (str) => { + if (!str || typeof str !== 'string' || str.length === 0) { + return null; + } + + return str.split('|').map(function (c) { + let server = null; + const arr = c.split(','); + + if (!!arr[0] !== '') { + server = {}; + server.urls = arr[0]; + if (!!arr[1]) { + server.username = arr[1]; + if (!!arr[2]) { + server.credential = arr[2]; + } + } + return server; + } + }); +}; + +export const isGreaterOrEqualVersion = (version, compareTo) => { + const [major, minor, patch] = version.split('-')[0].split('.'); + const [majorC, minorC, patchC] = compareTo.split('-')[0].split('.'); + return ( + major > majorC || + (major === majorC && minor > minorC) || + (major === majorC && minor === minorC && patch >= patchC) + ); +}; + +export const sliceListPerPage = <T extends Array<any>>(list: T, page: number, perPage = 10): T => { + const start = page * perPage; + const end = start + perPage; + return list.slice(start, end) as T; +}; + +export const positionOfTheNumber = (min, max, value, length) => { + const interval = (max - min) / length; + const position = Math.round((value - min) / interval); + return position; +}; + +export const convertElementToImage = async (el: HTMLElement) => { + // const fontEmbedCss = await htmlToImage.getFontEmbedCSS(el); + const image = await htmlToImage.toJpeg(el, { + pixelRatio: 2, + // fontEmbedCss, + filter: function (node) { + return node.id !== 'no-print'; + }, + }); + return image; +}; + +export const unserscoreToSpaceAndCapitalize = (str) => { + return str.replace(/_/g, ' ').replace(/\w\S*/g, (txt) => { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); +}; + +export const convertToCSV = (headers, objArray) => { + var array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray; + var str = ''; + const headersMap = headers.reduce((acc, curr) => { + acc[curr.key] = curr; + return acc; + }, {}); + + str += headers.map((h) => h.label).join(',') + '\r\n'; + + for (var i = 0; i < array.length; i++) { + var line = ''; + for (var index in headersMap) { + if (line !== '') line += ','; + line += array[i][index]; + } + str += line + '\r\n'; + } + + return str; +}; + +export const exportCSVFile = (headers, items, fileTitle) => { + var jsonObject = JSON.stringify(items); + var csv = convertToCSV(headers, jsonObject); + var exportedFilenmae = fileTitle + '.csv' || 'export.csv'; + + var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); + if (navigator.msSaveBlob) { + // IE 10+ + navigator.msSaveBlob(blob, exportedFilenmae); + } else { + var link = document.createElement('a'); + if (link.download !== undefined) { + var url = URL.createObjectURL(blob); + link.setAttribute('href', url); + link.setAttribute('download', exportedFilenmae); + link.style.visibility = 'hidden'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + } +}; + +export const cleanSessionFilters = (data: any) => { + const { filters, ...rest } = data; + const _fitlers = filters.filter((f: any) => { + if (f.operator === 'isAny' || f.operator === 'onAny') { + return true; + } // ignore filter with isAny/onAny operator + if (Array.isArray(f.filters) && f.filters.length > 0) { + return true; + } // ignore subfilters + + return f.value !== '' && Array.isArray(f.value) && f.value.length > 0; + }); + return { ...rest, filters: _fitlers }; +}; + +export const getSessionFilter = () => { + return JSON.parse(localStorage.getItem(SESSION_FILTER)); +}; + +export const setSessionFilter = (filter: any) => { + localStorage.setItem(SESSION_FILTER, JSON.stringify(filter)); +}; + +export const compareJsonObjects = (obj1: any, obj2: any) => { + return JSON.stringify(obj1) === JSON.stringify(obj2); +}; + +export const getInitials = (name: any) => { + const names = name.split(' '); + return ( + names + .slice(0, 2) + .map((n: any) => n[0]?.toUpperCase()) + .join('') || '' + ); +}; +export function getTimelinePosition(value: any, scale: any) { + const pos = value * scale; + return pos > 100 ? 100 : pos; +} + +export function millisToMinutesAndSeconds(millis: any) { + const minutes = Math.floor(millis / 60000); + const seconds: any = ((millis % 60000) / 1000).toFixed(0); + return minutes + 'm' + (seconds < 10 ? '0' : '') + seconds + 's'; +} + +export function throttle(func, wait, options) { + var context, args, result; + var timeout = null; + var previous = 0; + if (!options) options = {}; + var later = function () { + previous = options.leading === false ? 0 : Date.now(); + timeout = null; + result = func.apply(context, args); + if (!timeout) context = args = null; + }; + return function () { + var now = Date.now(); + if (!previous && options.leading === false) previous = now; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = now; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; +} + +export function deleteCookie(name: string, path: string, domain: string) { + document.cookie = + name + + '=' + + (path ? ';path=' + path : '') + + (domain ? ';domain=' + domain : '') + + ';expires=Thu, 01 Jan 1970 00:00:01 GMT'; +} diff --git a/frontend/app/utils/screenRecorder.ts b/frontend/app/utils/screenRecorder.ts new file mode 100644 index 000000000..a434f5d39 --- /dev/null +++ b/frontend/app/utils/screenRecorder.ts @@ -0,0 +1,114 @@ +const FILE_TYPE = 'video/webm'; +const FRAME_RATE = 30; + +function createFileRecorder( + stream: MediaStream, + mimeType: string, + recName: string, + sessionId: string, + saveCb: (saveObj: { name: string; duration: number }, blob: Blob) => void, + onStop: () => void +) { + let ended = false; + const start = new Date().getTime(); + + let recordedChunks: BlobPart[] = []; + const SAVE_INTERVAL_MS = 200; + const mediaRecorder = new MediaRecorder(stream); + + mediaRecorder.ondataavailable = function (e) { + if (e.data.size > 0) { + recordedChunks.push(e.data); + } + }; + + function onEnd() { + if (ended) return; + + ended = true; + saveFile(recordedChunks, mimeType, start, recName, sessionId, saveCb); + onStop() + recordedChunks = []; + } + + // sometimes we get race condition or the onstop won't trigger at all, + // this is why we have to make it twice to make sure that stream is saved + // plus we want to be able to handle both, native and custom button clicks + mediaRecorder.stream.getTracks().forEach((track) => (track.onended = onEnd)); + mediaRecorder.onstop = () => { + onEnd(); + mediaRecorder.stream.getTracks().forEach((track) => track.stop()); + }; + mediaRecorder.start(SAVE_INTERVAL_MS); + + return mediaRecorder; +} + +function saveFile( + recordedChunks: BlobPart[], + mimeType: string, + startDate: number, + recName: string, + sessionId: string, + saveCb: (saveObj: { name: string; duration: number }, blob: Blob) => void +) { + const saveObject = { name: recName, duration: new Date().getTime() - startDate, sessionId }; + + const blob = new Blob(recordedChunks, { + type: mimeType, + }); + saveCb(saveObject, blob); + + const filename = recName + '.' + mimeType.split('/')[1]; + const downloadLink = document.createElement('a'); + downloadLink.href = URL.createObjectURL(blob); + downloadLink.download = filename; + + document.body.appendChild(downloadLink); + downloadLink.click(); + + URL.revokeObjectURL(downloadLink.href); // clear from memory + document.body.removeChild(downloadLink); +} + +async function recordScreen() { + return await navigator.mediaDevices.getDisplayMedia({ + audio: true, + video: { frameRate: FRAME_RATE }, + // potential chrome hack + // @ts-ignore + preferCurrentTab: true, + }); +} + +/** + * Creates a screen recorder that sends the media stream to MediaRecorder + * which then saves the stream to a file. + * + * Supported Browsers: + * + * Windows: Chrome v91+, Edge v90+ - FULL SUPPORT; + * *Nix: Chrome v91+, Edge v90+ - LIMITED SUPPORT - (audio only captured from current tab) + * + * @returns a promise that resolves to a function that stops the recording + */ +export async function screenRecorder(recName: string, sessionId: string, saveCb: (saveObj: { name: string; duration: number }, blob: Blob) => void, onStop: () => void) { + try { + const stream = await recordScreen(); + const mediaRecorder = createFileRecorder(stream, FILE_TYPE, recName, sessionId, saveCb, onStop); + + return () => { + if (mediaRecorder.state !== 'inactive') { + mediaRecorder.stop(); + onStop() + } + } + } catch (e) { + console.log(e); + } +} + +// NOT SUPPORTED: +// macOS: chrome and edge only support capturing current tab's audio +// windows: chrome and edge supports all audio +// other: not supported diff --git a/frontend/app/utils/search.ts b/frontend/app/utils/search.ts new file mode 100644 index 000000000..5779df732 --- /dev/null +++ b/frontend/app/utils/search.ts @@ -0,0 +1,124 @@ +import { getFilterKeyTypeByKey, setQueryParamKeyFromFilterkey } from 'Types/filter/filterType'; +import Period, { LAST_24_HOURS, LAST_7_DAYS, LAST_30_DAYS, CUSTOM_RANGE } from 'Types/app/period'; +import Filter from 'Types/filter/filter'; +import { filtersMap } from 'App/types/filter/newFilter'; + +export const createUrlQuery = (filter: any) => { + const query = []; + + for (const f of filter.filters) { + if (!f.value.length) { + continue; + } + + let str = `${f.operator}|${f.value.join('|')}`; + if (f.hasSource) { + str = `${str}^${f.sourceOperator ? f.sourceOperator : ''}|${f.source ? f.source.join('|') : ''}`; + } + + let key: any = setQueryParamKeyFromFilterkey(f.key); + if (!key) { + key = [f.key]; + } + + query.push({ key: key + '[]', value: str }); + } + + if (query.length > 0) { + query.push({ key: 'range[]', value: filter.rangeValue }); + if (filter.rangeValue === CUSTOM_RANGE) { + query.push({ key: 'rStart[]', value: filter.startDate }); + query.push({ key: 'rEnd[]', value: filter.endDate }); + } + } + + return query.map(({ key, value }) => `${key}=${value}`).join('&'); +}; + +export const getFiltersFromQuery = (search: string, filter: any) => { + if (!search || filter.filters.size > 0) { + return; + } + + const entires = getQueryObject(search); + const period: any = getPeriodFromEntries(entires); + const filters = getFiltersFromEntries(entires); + + return Filter({ filters, rangeValue: period.rangeName }); +}; + +const getFiltersFromEntries = (entires: any) => { + const _filters: any = { ...filtersMap }; + const filters: any = []; + if (entires.length > 0) { + entires.forEach((item: any) => { + if (!item.key || !item.value) { + return; + } + + let filter: any = {}; + const filterKey = getFilterKeyTypeByKey(item.key); + const tmp = item.value.split('^'); + const valueArr = tmp[0].split('|'); + const operator = valueArr.shift(); + const sourceArr = tmp[1] ? tmp[1].split('|') : []; + const sourceOperator = sourceArr.shift(); + + if (filterKey) { + filter.type = filterKey; + filter.key = filterKey; + } else { + filter = _filters[item.key]; + if (!!filter) { + filter.type = filter.key; + filter.key = filter.key; + } + } + + if (!filter) { + return + } + + filter.value = valueArr; + filter.operator = operator; + if (filter.icon === "filters/metadata") { + filter.source = filter.type; + filter.type = 'METADATA'; + } else { + filter.source = sourceArr && sourceArr.length > 0 ? sourceArr : null; + filter.sourceOperator = !!sourceOperator ? decodeURI(sourceOperator) : null; + } + + if (!filter.filters || filter.filters.size === 0) { + filters.push(filter); + } + }); + } + return filters; +}; + +const getPeriodFromEntries = (entires: any) => { + const rangeFilter = entires.find(({ key }: any) => key === 'range'); + if (!rangeFilter) { + return Period(); + } + + if (rangeFilter.value === CUSTOM_RANGE) { + const start = entires.find(({ key }: any) => key === 'rStart').value; + const end = entires.find(({ key }: any) => key === 'rEnd').value; + return Period({ rangeName: rangeFilter.value, start, end }); + } + + return Period({ rangeName: rangeFilter.value }); +}; + +function getQueryObject(search: any) { + let jsonArray = search + .slice(1) + .split('&') + .map((item: any) => { + let [key, value] = item.split('='); + return { key: key.slice(0, -2), value }; + }); + return jsonArray; +} diff --git a/frontend/app/validate.js b/frontend/app/validate.js index 76d588ac9..771fbd461 100644 --- a/frontend/app/validate.js +++ b/frontend/app/validate.js @@ -5,13 +5,7 @@ export function validateIP(value) { export function validateURL(value) { if (typeof value !== 'string') return false; - var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol - '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name - '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address - '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path - '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string - '(\\#[-a-z\\d_]*)?$','i'); // fragment locator - return !!pattern.test(value); + return /^[(ftp|http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z0-9]{1,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/i.test(value); } function escapeRegexp(s) { diff --git a/frontend/build.sh b/frontend/build.sh index f57d98af2..55d295746 100644 --- a/frontend/build.sh +++ b/frontend/build.sh @@ -8,7 +8,8 @@ # Example # Usage: IMAGE_TAG=latest DOCKER_REPO=myDockerHubID bash build.sh -git_sha1=${IMAGE_TAG:-$(git rev-parse HEAD)} +git_sha=$(git rev-parse --short HEAD) +image_tag=${IMAGE_TAG:-git_sha} ee="false" check_prereq() { which docker || { @@ -21,11 +22,14 @@ check_prereq() { export DOCKER_BUILDKIT=1 function build(){ # Run docker as the same user, else we'll run in to permission issues. - docker build -t ${DOCKER_REPO:-'local'}/frontend:${git_sha1} --platform linux/amd64 --build-arg SERVICE_NAME=$image . + docker build -t ${DOCKER_REPO:-'local'}/frontend:${image_tag} --platform linux/amd64 --build-arg GIT_SHA=$git_sha . [[ $PUSH_IMAGE -eq 1 ]] && { - docker push ${DOCKER_REPO:-'local'}/frontend:${git_sha1} + docker push ${DOCKER_REPO:-'local'}/frontend:${image_tag} } - echo "frotend build completed" + [[ $SIGN_IMAGE -eq 1 ]] && { + cosign sign --key $SIGN_KEY ${DOCKER_REPO:-'local'}/frontend:${image_tag} + } + echo "frontend build completed" } check_prereq diff --git a/frontend/cypress.config.ts b/frontend/cypress.config.ts new file mode 100644 index 000000000..ce9a3b91d --- /dev/null +++ b/frontend/cypress.config.ts @@ -0,0 +1,25 @@ +import { defineConfig } from "cypress"; +import {addMatchImageSnapshotPlugin} from 'cypress-image-snapshot/plugin'; + +const data = {} + +export default defineConfig({ + e2e: { + viewportHeight: 900, + viewportWidth: 1400, + baseUrl: 'http://0.0.0.0:3333/', + setupNodeEvents(on, config) { + // implement node event listeners here + addMatchImageSnapshotPlugin(on, config) + on('task', { + setValue({key, value}) { + data[key] = value + return null + }, + getValue(key) { + return data[key] || null + }, + }) + }, + } +}); diff --git a/frontend/cypress.env.example.json b/frontend/cypress.env.example.json new file mode 100644 index 000000000..465ee69d5 --- /dev/null +++ b/frontend/cypress.env.example.json @@ -0,0 +1,4 @@ +{ + "account": "test", + "password": "test" +} \ No newline at end of file diff --git a/frontend/cypress/e2e/generalStability.cy.ts b/frontend/cypress/e2e/generalStability.cy.ts new file mode 100644 index 000000000..f446bc962 --- /dev/null +++ b/frontend/cypress/e2e/generalStability.cy.ts @@ -0,0 +1,34 @@ +describe('Testing general stability', { + viewportHeight: 900, + viewportWidth: 1400, +}, () => { + it('Checking if app will crash', () => { + cy.intercept('**/api/account').as('getAccount'); + + cy.visit('/') + cy.get('[data-test-id=login]').type(Cypress.env('account').replaceAll('"', '')); + cy.get('[data-test-id=password]').type(Cypress.env('password').replaceAll('"', '')); + cy.get('[data-test-id=log-button]').click(); + cy.wait('@getAccount') + + cy.get('#search').should('be.visible') + + + cy.get('[href="/2/dashboard"] > span').click() + + cy.get(':nth-child(1) > .relative > :nth-child(1) > #menu-manage-alerts > .w-full').should('be.visible') + + + cy.visit('/client/account') + + cy.get(':nth-child(2) > .profileSettings-module__left--D4pCi > .profileSettings-module__info--DhVpL').should('be.visible') + + cy.get(':nth-child(3) > .relative > :nth-child(1) > .sideMenuItem-module__menuItem--UzuXv > .w-full > .sideMenuItem-module__iconLabel--Cl_48 > .sideMenuItem-module__title--IFkbw').click() + cy.get(':nth-child(4) > .relative > :nth-child(1) > .sideMenuItem-module__menuItem--UzuXv > .w-full > .sideMenuItem-module__iconLabel--Cl_48 > .sideMenuItem-module__title--IFkbw').click() + cy.get(':nth-child(5) > .relative > :nth-child(1) > .sideMenuItem-module__menuItem--UzuXv > .w-full > .sideMenuItem-module__iconLabel--Cl_48 > .sideMenuItem-module__title--IFkbw').click() + + cy.get('.webhooks-module__tabHeader--I0FXb').should('be.visible') + + // if test has not failed, we assume that app is not crashed (so far) + }) +}) \ No newline at end of file diff --git a/frontend/cypress/e2e/replayer.cy.ts b/frontend/cypress/e2e/replayer.cy.ts new file mode 100644 index 000000000..4c764498e --- /dev/null +++ b/frontend/cypress/e2e/replayer.cy.ts @@ -0,0 +1,150 @@ +const SECOND = 1000; + +describe( + 'Replayer visual match test', + { + viewportHeight: 900, + viewportWidth: 1400, + }, + () => { + it('Testing generated tracker session', () => { + cy.intercept('**/api/account').as('getAccount'); + cy.intercept('**/dom.mobs?*').as('getFirstMob') + + cy.visit('http://0.0.0.0:3333', { + onBeforeLoad: function (window) { + window.localStorage.setItem('notesFeatureViewed', 'true'); + }, + }); + + cy.origin('http://localhost:3000/', { args: { SECOND } }, ({ SECOND }) => { + cy.visit('/'); + cy.wait(SECOND * 3); + cy.get('#get-table').click() + cy.wait(SECOND * 3); + cy.get('#testrender').click(); + cy.wait(SECOND * 3); + cy.get('#testrender').click(); + cy.get('#get-main').click() + + cy.get('#obscured-text').type('testing typing in obscured input'); + cy.get('#visible-input').type('testing typing in visible input'); + cy.wait(SECOND * 3); + + cy.get('#redcounter').click().click().click(); + cy.get('#test-api').click().click(); + cy.get('#test-event').click().click(); + cy.wait(SECOND * 3); + + cy.log('finished generating a session') + + cy.window().then((window) => { + cy.task('setValue', { key: 'url', value: window.__OPENREPLAY__.app.getSessionURL() }); + cy.log(window.__OPENREPLAY__.app.getSessionURL()); + cy.wait(SECOND * 3) + }); + }); + + + cy.task('getValue', 'url').as('firstAlias'); + + cy.visit('/'); + cy.get('[data-test-id=login]').type(Cypress.env('account').replaceAll('"', '')); + cy.get('[data-test-id=password]').type(Cypress.env('password').replaceAll('"', '')); + cy.get('[data-test-id=log-button]').click(); + cy.wait('@getAccount'); + // checking real session + cy.get('@firstAlias').then((firstAlias) => { + cy.log(firstAlias); + cy.log('waiting for session to save') + cy.wait(SECOND * 180); + cy.visit(firstAlias.slice(27) + '?freeze=true'); + cy.log('loading session') + cy.wait(SECOND * 25); + + cy.window().then(win => { + const jumpMethod = win.playerJump ? win.playerJump : win.playerJumpToTime + jumpMethod(SECOND * 3) + }) + cy.wait(SECOND * 3); + cy.matchImageSnapshot('Tracker-3'); + + cy.window().then(win => { + win.playerJump(SECOND * 6) + }) + cy.wait(SECOND * 3); + cy.matchImageSnapshot('Tracker-5'); + + cy.window().then(win => { + const jumpMethod = win.playerJump ? win.playerJump : win.playerJumpToTime + jumpMethod(SECOND * 9) + }) + cy.wait(SECOND * 3); + cy.matchImageSnapshot('Tracker-9'); + + cy.window().then(win => { + const jumpMethod = win.playerJump ? win.playerJump : win.playerJumpToTime + jumpMethod(SECOND * 20) + }) + cy.wait(SECOND * 3); + cy.get('#control-button-redux > .controlButton-module__label--YznMl').click() + cy.wait(SECOND * 0.5) + cy.matchImageSnapshot('Tracker-19-redux'); + + cy.get('#control-button-network').click() + cy.wait(SECOND * 0.5) + cy.matchImageSnapshot('Tracker-19-network'); + + cy.get('#control-button-events').click() + cy.wait(SECOND * 0.5) + cy.matchImageSnapshot('Tracker-19-events'); + + cy.log('custom session test success') + }); + }); + + it('Checking Replayer at breakpoints, user events and console', () => { + cy.intercept('**/api/account').as('getAccount') + cy.intercept('**/mobs/7585361734083637/dom.mobs?*').as('getFirstMob') + cy.intercept('**/mobs/7585361734083637/dom.mobe?*').as('getSecondMob') + cy.log('testing premade session') + + cy.visit('http://0.0.0.0:3333', { + onBeforeLoad: function (window) { + window.localStorage.setItem('notesFeatureViewed', 'true'); + } + }) + cy.get('[data-test-id=login]').type(Cypress.env('account').replaceAll('"', '')); + cy.get('[data-test-id=password]').type(Cypress.env('password').replaceAll('"', '')); + cy.get('[data-test-id=log-button]').click(); + cy.wait('@getAccount') + cy.wait(SECOND * 2) + cy.visit('3/session/7585361734083637?jumpto=7500&freeze=true') + cy.wait('@getFirstMob') + cy.wait('@getSecondMob') + cy.wait(SECOND * 2) + + cy.window().then(win => { + const jumpMethod = win.playerJump ? win.playerJump : win.playerJumpToTime + jumpMethod(SECOND * 7.5) + }) + cy.wait(SECOND * 4) + cy.matchImageSnapshot('1st-breakpoint'); + + cy.window().then(win => { + const jumpMethod = win.playerJump ? win.playerJump : win.playerJumpToTime + jumpMethod(SECOND * 21) + }) + cy.wait(SECOND * 4) + cy.matchImageSnapshot('2nd-breakpoint'); + + cy.get('[data-openreplay-label="User Steps"]').click() + cy.wait(SECOND * 0.5) + cy.matchImageSnapshot('User-Events'); + + cy.get('#control-button-network > .controlButton-module__label--YznMl').click() + cy.wait(SECOND * 0.5) + cy.matchImageSnapshot('Network-Events'); + }) + } +); diff --git a/frontend/cypress/fixtures/example.json b/frontend/cypress/fixtures/example.json new file mode 100644 index 000000000..02e425437 --- /dev/null +++ b/frontend/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/frontend/cypress/plugins/index.js b/frontend/cypress/plugins/index.js new file mode 100644 index 000000000..9d50d97dd --- /dev/null +++ b/frontend/cypress/plugins/index.js @@ -0,0 +1,7 @@ +const { + addMatchImageSnapshotPlugin, +} = require('cypress-image-snapshot/plugin'); + +module.exports = (on, config) => { + addMatchImageSnapshotPlugin(on, config); +}; \ No newline at end of file diff --git a/frontend/cypress/snapshots/replayer.cy.ts/1st-breakpoint.snap.png b/frontend/cypress/snapshots/replayer.cy.ts/1st-breakpoint.snap.png new file mode 100644 index 000000000..364532687 Binary files /dev/null and b/frontend/cypress/snapshots/replayer.cy.ts/1st-breakpoint.snap.png differ diff --git a/frontend/cypress/snapshots/replayer.cy.ts/2nd-breakpoint.snap.png b/frontend/cypress/snapshots/replayer.cy.ts/2nd-breakpoint.snap.png new file mode 100644 index 000000000..e2b7e9f92 Binary files /dev/null and b/frontend/cypress/snapshots/replayer.cy.ts/2nd-breakpoint.snap.png differ diff --git a/frontend/cypress/snapshots/replayer.cy.ts/Network-Events.snap.png b/frontend/cypress/snapshots/replayer.cy.ts/Network-Events.snap.png new file mode 100644 index 000000000..d7f41fd2f Binary files /dev/null and b/frontend/cypress/snapshots/replayer.cy.ts/Network-Events.snap.png differ diff --git a/frontend/cypress/snapshots/replayer.cy.ts/Tracker-19-events.snap.png b/frontend/cypress/snapshots/replayer.cy.ts/Tracker-19-events.snap.png new file mode 100644 index 000000000..ecc752424 Binary files /dev/null and b/frontend/cypress/snapshots/replayer.cy.ts/Tracker-19-events.snap.png differ diff --git a/frontend/cypress/snapshots/replayer.cy.ts/Tracker-19-network.snap.png b/frontend/cypress/snapshots/replayer.cy.ts/Tracker-19-network.snap.png new file mode 100644 index 000000000..a4b38784b Binary files /dev/null and b/frontend/cypress/snapshots/replayer.cy.ts/Tracker-19-network.snap.png differ diff --git a/frontend/cypress/snapshots/replayer.cy.ts/Tracker-19-redux.snap.png b/frontend/cypress/snapshots/replayer.cy.ts/Tracker-19-redux.snap.png new file mode 100644 index 000000000..73a139809 Binary files /dev/null and b/frontend/cypress/snapshots/replayer.cy.ts/Tracker-19-redux.snap.png differ diff --git a/frontend/cypress/snapshots/replayer.cy.ts/Tracker-3.snap.png b/frontend/cypress/snapshots/replayer.cy.ts/Tracker-3.snap.png new file mode 100644 index 000000000..1e167e082 Binary files /dev/null and b/frontend/cypress/snapshots/replayer.cy.ts/Tracker-3.snap.png differ diff --git a/frontend/cypress/snapshots/replayer.cy.ts/Tracker-5.snap.png b/frontend/cypress/snapshots/replayer.cy.ts/Tracker-5.snap.png new file mode 100644 index 000000000..a179465c9 Binary files /dev/null and b/frontend/cypress/snapshots/replayer.cy.ts/Tracker-5.snap.png differ diff --git a/frontend/cypress/snapshots/replayer.cy.ts/Tracker-9.snap.png b/frontend/cypress/snapshots/replayer.cy.ts/Tracker-9.snap.png new file mode 100644 index 000000000..96ade75f6 Binary files /dev/null and b/frontend/cypress/snapshots/replayer.cy.ts/Tracker-9.snap.png differ diff --git a/frontend/cypress/snapshots/replayer.cy.ts/User-Events.snap.png b/frontend/cypress/snapshots/replayer.cy.ts/User-Events.snap.png new file mode 100644 index 000000000..0f2c8e4bc Binary files /dev/null and b/frontend/cypress/snapshots/replayer.cy.ts/User-Events.snap.png differ diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts new file mode 100644 index 000000000..7288dd0bd --- /dev/null +++ b/frontend/cypress/support/commands.ts @@ -0,0 +1,45 @@ +/// <reference types="cypress" /> +// *********************************************** +// This example commands.ts shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +// +// declare global { +// namespace Cypress { +// interface Chainable { +// login(email: string, password: string): Chainable<void> +// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element> +// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element> +// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element> +// } +// } +// } +import { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command'; + +addMatchImageSnapshotCommand({ + failureThreshold: 0.03, // threshold for entire image + failureThresholdType: "percent", // percent of image or number of pixels + customDiffConfig: { threshold: 0.1 }, // threshold for each pixel + capture: "viewport" // capture viewport in screenshot +}); \ No newline at end of file diff --git a/frontend/cypress/support/e2e.ts b/frontend/cypress/support/e2e.ts new file mode 100644 index 000000000..f80f74f8e --- /dev/null +++ b/frontend/cypress/support/e2e.ts @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/e2e.ts is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') \ No newline at end of file diff --git a/frontend/jest.config.js b/frontend/jest.config.js index 729cf29a6..959d918c1 100644 --- a/frontend/jest.config.js +++ b/frontend/jest.config.js @@ -1,3 +1,34 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { - // "preset": "jest-puppeteer" -} \ No newline at end of file + preset: 'ts-jest', + rootDir: './', + testEnvironment: 'node', + moduleNameMapper: { + '^Types/session/(.+)$': '<rootDir>/app/types/session/$1', + '^App/(.+)$': '<rootDir>/app/$1', + }, + transform: { + '^.+\\.(ts|tsx)?$': ['ts-jest', { isolatedModules: true, diagnostics: { warnOnly: true } }], + '^.+\\.(js|jsx)$': 'babel-jest', + }, + moduleDirectories: ['node_modules', 'app'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], +}; + +// +// module.exports = { +// globals: { +// "ts-jest": { +// tsConfig: "tsconfig.json", +// diagnostics: true +// }, +// NODE_ENV: "test" +// }, +// moduleNameMapper: { +// "^Types/(.+)$": "<rootDir>/app/types/$1" +// }, +// moduleDirectories: ["node_modules", 'app'], +// moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json"], + +// verbose: true +// }; \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index c4f0a68de..5467b3cc7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,9 +12,15 @@ "gen:constants": "node ./scripts/constants.js", "gen:icons": "node ./scripts/icons.ts", "gen:colors": "node ./scripts/colors.js", - "storybook": "start-storybook", + "storybook": "start-storybook -p 6006", "flow": "flow", - "postinstall": "yarn gen:icons && yarn gen:colors" + "gen:static": "yarn gen:icons && yarn gen:colors", + "build-storybook": "build-storybook", + "test": "jest", + "cy:open": "cypress open", + "cy:test": "cypress run --browser chrome", + "cy:test-firefox": "cypress run --browser firefox", + "cy:test-edge": "cypress run --browser edge" }, "dependencies": { "@floating-ui/react-dom-interactions": "^0.10.3", @@ -42,7 +48,6 @@ "react": "^18.2.0", "react-circular-progressbar": "^2.1.0", "react-confirm": "^0.2.3", - "react-date-range": "^1.4.0", "react-daterange-picker": "^2.0.1", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^15.1.2", @@ -85,7 +90,18 @@ "@babel/preset-react": "^7.17.12", "@babel/preset-typescript": "^7.17.12", "@babel/runtime": "^7.17.9", + "@jest/globals": "^29.3.1", + "@mdx-js/react": "^1.6.22", "@openreplay/sourcemap-uploader": "^3.0.0", + "@storybook/addon-actions": "^6.5.12", + "@storybook/addon-docs": "^6.5.12", + "@storybook/addon-essentials": "^6.5.12", + "@storybook/addon-interactions": "^6.5.12", + "@storybook/addon-links": "^6.5.12", + "@storybook/builder-webpack5": "^6.5.12", + "@storybook/manager-webpack5": "^6.5.12", + "@storybook/react": "^6.5.12", + "@storybook/testing-library": "^0.0.13", "@types/luxon": "^3.0.0", "@types/react": "^18.0.9", "@types/react-dom": "^18.0.4", @@ -96,14 +112,16 @@ "@typescript-eslint/parser": "^5.24.0", "autoprefixer": "^10.4.7", "babel-loader": "^8.2.4", + "babel-plugin-react-require": "^3.1.3", "babel-plugin-recharts": "^1.2.1", "babel-plugin-transform-decorators-legacy": "^1.3.5", - "circular-dependency-plugin": "^5.2.0", "compression-webpack-plugin": "^10.0.0", "copy-webpack-plugin": "^11.0.0", "country-data": "0.0.31", "css-loader": "^6.7.1", "cssnano": "^5.0.12", + "cypress": "^12.3.0", + "cypress-image-snapshot": "^4.0.1", "deasync-promise": "^1.0.1", "deploy-aws-s3-cloudfront": "^3.6.0", "dotenv": "^6.2.0", @@ -114,6 +132,7 @@ "file-loader": "^6.2.0", "flow-bin": "^0.115.0", "html-webpack-plugin": "^5.5.0", + "jest": "^29.3.1", "mini-css-extract-plugin": "^2.6.0", "minio": "^7.0.18", "moment-locales-webpack-plugin": "^1.2.0", @@ -131,6 +150,7 @@ "svg-inline-loader": "^0.8.2", "svgo": "^2.8.0", "tailwindcss": "^3.1.4", + "ts-jest": "^29.0.5", "ts-node": "^10.7.0", "typescript": "^4.6.4", "webpack": "^5.72.1", diff --git a/frontend/scripts/colors.js b/frontend/scripts/colors.js index 928fd2275..2baf1a6b0 100644 --- a/frontend/scripts/colors.js +++ b/frontend/scripts/colors.js @@ -8,6 +8,7 @@ fs.writeFileSync('app/styles/colors-autogen.css', `/* Auto-generated, DO NOT EDI /* fill */ ${ colors.map(color => `.fill-${ color } { fill: $${ color } }`).join('\n') } +${ colors.map(color => `.hover-fill-${ color }:hover svg { fill: $${ color } }`).join('\n') } /* color */ ${ colors.map(color => `.color-${ color } { color: $${ color } }`).join('\n') } diff --git a/frontend/scripts/constants.js b/frontend/scripts/constants.js index 32b4de9a6..759dcb621 100644 --- a/frontend/scripts/constants.js +++ b/frontend/scripts/constants.js @@ -3,7 +3,7 @@ const countries = require('country-data').countries; delete countries['UK']; delete countries['EU']; -for (code in countries) { +for (let code in countries) { const country = countries[code]; if (code.length != 2) { delete countries[code]; diff --git a/frontend/scripts/icons.ts b/frontend/scripts/icons.ts index c3b5aaf9e..dab11b224 100644 --- a/frontend/scripts/icons.ts +++ b/frontend/scripts/icons.ts @@ -90,6 +90,9 @@ ${icons.map(icon => { .replace(/xmlns\:xlink/g, 'xmlnsXlink') .replace(/clip-path/g, 'clipPath') .replace(/clip-rule/g, 'clipRule') + // hack to keep fill rule for some icons like stop recording square + .replace(/clipRule="evenoddCustomFill"/g, 'clipRule="evenodd" fillRule="evenodd"') + .replace(/fill-rule/g, 'fillRule') .replace(/fill-opacity/g, 'fillOpacity') .replace(/stop-color/g, 'stopColor') .replace(/xml:space="preserve"/g, '')};`; diff --git a/ee/sourcemap-reader/Readme.md b/frontend/stories/Introduction.stories.mdx similarity index 100% rename from ee/sourcemap-reader/Readme.md rename to frontend/stories/Introduction.stories.mdx diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index c6cb61fa5..8eaeab4e8 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,5 +1,7 @@ const colors = require('./app/theme/colors'); +const defaultColors = require('tailwindcss/colors') +console.log(defaultColors); module.exports = { // important: true, content: [ @@ -106,7 +108,10 @@ module.exports = { // 'zIndex' // ], theme: { - colors, + colors: { + ...defaultColors, + ...colors, + }, // borderColor: { // default: '#DDDDDD', // "gray-light-shade": colors["gray-light-shade"], diff --git a/frontend/tests/mocks/devtools.mob b/frontend/tests/mocks/devtools.mob new file mode 100644 index 000000000..f0960c7f6 Binary files /dev/null and b/frontend/tests/mocks/devtools.mob differ diff --git a/frontend/tests/mocks/dom1.mobs b/frontend/tests/mocks/dom1.mobs new file mode 100644 index 000000000..e4f1a947f Binary files /dev/null and b/frontend/tests/mocks/dom1.mobs differ diff --git a/frontend/tests/mocks/dom2.mobe b/frontend/tests/mocks/dom2.mobe new file mode 100644 index 000000000..a190e354f Binary files /dev/null and b/frontend/tests/mocks/dom2.mobe differ diff --git a/frontend/tests/mocks/sessionData.js b/frontend/tests/mocks/sessionData.js new file mode 100644 index 000000000..f679959ae --- /dev/null +++ b/frontend/tests/mocks/sessionData.js @@ -0,0 +1,175 @@ +export const issues = [ + { + issueId: '9158adad14bcb1e0db18384c778a18f5ef2', + sessionId: 8119081922378909, + timestamp: 1673887658753, + seqIndex: 26901, + payload: { Rate: 71, Duration: 20804 }, + projectId: 2325, + type: 'cpu', + contextString: 'https://app.openreplay.com/5095/session/8118970199817356', + context: null, + icon: undefined, + key: 0, + name: undefined, + time: 300321, + }, + { + issueId: '915b2c49d8e08176a84c0d8e58732995fb8', + sessionId: 8119081922378909, + timestamp: 1673888064761, + seqIndex: 74984, + payload: { Rate: 80, Duration: 9684 }, + projectId: 2325, + type: 'cpu', + contextString: 'https://app.openreplay.com/5095/session/8118613089434832', + context: null, + icon: undefined, + key: 1, + name: undefined, + time: 706329, + }, +]; +export const events = [ + { + time: 519, + label: undefined, + key: 0, + target: { path: undefined, label: undefined }, + name: 'Location', + type: 'LOCATION', + sessionId: 8119081922378909, + messageId: 14, + timestamp: 1673887358951, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: 1637, + firstPaintTime: 1674, + firstContentfulPaintTime: 1674, + loadTime: 1870, + speedIndex: 1889, + visuallyComplete: 5110, + timeToInteractive: 5328, + domBuildingTime: 337, + path: '/3064/sessions', + baseReferrer: '', + responseTime: 2, + responseEnd: 1295, + ttfb: null, + query: '', + value: '/3064/sessions', + url: '/3064/sessions', + fcpTime: 1674, + }, + { + time: 10133, + label: + 'Inicio De Prova SESSIONS ASSIST DASHBOARDS FD Saved Search 0 Clear Search SESSIONS BOOKMARKS NOTES A', + key: 1, + target: { path: undefined, label: undefined }, + type: 'CLICK', + name: 'Click', + targetContent: undefined, + count: undefined, + }, + { + time: 12287, + label: 'Past 7 Days', + key: 2, + target: { path: undefined, label: undefined }, + type: 'CLICK', + name: 'Click', + targetContent: undefined, + count: undefined, + }, + { + time: 13146, + label: 'Search sessions using any captured event (click, input, page, error...)', + key: 3, + target: { path: undefined, label: undefined }, + type: 'CLICK', + name: 'Click', + targetContent: undefined, + count: undefined, + }, + { + time: 13777, + label: + 'Inicio De Prova Add Project Inicio De Prova Nova Proposta SESSIONS ASSIST DASHBOARDS FD INTERACTIONS', + key: 4, + target: { path: undefined, label: undefined }, + type: 'CLICK', + name: 'Click', + targetContent: undefined, + count: undefined, + }, + { + time: 14440, + label: 'Nova Proposta', + key: 5, + target: { path: undefined, label: undefined }, + type: 'CLICK', + name: 'Click', + targetContent: undefined, + count: undefined, + }, + { + time: 14496, + label: undefined, + key: 6, + target: { path: undefined, label: undefined }, + name: 'Location', + type: 'LOCATION', + sessionId: 8119081922378909, + messageId: 2038, + timestamp: 1673887372928, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/sessions', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/sessions', + url: '/5095/sessions', + fcpTime: null, + }, + { + time: 15166, + label: 'Search sessions using any captured event (click, input, page, error...)', + key: 7, + target: { path: undefined, label: undefined }, + type: 'CLICK', + name: 'Click', + targetContent: undefined, + count: undefined, + }, + { + time: 15726, + label: 'Search sessions using any captured event (click, input, page, error...)', + key: 8, + target: { path: undefined, label: undefined }, + type: 'INPUT', + name: 'Input', + value: 'click', + }, + { + time: 17270, + label: 'Click', + key: 9, + target: { path: undefined, label: undefined }, + type: 'CLICK', + name: 'Click', + targetContent: undefined, + count: undefined, + }, +]; diff --git a/frontend/tests/mocks/sessionResponse.js b/frontend/tests/mocks/sessionResponse.js new file mode 100644 index 000000000..6064aec0a --- /dev/null +++ b/frontend/tests/mocks/sessionResponse.js @@ -0,0 +1,12863 @@ +export const session = { + data: { + sessionId: '8119081922378909', + projectId: 2325, + startTs: 1673887358432, + duration: 789966, + userId: 'fernando.dufour@pravaler.com.br', + userAnonymousId: null, + userUuid: '94e71049-8701-45e6-b9ce-b246714ccbc5', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', + userOs: 'Mac OS X', + userBrowser: 'Chrome', + userDevice: '', + userDeviceType: 'desktop', + userCountry: 'BR', + pagesCount: 23, + eventsCount: 362, + errorsCount: 0, + revId: 'Saas_1.9.0', + userOsVersion: '10.15.7', + userBrowserVersion: '108.0.0', + userDeviceHeapSize: 2172649472, + userDeviceMemorySize: 8192, + trackerVersion: '4.1.8', + watchdogsScore: 0, + platform: 'web', + issueScore: 1673889358, + issueTypes: '{cpu}', + isSnippet: false, + rehydrationId: null, + utmSource: null, + utmMedium: null, + utmCampaign: null, + referrer: null, + baseReferrer: null, + fileKey: null, + projectKey: '9WiKbZukI3t64rIc3lnn', + favorite: false, + viewed: true, + events: [ + { + sessionId: 8119081922378909, + messageId: 14, + timestamp: 1673887358951, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: 1637, + firstPaintTime: 1674, + firstContentfulPaintTime: 1674, + loadTime: 1870, + speedIndex: 1889, + visuallyComplete: 5110, + timeToInteractive: 5328, + domBuildingTime: 337, + path: '/3064/sessions', + baseReferrer: '', + responseTime: 2, + responseEnd: 1295, + ttfb: null, + query: '', + value: '/3064/sessions', + url: '/3064/sessions', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 993, + timestamp: 1673887368565, + label: + 'Inicio De Prova SESSIONS ASSIST DASHBOARDS FD Saved Search 0 Clear Search SESSIONS BOOKMARKS NOTES A', + url: 'app.openreplay.com/3064/sessions', + selector: '#app', + path: '/3064/sessions', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 1022, + timestamp: 1673887370719, + label: 'Past 7 Days', + url: 'app.openreplay.com/3064/sessions', + selector: '#react-select-2-option-1', + path: '/3064/sessions', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 1677, + timestamp: 1673887371578, + label: 'Search sessions using any captured event (click, input, page, error...)', + url: 'app.openreplay.com/3064/sessions', + selector: '#search', + path: '/3064/sessions', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 1686, + timestamp: 1673887372209, + label: + 'Inicio De Prova Add Project Inicio De Prova Nova Proposta SESSIONS ASSIST DASHBOARDS FD INTERACTIONS', + url: 'app.openreplay.com/3064/sessions', + selector: '#app', + path: '/3064/sessions', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 1695, + timestamp: 1673887372872, + label: 'Nova Proposta', + url: 'app.openreplay.com/3064/sessions', + selector: + '#app > div.header-module__header--Jwjd7.fixed.w-full.bg-white.flex.justify-between > div.flex.items-center > div > div.siteDropdown-module__wrapper--HHW4o > div.siteDropdown-module__menu--v6SPy > ul > li', + path: '/3064/sessions', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 2038, + timestamp: 1673887372928, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/sessions', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/sessions', + url: '/5095/sessions', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 2617, + timestamp: 1673887373598, + label: 'Search sessions using any captured event (click, input, page, error...)', + url: 'app.openreplay.com/5095/sessions', + selector: '#search', + path: '/5095/sessions', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 5318, + timestamp: 1673887374158, + label: 'Search sessions using any captured event (click, input, page, error...)', + value: 'click', + type: 'INPUT', + }, + { + sessionId: 8119081922378909, + messageId: 5397, + timestamp: 1673887375702, + label: 'Click', + url: 'app.openreplay.com/5095/sessions', + selector: + '#app > div.page-margin.container-90.flex.relative > div.flex-1.flex > div.w-full.mx-auto > div.mb-5 > div.flex.items-center > div > div.relative > div.absolute.left-0.border.shadow.rounded.bg-white.z-50 > div.FilterModal-module__wrapper--E7xyB > div.mb-6 > div.mb-6.flex.flex-col.gap-2 > div > div.FilterModal-module__optionItem--AWh_e.flex.items-center.py-2.cursor-pointer.-mx-2.px-2', + path: '/5095/sessions', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 5689, + timestamp: 1673887375745, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/sessions', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: 'clk=on%7C', + value: '/5095/sessions', + url: '/5095/sessions', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 8353, + timestamp: 1673887376919, + label: 'Type to search', + url: 'app.openreplay.com/5095/sessions', + selector: + '#app > div.page-margin.container-90.flex.relative > div.flex-1.flex > div.w-full.mx-auto > div.mb-5 > div.border.bg-white.rounded.mt-4 > div.p-5 > div.flex.flex-col > div.flex.items-center.hover:bg-active-blue.-mx-5.px-5.py-2 > div.flex.items-start.w-full > div.grid.gap-3.grid-cols-3 > div > div.relative.flex.items-center > div.FilterAutoComplete-module__wrapper--lqRmv.relative > input.w-full.rounded.px-2.no-focus', + path: '/5095/sessions', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 8380, + timestamp: 1673887377375, + label: 'Type to search', + value: 'Buscar', + type: 'INPUT', + }, + { + sessionId: 8119081922378909, + messageId: 8423, + timestamp: 1673887380115, + label: 'BUSCAR CURSOS', + url: 'app.openreplay.com/5095/sessions', + selector: '#react-select-7-option-0', + path: '/5095/sessions', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 8474, + timestamp: 1673887380152, + label: 'Type to search', + value: 'BUSCAR CURSOS', + type: 'INPUT', + }, + { + sessionId: 8119081922378909, + messageId: 8475, + timestamp: 1673887380152, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/sessions', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: 'clk=on%7CBUSCAR+CURSOS', + value: '/5095/sessions', + url: '/5095/sessions', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 16759, + timestamp: 1673887409535, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/sessions', + selector: '#play-button > a.link-module__link--kvDmn', + path: '/5095/sessions', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 16798, + timestamp: 1673887409611, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8119041915010275', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8119041915010275', + url: '/5095/session/8119041915010275', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 18460, + timestamp: 1673887413132, + label: + 'Back Anonymous User 01:32pm · Brazil · Mobile Safari, IOS, IPhone · More ApplicationId 63226bf93b9b0', + url: 'app.openreplay.com/5095/session/8119041915010275', + selector: '#app', + path: '/5095/session/8119041915010275', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 18768, + timestamp: 1673887414749, + label: '00:20', + url: 'app.openreplay.com/5095/session/8119041915010275', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119041915010275', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 19797, + timestamp: 1673887420017, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8119041915010275', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119041915010275', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 19940, + timestamp: 1673887420737, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8119041915010275', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119041915010275', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 20008, + timestamp: 1673887421068, + label: '00:53', + url: 'app.openreplay.com/5095/session/8119041915010275', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119041915010275', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 20082, + timestamp: 1673887421433, + label: '00:53', + url: 'app.openreplay.com/5095/session/8119041915010275', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119041915010275', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 20220, + timestamp: 1673887422101, + label: 'overlay-module__overlay--cParP', + url: 'app.openreplay.com/5095/session/8119041915010275', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.relative.flex-1.overflow-hidden > div.overlay-module__overlay--cParP', + path: '/5095/session/8119041915010275', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 20262, + timestamp: 1673887435113, + label: 'overlay-module__overlay--cParP', + url: 'app.openreplay.com/5095/session/8119041915010275', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.relative.flex-1.overflow-hidden > div.overlay-module__overlay--cParP', + path: '/5095/session/8119041915010275', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 20376, + timestamp: 1673887435724, + label: '01:31', + url: 'app.openreplay.com/5095/session/8119041915010275', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119041915010275', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 20435, + timestamp: 1673887436057, + label: + 'Back Anonymous User 01:32pm · Brazil · Mobile Safari, IOS, IPhone · More ApplicationId 63226bf93b9b0', + url: 'app.openreplay.com/5095/session/8119041915010275', + selector: '#app', + path: '/5095/session/8119041915010275', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 20515, + timestamp: 1673887436481, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8119041915010275', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119041915010275', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 20658, + timestamp: 1673887437138, + label: '02:12', + url: 'app.openreplay.com/5095/session/8119041915010275', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119041915010275', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 21074, + timestamp: 1673887634761, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8119041915010275', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8119041915010275', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 21108, + timestamp: 1673887634918, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8119023158082639', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8119023158082639', + url: '/5095/session/8119023158082639', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 22953, + timestamp: 1673887637612, + label: '00:38', + url: 'app.openreplay.com/5095/session/8119023158082639', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119023158082639', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 23109, + timestamp: 1673887638175, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8119023158082639', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119023158082639', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 23208, + timestamp: 1673887638543, + label: '01:11', + url: 'app.openreplay.com/5095/session/8119023158082639', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119023158082639', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 23326, + timestamp: 1673887639223, + label: '01:11', + url: 'app.openreplay.com/5095/session/8119023158082639', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119023158082639', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 23420, + timestamp: 1673887639656, + label: '01:29', + url: 'app.openreplay.com/5095/session/8119023158082639', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119023158082639', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 23509, + timestamp: 1673887639993, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8119023158082639', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119023158082639', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 23781, + timestamp: 1673887641343, + label: 'overlay-module__overlay--cParP', + url: 'app.openreplay.com/5095/session/8119023158082639', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.relative.flex-1.overflow-hidden > div.overlay-module__overlay--cParP', + path: '/5095/session/8119023158082639', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 23807, + timestamp: 1673887641758, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8119023158082639', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119023158082639', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 23833, + timestamp: 1673887642229, + label: '04:10', + url: 'app.openreplay.com/5095/session/8119023158082639', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119023158082639', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 23864, + timestamp: 1673887642680, + label: '04:14', + url: 'app.openreplay.com/5095/session/8119023158082639', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119023158082639', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 23887, + timestamp: 1673887643031, + label: '04:20', + url: 'app.openreplay.com/5095/session/8119023158082639', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119023158082639', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 23921, + timestamp: 1673887643497, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8119023158082639', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8119023158082639', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 23963, + timestamp: 1673887645210, + label: 'overlay-module__overlay--cParP', + url: 'app.openreplay.com/5095/session/8119023158082639', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.relative.flex-1.overflow-hidden > div.overlay-module__overlay--cParP', + path: '/5095/session/8119023158082639', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 24120, + timestamp: 1673887648980, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8119023158082639', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8119023158082639', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 24147, + timestamp: 1673887649132, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118970199817356', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118970199817356', + url: '/5095/session/8118970199817356', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 25889, + timestamp: 1673887652022, + label: '00:11', + url: 'app.openreplay.com/5095/session/8118970199817356', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118970199817356', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 26015, + timestamp: 1673887652517, + label: '00:15', + url: 'app.openreplay.com/5095/session/8118970199817356', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118970199817356', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 26107, + timestamp: 1673887653010, + label: '00:15', + url: 'app.openreplay.com/5095/session/8118970199817356', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118970199817356', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 26256, + timestamp: 1673887653759, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118970199817356', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118970199817356', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 26332, + timestamp: 1673887654152, + label: '00:28', + url: 'app.openreplay.com/5095/session/8118970199817356', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118970199817356', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 26417, + timestamp: 1673887654584, + label: '00:40', + url: 'app.openreplay.com/5095/session/8118970199817356', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118970199817356', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 26542, + timestamp: 1673887655207, + label: 'overlay-module__overlay--cParP', + url: 'app.openreplay.com/5095/session/8118970199817356', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.relative.flex-1.overflow-hidden > div.overlay-module__overlay--cParP', + path: '/5095/session/8118970199817356', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 26557, + timestamp: 1673887655554, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118970199817356', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118970199817356', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 26622, + timestamp: 1673887655838, + label: '00:55', + url: 'app.openreplay.com/5095/session/8118970199817356', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118970199817356', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 26665, + timestamp: 1673887656302, + label: '00:59', + url: 'app.openreplay.com/5095/session/8118970199817356', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118970199817356', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 26714, + timestamp: 1673887657071, + label: 'overlay-module__overlay--cParP', + url: 'app.openreplay.com/5095/session/8118970199817356', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.relative.flex-1.overflow-hidden > div.overlay-module__overlay--cParP', + path: '/5095/session/8118970199817356', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 26888, + timestamp: 1673887658111, + label: 'overlay-module__overlay--cParP', + url: 'app.openreplay.com/5095/session/8118970199817356', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.relative.flex-1.overflow-hidden > div.overlay-module__overlay--cParP', + path: '/5095/session/8118970199817356', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 26913, + timestamp: 1673887680545, + label: 'overlay-module__overlay--cParP', + url: 'app.openreplay.com/5095/session/8118970199817356', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.relative.flex-1.overflow-hidden > div.overlay-module__overlay--cParP', + path: '/5095/session/8118970199817356', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 26949, + timestamp: 1673887681614, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8118970199817356', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8118970199817356', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 26979, + timestamp: 1673887681778, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118941288938501', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118941288938501', + url: '/5095/session/8118941288938501', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 28537, + timestamp: 1673887685261, + label: 'overlay-module__overlay--cParP', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.relative.flex-1.overflow-hidden > div.overlay-module__overlay--cParP', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 28782, + timestamp: 1673887686146, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 28867, + timestamp: 1673887686590, + label: '00:28', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 28935, + timestamp: 1673887686943, + label: '00:47', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 29419, + timestamp: 1673887689401, + label: '00:31', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 29526, + timestamp: 1673887689742, + label: '00:32', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 29603, + timestamp: 1673887690224, + label: '00:32', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 29762, + timestamp: 1673887690874, + label: '00:45', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 29946, + timestamp: 1673887691747, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 30048, + timestamp: 1673887692095, + label: + 'Back Anonymous User 01:06pm · Brazil · Chrome, Windows, Desktop · More ApplicationId 63c5761909dd6f4', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: '#app', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 30110, + timestamp: 1673887692445, + label: + 'Back Anonymous User 01:06pm · Brazil · Chrome, Windows, Desktop · More ApplicationId 63c5761909dd6f4', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: '#app', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 30220, + timestamp: 1673887692927, + label: + 'Back Anonymous User 01:06pm · Brazil · Chrome, Windows, Desktop · More ApplicationId 63c5761909dd6f4', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: '#app', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 30309, + timestamp: 1673887693439, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 30414, + timestamp: 1673887693927, + label: '02:12', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 30631, + timestamp: 1673887695086, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 30769, + timestamp: 1673887695584, + label: '03:07', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 30896, + timestamp: 1673887696073, + label: '03:41', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 31084, + timestamp: 1673887697099, + label: '03:51', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 31158, + timestamp: 1673887697489, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 31479, + timestamp: 1673887699033, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8118941288938501', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8118941288938501', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 31510, + timestamp: 1673887699178, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118857356000307', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118857356000307', + url: '/5095/session/8118857356000307', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 33824, + timestamp: 1673887702568, + label: '00:32', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 33917, + timestamp: 1673887703018, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 34025, + timestamp: 1673887703537, + label: '01:27', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 34168, + timestamp: 1673887704293, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 34253, + timestamp: 1673887704710, + label: '01:42', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 34338, + timestamp: 1673887705146, + label: '02:09', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 34493, + timestamp: 1673887705938, + label: + 'Back Anonymous User 12:45pm · Brazil · Chrome, Windows, Desktop · More ApplicationId 63c571183040b80', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: '#app', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 34554, + timestamp: 1673887706304, + label: + 'Back Anonymous User 12:45pm · Brazil · Chrome, Windows, Desktop · More ApplicationId 63c571183040b80', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: '#app', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 34616, + timestamp: 1673887706638, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 34740, + timestamp: 1673887707199, + label: '03:14', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 34895, + timestamp: 1673887707746, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 34955, + timestamp: 1673887708051, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 35038, + timestamp: 1673887708495, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 35203, + timestamp: 1673887709457, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 35311, + timestamp: 1673887710019, + label: '06:57', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 35393, + timestamp: 1673887710378, + label: '07:21', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 35493, + timestamp: 1673887710931, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 35615, + timestamp: 1673887711495, + label: '07:27', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 35801, + timestamp: 1673887713149, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8118857356000307', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8118857356000307', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 35832, + timestamp: 1673887713338, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118843021704432', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118843021704432', + url: '/5095/session/8118843021704432', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 37522, + timestamp: 1673887715900, + label: 'hover-main color-main cursor-pointer rounded hover:bg-gray-light-shade', + url: 'app.openreplay.com/5095/session/8118843021704432', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.controls-module__buttons--vje3y > div.flex.items-center > div.flex.items-center > div.relative > div > div.hover-main.color-main.cursor-pointer.rounded.hover:bg-gray-light-shade', + path: '/5095/session/8118843021704432', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 37830, + timestamp: 1673887905304, + label: 'overlay-module__overlay--cParP', + url: 'app.openreplay.com/5095/session/8118843021704432', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.relative.flex-1.overflow-hidden > div.overlay-module__overlay--cParP', + path: '/5095/session/8118843021704432', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 38007, + timestamp: 1673887906287, + label: '00:25', + url: 'app.openreplay.com/5095/session/8118843021704432', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118843021704432', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 38094, + timestamp: 1673887906714, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118843021704432', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118843021704432', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 38187, + timestamp: 1673887907134, + label: '00:43', + url: 'app.openreplay.com/5095/session/8118843021704432', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118843021704432', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 38277, + timestamp: 1673887907588, + label: '00:51', + url: 'app.openreplay.com/5095/session/8118843021704432', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118843021704432', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 38381, + timestamp: 1673887908132, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118843021704432', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118843021704432', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 38473, + timestamp: 1673887908561, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118843021704432', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118843021704432', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 39032, + timestamp: 1673887911408, + label: '01:15', + url: 'app.openreplay.com/5095/session/8118843021704432', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118843021704432', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 39338, + timestamp: 1673887913006, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8118843021704432', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8118843021704432', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 39368, + timestamp: 1673887913137, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118806248420922', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118806248420922', + url: '/5095/session/8118806248420922', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 41222, + timestamp: 1673887916756, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118806248420922', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118806248420922', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 41320, + timestamp: 1673887917191, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118806248420922', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118806248420922', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 41441, + timestamp: 1673887917772, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118806248420922', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118806248420922', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 41554, + timestamp: 1673887918231, + label: '00:47', + url: 'app.openreplay.com/5095/session/8118806248420922', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118806248420922', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 41620, + timestamp: 1673887918588, + label: '00:47', + url: 'app.openreplay.com/5095/session/8118806248420922', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118806248420922', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 41791, + timestamp: 1673887919411, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118806248420922', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118806248420922', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 41878, + timestamp: 1673887919752, + label: '00:59', + url: 'app.openreplay.com/5095/session/8118806248420922', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118806248420922', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 41978, + timestamp: 1673887920193, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118806248420922', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118806248420922', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 42085, + timestamp: 1673887920669, + label: '01:07', + url: 'app.openreplay.com/5095/session/8118806248420922', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118806248420922', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 42201, + timestamp: 1673887921245, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118806248420922', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118806248420922', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 42431, + timestamp: 1673887922478, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118806248420922', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118806248420922', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 42704, + timestamp: 1673887923933, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8118806248420922', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8118806248420922', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 42734, + timestamp: 1673887924087, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118804409742859', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118804409742859', + url: '/5095/session/8118804409742859', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 44431, + timestamp: 1673887926799, + label: '00:17', + url: 'app.openreplay.com/5095/session/8118804409742859', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118804409742859', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 47569, + timestamp: 1673887945617, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118804409742859', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118804409742859', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 47705, + timestamp: 1673887946283, + label: '01:54', + url: 'app.openreplay.com/5095/session/8118804409742859', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118804409742859', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 47848, + timestamp: 1673887947314, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8118804409742859', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8118804409742859', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 47874, + timestamp: 1673887947454, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118789878445746', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118789878445746', + url: '/5095/session/8118789878445746', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 50106, + timestamp: 1673887950396, + label: '00:18', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 50184, + timestamp: 1673887950819, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 50278, + timestamp: 1673887951274, + label: '00:54', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 50360, + timestamp: 1673887951688, + label: '01:06', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 50435, + timestamp: 1673887952133, + label: '01:06', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 50517, + timestamp: 1673887952549, + label: '01:16', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 50613, + timestamp: 1673887953069, + label: '01:16', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 50684, + timestamp: 1673887953377, + label: '01:55', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 50756, + timestamp: 1673887953787, + label: '01:55', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 50833, + timestamp: 1673887954172, + label: '02:03', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 51011, + timestamp: 1673887955089, + label: '02:34', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 51107, + timestamp: 1673887955587, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 51188, + timestamp: 1673887956021, + label: '02:43', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 51319, + timestamp: 1673887956731, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 51401, + timestamp: 1673887957167, + label: '03:13', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 51596, + timestamp: 1673887958210, + label: '04:37', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 51664, + timestamp: 1673887958554, + label: '04:37', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 51733, + timestamp: 1673887958896, + label: '05:07', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 51798, + timestamp: 1673887959186, + label: '05:50', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 51873, + timestamp: 1673887959536, + label: '06:05', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 51930, + timestamp: 1673887959848, + label: '06:05', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 52004, + timestamp: 1673887960213, + label: '06:20', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 52079, + timestamp: 1673887960608, + label: '06:36', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 52150, + timestamp: 1673887960968, + label: '06:59', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 52240, + timestamp: 1673887961454, + label: '07:24', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 52303, + timestamp: 1673887961771, + label: '07:44', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 52367, + timestamp: 1673887962085, + label: '07:57', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 52446, + timestamp: 1673887962477, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 52821, + timestamp: 1673887964594, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8118789878445746', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8118789878445746', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 52851, + timestamp: 1673887964798, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118778337868907', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118778337868907', + url: '/5095/session/8118778337868907', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 54862, + timestamp: 1673887968522, + label: '00:34', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 54928, + timestamp: 1673887968856, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 55114, + timestamp: 1673887969890, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 55282, + timestamp: 1673887970613, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 55434, + timestamp: 1673887971164, + label: '00:37', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 55586, + timestamp: 1673887971775, + label: '00:30', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 55791, + timestamp: 1673887972755, + label: '00:30', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 55881, + timestamp: 1673887973220, + label: '00:43', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 55958, + timestamp: 1673887973618, + label: '00:48', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 56060, + timestamp: 1673887974053, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 56172, + timestamp: 1673887974571, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 56252, + timestamp: 1673887974970, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 56515, + timestamp: 1673887976449, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 56847, + timestamp: 1673887978187, + label: '05:32', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 56957, + timestamp: 1673887978765, + label: '05:29', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 57041, + timestamp: 1673887979238, + label: '05:29', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 57233, + timestamp: 1673887980236, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8118778337868907', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8118778337868907', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 57260, + timestamp: 1673887980376, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118750395677903', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118750395677903', + url: '/5095/session/8118750395677903', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 59858, + timestamp: 1673887984084, + label: '00:28', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 59920, + timestamp: 1673887984438, + label: '00:28', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 60099, + timestamp: 1673887985427, + label: '02:21', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 60189, + timestamp: 1673887985871, + label: '02:09', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 60265, + timestamp: 1673887986324, + label: '02:09', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 60513, + timestamp: 1673887987651, + label: '05:13', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 60596, + timestamp: 1673887988155, + label: '05:13', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 60659, + timestamp: 1673887988464, + label: '05:31', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 61027, + timestamp: 1673887990588, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 61098, + timestamp: 1673887990935, + label: '07:27', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 61166, + timestamp: 1673887991315, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 61256, + timestamp: 1673887991725, + label: '09:01', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 61405, + timestamp: 1673887992509, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 61463, + timestamp: 1673887992797, + label: '10:28', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 61570, + timestamp: 1673887993361, + label: '11:12', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 61638, + timestamp: 1673887993722, + label: '11:27', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 61704, + timestamp: 1673887994084, + label: '12:10', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 61771, + timestamp: 1673887994425, + label: '12:37', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 61844, + timestamp: 1673887994784, + label: '13:12', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 61915, + timestamp: 1673887995125, + label: '13:39', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 61982, + timestamp: 1673887995469, + label: '14:17', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 62037, + timestamp: 1673887995751, + label: '14:32', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 62198, + timestamp: 1673887996610, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8118750395677903', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8118750395677903', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 62224, + timestamp: 1673887996786, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118746485800060', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118746485800060', + url: '/5095/session/8118746485800060', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 64030, + timestamp: 1673887999813, + label: '00:19', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 64108, + timestamp: 1673888000198, + label: '00:15', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 64201, + timestamp: 1673888000679, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 64289, + timestamp: 1673888001151, + label: '00:30', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 64375, + timestamp: 1673888001504, + label: '00:43', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 64458, + timestamp: 1673888001945, + label: '00:48', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 64541, + timestamp: 1673888002401, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 64610, + timestamp: 1673888002736, + label: '00:55', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 64748, + timestamp: 1673888003422, + label: + 'Back Anonymous User 12:17pm · Brazil · Chrome Mobile, Android, Motorola G(60) · More ApplicationId 6', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: '#app', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 64808, + timestamp: 1673888003778, + label: + 'Back Anonymous User 12:17pm · Brazil · Chrome Mobile, Android, Motorola G(60) · More ApplicationId 6', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: '#app', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 64866, + timestamp: 1673888004132, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 64933, + timestamp: 1673888004449, + label: '01:27', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 65008, + timestamp: 1673888004764, + label: '01:40', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 65080, + timestamp: 1673888005110, + label: + 'Back Anonymous User 12:17pm · Brazil · Chrome Mobile, Android, Motorola G(60) · More ApplicationId 6', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: '#app', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 65142, + timestamp: 1673888005494, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 65215, + timestamp: 1673888005843, + label: '01:54', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 65280, + timestamp: 1673888006147, + label: '01:54', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 65439, + timestamp: 1673888006845, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 65501, + timestamp: 1673888007190, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 65565, + timestamp: 1673888007502, + label: '02:37', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 65640, + timestamp: 1673888007877, + label: '02:55', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 65726, + timestamp: 1673888008284, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 65791, + timestamp: 1673888008586, + label: '03:13', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 65969, + timestamp: 1673888009549, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 66128, + timestamp: 1673888010395, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8118746485800060', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8118746485800060', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 66159, + timestamp: 1673888010549, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118633985734291', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118633985734291', + url: '/5095/session/8118633985734291', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 67718, + timestamp: 1673888012602, + label: 'hover-main color-main cursor-pointer rounded hover:bg-gray-light-shade', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.controls-module__buttons--vje3y > div.flex.items-center > div.flex.items-center > div.relative > div > div.hover-main.color-main.cursor-pointer.rounded.hover:bg-gray-light-shade', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 67905, + timestamp: 1673888019368, + label: 'overlay-module__overlay--cParP', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.relative.flex-1.overflow-hidden > div.overlay-module__overlay--cParP', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 68094, + timestamp: 1673888020343, + label: '00:16', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 68173, + timestamp: 1673888020722, + label: '00:11', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 68277, + timestamp: 1673888021266, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 68364, + timestamp: 1673888021665, + label: '00:38', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 68614, + timestamp: 1673888022969, + label: '00:47', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 68697, + timestamp: 1673888023350, + label: '00:57', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 68840, + timestamp: 1673888024075, + label: '01:03', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 68981, + timestamp: 1673888024760, + label: '01:10', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 69129, + timestamp: 1673888025520, + label: '01:24', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 69209, + timestamp: 1673888025955, + label: '01:24', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 69528, + timestamp: 1673888027711, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 69587, + timestamp: 1673888027983, + label: '01:58', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 69654, + timestamp: 1673888028334, + label: '01:58', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 69714, + timestamp: 1673888028588, + label: '02:34', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 69876, + timestamp: 1673888029475, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 69953, + timestamp: 1673888029831, + label: '04:14', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 70037, + timestamp: 1673888030253, + label: '04:14', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 70110, + timestamp: 1673888030615, + label: '04:28', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 70185, + timestamp: 1673888031016, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 70234, + timestamp: 1673888031229, + label: '05:00', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 70313, + timestamp: 1673888031575, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 70482, + timestamp: 1673888032496, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8118633985734291', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8118633985734291', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 70513, + timestamp: 1673888032659, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118630504330810', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118630504330810', + url: '/5095/session/8118630504330810', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 72283, + timestamp: 1673888056856, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118630504330810', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118630504330810', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 72362, + timestamp: 1673888057237, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118630504330810', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118630504330810', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 72455, + timestamp: 1673888057670, + label: 'overlay-module__overlay--cParP', + url: 'app.openreplay.com/5095/session/8118630504330810', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.relative.flex-1.overflow-hidden > div.overlay-module__overlay--cParP', + path: '/5095/session/8118630504330810', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 72468, + timestamp: 1673888058107, + label: 'overlay-module__overlay--cParP', + url: 'app.openreplay.com/5095/session/8118630504330810', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.relative.flex-1.overflow-hidden > div.overlay-module__overlay--cParP', + path: '/5095/session/8118630504330810', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 72526, + timestamp: 1673888058447, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118630504330810', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118630504330810', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 72649, + timestamp: 1673888058915, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118630504330810', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118630504330810', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 72720, + timestamp: 1673888059247, + label: '00:47', + url: 'app.openreplay.com/5095/session/8118630504330810', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118630504330810', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 72795, + timestamp: 1673888059656, + label: '00:47', + url: 'app.openreplay.com/5095/session/8118630504330810', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118630504330810', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 72991, + timestamp: 1673888060632, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8118630504330810', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8118630504330810', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 73021, + timestamp: 1673888060767, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118613089434832', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118613089434832', + url: '/5095/session/8118613089434832', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 75400, + timestamp: 1673888084323, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118613089434832', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118613089434832', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 75495, + timestamp: 1673888084848, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118613089434832', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118613089434832', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 75577, + timestamp: 1673888085273, + label: '01:10', + url: 'app.openreplay.com/5095/session/8118613089434832', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118613089434832', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 75663, + timestamp: 1673888085731, + label: '01:46', + url: 'app.openreplay.com/5095/session/8118613089434832', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118613089434832', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 75725, + timestamp: 1673888086047, + label: '02:13', + url: 'app.openreplay.com/5095/session/8118613089434832', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118613089434832', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 75794, + timestamp: 1673888086393, + label: '02:19', + url: 'app.openreplay.com/5095/session/8118613089434832', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118613089434832', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 75892, + timestamp: 1673888086830, + label: '03:14', + url: 'app.openreplay.com/5095/session/8118613089434832', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118613089434832', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 75991, + timestamp: 1673888087362, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118613089434832', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118613089434832', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 76059, + timestamp: 1673888087695, + label: '03:32', + url: 'app.openreplay.com/5095/session/8118613089434832', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118613089434832', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 76119, + timestamp: 1673888087973, + label: '03:49', + url: 'app.openreplay.com/5095/session/8118613089434832', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118613089434832', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 76280, + timestamp: 1673888088861, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118613089434832', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118613089434832', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 76432, + timestamp: 1673888089681, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118613089434832', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118613089434832', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 76507, + timestamp: 1673888090116, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118613089434832', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118613089434832', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 76684, + timestamp: 1673888091087, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8118613089434832', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8118613089434832', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 76714, + timestamp: 1673888091277, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118592777665791', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118592777665791', + url: '/5095/session/8118592777665791', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 78810, + timestamp: 1673888097086, + label: '00:19', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 78866, + timestamp: 1673888097360, + label: '00:19', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 78938, + timestamp: 1673888097708, + label: '00:19', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 79017, + timestamp: 1673888098012, + label: '00:53', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 79170, + timestamp: 1673888098820, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 79234, + timestamp: 1673888099155, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 79306, + timestamp: 1673888099490, + label: '01:22', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 79467, + timestamp: 1673888100272, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 79525, + timestamp: 1673888100592, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 79608, + timestamp: 1673888100986, + label: '02:03', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 79771, + timestamp: 1673888101842, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 79849, + timestamp: 1673888102224, + label: '02:23', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 79909, + timestamp: 1673888102509, + label: '02:35', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 79990, + timestamp: 1673888102941, + label: '02:35', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 80067, + timestamp: 1673888103261, + label: '03:03', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 80128, + timestamp: 1673888103620, + label: '03:03', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 80195, + timestamp: 1673888103954, + label: '03:12', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 80257, + timestamp: 1673888104243, + label: '03:21', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 80329, + timestamp: 1673888104487, + label: '03:47', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 80398, + timestamp: 1673888104841, + label: '03:47', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 80463, + timestamp: 1673888105146, + label: '03:54', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 80550, + timestamp: 1673888105544, + label: '04:08', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 80834, + timestamp: 1673888107051, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8118592777665791', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8118592777665791', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 80865, + timestamp: 1673888107239, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118562697246396', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118562697246396', + url: '/5095/session/8118562697246396', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 82627, + timestamp: 1673888109892, + label: '00:11', + url: 'app.openreplay.com/5095/session/8118562697246396', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118562697246396', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 82713, + timestamp: 1673888110281, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118562697246396', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118562697246396', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 82816, + timestamp: 1673888110699, + label: '00:22', + url: 'app.openreplay.com/5095/session/8118562697246396', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118562697246396', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 82951, + timestamp: 1673888111100, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118562697246396', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118562697246396', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 83069, + timestamp: 1673888111621, + label: '00:39', + url: 'app.openreplay.com/5095/session/8118562697246396', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118562697246396', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 83195, + timestamp: 1673888112264, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118562697246396', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118562697246396', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 83285, + timestamp: 1673888112710, + label: '00:47', + url: 'app.openreplay.com/5095/session/8118562697246396', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118562697246396', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 83353, + timestamp: 1673888113044, + label: '00:47', + url: 'app.openreplay.com/5095/session/8118562697246396', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118562697246396', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 83523, + timestamp: 1673888113910, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118562697246396', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118562697246396', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 83608, + timestamp: 1673888114270, + label: + 'Back Anonymous User 11:30am · Brazil · Mobile Safari, IOS, IPhone · More ApplicationId 63c541342aba9', + url: 'app.openreplay.com/5095/session/8118562697246396', + selector: '#app', + path: '/5095/session/8118562697246396', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 83699, + timestamp: 1673888114832, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118562697246396', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118562697246396', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 83751, + timestamp: 1673888115021, + label: + 'Back Anonymous User 11:30am · Brazil · Mobile Safari, IOS, IPhone · More ApplicationId 63c541342aba9', + url: 'app.openreplay.com/5095/session/8118562697246396', + selector: '#app', + path: '/5095/session/8118562697246396', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 83956, + timestamp: 1673888116167, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118562697246396', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118562697246396', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 84209, + timestamp: 1673888117518, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8118562697246396', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8118562697246396', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 84235, + timestamp: 1673888117650, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118559548241523', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118559548241523', + url: '/5095/session/8118559548241523', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 86588, + timestamp: 1673888120950, + label: '00:18', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 86651, + timestamp: 1673888121298, + label: '00:18', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 86723, + timestamp: 1673888121662, + label: '00:26', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 86883, + timestamp: 1673888122561, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 86979, + timestamp: 1673888123037, + label: '03:46', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 87098, + timestamp: 1673888123627, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 87288, + timestamp: 1673888124715, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 87343, + timestamp: 1673888125053, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 87421, + timestamp: 1673888125428, + label: '06:42', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 87562, + timestamp: 1673888126005, + label: '06:42', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 87715, + timestamp: 1673888126781, + label: '07:32', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 87790, + timestamp: 1673888127207, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 87856, + timestamp: 1673888127584, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 87924, + timestamp: 1673888127937, + label: '08:06', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 88059, + timestamp: 1673888128646, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 88140, + timestamp: 1673888129115, + label: '09:40', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 88238, + timestamp: 1673888129614, + label: '10:58', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 88363, + timestamp: 1673888130346, + label: 'link-module__link--kvDmn', + url: 'app.openreplay.com/5095/session/8118559548241523', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.w-full.px-4.py-2.flex.items-center.border-b > div.ml-auto.text-sm.flex.items-center.color-gray-medium.gap-2 > div > div.flex.items-center > div.relative > div > a.link-module__link--kvDmn', + path: '/5095/session/8118559548241523', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 88393, + timestamp: 1673888130578, + host: 'app.openreplay.com', + referrer: '', + domContentLoadedTime: null, + firstPaintTime: null, + firstContentfulPaintTime: null, + loadTime: null, + speedIndex: null, + visuallyComplete: null, + timeToInteractive: null, + domBuildingTime: null, + path: '/5095/session/8118556979885624', + baseReferrer: '', + responseTime: null, + responseEnd: null, + ttfb: null, + query: '', + value: '/5095/session/8118556979885624', + url: '/5095/session/8118556979885624', + type: 'LOCATION', + }, + { + sessionId: 8119081922378909, + messageId: 90402, + timestamp: 1673888133249, + label: 'hover-main color-main cursor-pointer rounded hover:bg-gray-light-shade', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.controls-module__buttons--vje3y > div.flex.items-center > div.flex.items-center > div.relative > div > div.hover-main.color-main.cursor-pointer.rounded.hover:bg-gray-light-shade', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 90437, + timestamp: 1673888136912, + label: 'overlay-module__overlay--cParP', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.relative.flex-1.overflow-hidden > div.overlay-module__overlay--cParP', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 90590, + timestamp: 1673888137720, + label: '00:15', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 90658, + timestamp: 1673888138083, + label: '00:15', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 90739, + timestamp: 1673888138465, + label: '00:22', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 91026, + timestamp: 1673888139974, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 91100, + timestamp: 1673888140318, + label: '00:45', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 91194, + timestamp: 1673888140694, + label: '00:58', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 91281, + timestamp: 1673888141121, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 91424, + timestamp: 1673888141937, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 91478, + timestamp: 1673888142206, + label: '01:11', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 91635, + timestamp: 1673888143053, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 91702, + timestamp: 1673888143355, + label: '02:55', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 91786, + timestamp: 1673888143762, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 91851, + timestamp: 1673888144071, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 91994, + timestamp: 1673888144817, + label: '03:14', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 92082, + timestamp: 1673888145213, + label: '03:22', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 92168, + timestamp: 1673888145642, + label: '03:34', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 92251, + timestamp: 1673888146059, + label: '03:45', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 92339, + timestamp: 1673888146394, + label: '04:00', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 92416, + timestamp: 1673888146720, + label: '04:09', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 92488, + timestamp: 1673888147096, + label: '04:14', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 92559, + timestamp: 1673888147441, + label: '04:21', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + { + sessionId: 8119081922378909, + messageId: 92741, + timestamp: 1673888148063, + label: 'timeline-module__progress--zmG3d', + url: 'app.openreplay.com/5095/session/8118556979885624', + selector: + '#app > div.relative > div.flex > div.w-full > div.session-module__session--PKpp5.relative > div.playerBlock-module__playerBlock--c8_Ul.flex.flex-col.overflow-x-hidden > div.flex-1.player-module__playerBody--aoTX_.flex.flex-col.relative > div.controls-module__controls--fXp80 > div.flex.items-center.absolute.w-full > div.timeline-module__progress--zmG3d', + path: '/5095/session/8118556979885624', + type: 'CLICK', + }, + ], + stackEvents: [], + errors: [], + userEvents: [], + domURL: [ + 'https://asayer-mobs.s3.amazonaws.com/8119081922378909/dom.mobs?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6RAO3SOP6GE5ISKO%2F20230117%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20230117T143651Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=d0b022119cae8f66d4a65bf1805424e3fc6c37fab2a3836c45ee40ed522d1f5d', + 'https://asayer-mobs.s3.amazonaws.com/8119081922378909/dom.mobe?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6RAO3SOP6GE5ISKO%2F20230117%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20230117T143651Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=f8b8d4759b7b5daaa57ace809321e136e801fb19c6dcc77b629b01f87060af68', + ], + mobsUrl: [ + 'https://asayer-mobs.s3.amazonaws.com/8119081922378909?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6RAO3SOP6GE5ISKO%2F20230117%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20230117T143651Z&X-Amz-Expires=100000&X-Amz-SignedHeaders=host&X-Amz-Signature=72c446fb41871ebcc17076e6b4b6b5eeed8405205e0af063716346de0c1e5e2c', + 'https://asayer-mobs.s3.amazonaws.com/8119081922378909e?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6RAO3SOP6GE5ISKO%2F20230117%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20230117T143651Z&X-Amz-Expires=100000&X-Amz-SignedHeaders=host&X-Amz-Signature=4058af85175bc590ee868f751d9e161f0ac11dcee99f309c9faecccb5a331f8c', + ], + devtoolsURL: [ + 'https://asayer-mobs.s3.amazonaws.com/8119081922378909/devtools.mob?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6RAO3SOP6GE5ISKO%2F20230117%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20230117T143651Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=520bc5371ba14ac143965d15e20de95d75cfe87a9361bc1a690950b9dceb0aaf', + ], + resources: [ + { + datetime: 1673887354000, + url: 'https://fonts.googleapis.com/css2', + type: 'other', + duration: 6, + ttfb: 6, + headerSize: null, + encodedBodySize: 717, + decodedBodySize: 8820, + success: 1, + status: 200, + }, + { + datetime: 1673887354000, + url: 'https://app.openreplay.com/app-a69ab38.js', + type: 'script', + duration: 123, + ttfb: 23, + headerSize: 300, + encodedBodySize: 1217842, + decodedBodySize: 4610798, + success: 1, + status: 200, + }, + { + datetime: 1673887354000, + url: 'https://app.openreplay.com/app-48509b8.js', + type: 'script', + duration: 123, + ttfb: 108, + headerSize: 300, + encodedBodySize: 263378, + decodedBodySize: 1097309, + success: 1, + status: 200, + }, + { + datetime: 1673887354000, + url: 'https://d2yyd1h5u9mauk.cloudfront.net/integrations/web/v1/library/MehpSkgi5fumG1v1/delighted.js', + type: 'script', + duration: 306, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887354000, + url: 'https://client.crisp.chat/l.js', + type: 'script', + duration: 96, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887354000, + url: 'https://app.openreplay.com/main.css', + type: 'stylesheet', + duration: 6, + ttfb: 4, + headerSize: null, + encodedBodySize: 28428, + decodedBodySize: 168932, + success: 1, + status: 200, + }, + { + datetime: 1673887354000, + url: 'https://api.openreplay.com/account', + type: 'fetch', + duration: 948, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887354000, + url: 'https://app.openreplay.com/assets/favicon-32x32.png', + type: 'img', + duration: 2, + ttfb: 1, + headerSize: null, + encodedBodySize: 1090, + decodedBodySize: 1090, + success: 1, + status: 200, + }, + { + datetime: 1673887354000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 2, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887355000, + url: 'https://client.crisp.chat/settings/website/adc74d6f-70c5-4947-bdf1-c359f3becfaf/prelude/', + type: 'other', + duration: 273, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887355000, + url: 'https://client.crisp.chat/static/javascripts/client.js', + type: 'script', + duration: 88, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887355000, + url: 'https://client.crisp.chat/static/stylesheets/client_default.css', + type: 'stylesheet', + duration: 3, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887355000, + url: 'https://api.openreplay.com/projects', + type: 'fetch', + duration: 1731, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887357000, + url: 'https://client.crisp.chat/settings/website/adc74d6f-70c5-4947-bdf1-c359f3becfaf/', + type: 'other', + duration: 247, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887357000, + url: 'https://client.crisp.chat/static/javascripts/locales/pt-br.js', + type: 'script', + duration: 2, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887357000, + url: 'https://app.openreplay.com/524.app-f7855df.js', + type: 'script', + duration: 2, + ttfb: 2, + headerSize: null, + encodedBodySize: 9215, + decodedBodySize: 34130, + success: 1, + status: 200, + }, + { + datetime: 1673887357000, + url: 'https://app.openreplay.com/524.css', + type: 'stylesheet', + duration: 2, + ttfb: 2, + headerSize: null, + encodedBodySize: 945, + decodedBodySize: 945, + success: 1, + status: 200, + }, + { + datetime: 1673887357000, + url: 'https://api.openreplay.com/config/assist/credentials', + type: 'fetch', + duration: 452, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887357000, + url: 'https://api.openreplay.com/boarding', + type: 'fetch', + duration: 509, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887357000, + url: 'https://api.openreplay.com/limits', + type: 'fetch', + duration: 490, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887357000, + url: 'https://api.openreplay.com/notifications/count', + type: 'fetch', + duration: 489, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887357000, + url: 'https://api.openreplay.com/3064/metadata', + type: 'fetch', + duration: 489, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887357000, + url: 'https://api.openreplay.com/3064/saved_search', + type: 'fetch', + duration: 634, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887357000, + url: 'https://api.openreplay.com/3064/sessions/search', + type: 'fetch', + duration: 639, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887357000, + url: 'https://api.openreplay.com/3064/metadata', + type: 'fetch', + duration: 696, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887357000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 631, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887357000, + url: 'https://app.openreplay.com/a52087055f7c449b58e37ce2983a1c2b.svg', + type: 'img', + duration: 2, + ttfb: 2, + headerSize: null, + encodedBodySize: 663, + decodedBodySize: 1390, + success: 1, + status: 200, + }, + { + datetime: 1673887357000, + url: 'https://app.openreplay.com/72e998d1a54e62418bf4fd23d08fb439.svg', + type: 'img', + duration: 1, + ttfb: 1, + headerSize: null, + encodedBodySize: 895, + decodedBodySize: 2194, + success: 1, + status: 200, + }, + { + datetime: 1673887370000, + url: 'https://api.openreplay.com/3064/sessions/search', + type: 'fetch', + duration: 298, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887370000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 14, + ttfb: 14, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887372000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 232, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887372000, + url: 'https://api.openreplay.com/5095/metadata', + type: 'fetch', + duration: 488, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887372000, + url: 'https://api.openreplay.com/5095/saved_search', + type: 'fetch', + duration: 465, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887372000, + url: 'https://api.openreplay.com/5095/assist/sessions', + type: 'fetch', + duration: 590, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887372000, + url: 'https://api.openreplay.com/5095/metadata', + type: 'fetch', + duration: 683, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887372000, + url: 'https://api.openreplay.com/5095/sessions/search', + type: 'fetch', + duration: 941, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887372000, + url: 'https://api.openreplay.com/5095/sessions/search', + type: 'fetch', + duration: 910, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887373000, + url: 'https://app.openreplay.com/cde4fdba423055516fe5.png', + type: 'img', + duration: 2, + ttfb: 1, + headerSize: null, + encodedBodySize: 55435, + decodedBodySize: 55435, + success: 1, + status: 200, + }, + { + datetime: 1673887375000, + url: 'https://api.openreplay.com/5095/sessions/search', + type: 'fetch', + duration: 275, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887375000, + url: 'https://api.openreplay.com/5095/events/search', + type: 'fetch', + duration: 937, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887378000, + url: 'https://api.openreplay.com/5095/events/search', + type: 'fetch', + duration: 793, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887379000, + url: 'https://api.openreplay.com/5095/sessions/search', + type: 'fetch', + duration: 416, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887379000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 13, + ttfb: 12, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887381000, + url: 'https://api.openreplay.com/5095/sessions/search', + type: 'fetch', + duration: 265, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887407000, + url: 'https://api.openreplay.com/5095/sessions/search', + type: 'fetch', + duration: 269, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887407000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 15, + ttfb: 15, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887409000, + url: 'https://app.openreplay.com/964.app-d791f13.js', + type: 'script', + duration: 27, + ttfb: 25, + headerSize: null, + encodedBodySize: 14492, + decodedBodySize: 52143, + success: 1, + status: 200, + }, + { + datetime: 1673887409000, + url: 'https://app.openreplay.com/577.app-d38b8c9.js', + type: 'script', + duration: 27, + ttfb: 24, + headerSize: null, + encodedBodySize: 38635, + decodedBodySize: 163450, + success: 1, + status: 200, + }, + { + datetime: 1673887409000, + url: 'https://app.openreplay.com/723.app-ae9daea.js', + type: 'script', + duration: 28, + ttfb: 26, + headerSize: null, + encodedBodySize: 46917, + decodedBodySize: 216435, + success: 1, + status: 200, + }, + { + datetime: 1673887409000, + url: 'https://app.openreplay.com/308.app-3fbd091.js', + type: 'script', + duration: 25, + ttfb: 23, + headerSize: null, + encodedBodySize: 9465, + decodedBodySize: 33695, + success: 1, + status: 200, + }, + { + datetime: 1673887409000, + url: 'https://app.openreplay.com/723.css', + type: 'stylesheet', + duration: 26, + ttfb: 25, + headerSize: null, + encodedBodySize: 5572, + decodedBodySize: 27902, + success: 1, + status: 200, + }, + { + datetime: 1673887409000, + url: 'https://app.openreplay.com/308.css', + type: 'stylesheet', + duration: 25, + ttfb: 24, + headerSize: null, + encodedBodySize: 1797, + decodedBodySize: 8546, + success: 1, + status: 200, + }, + { + datetime: 1673887409000, + url: 'https://api.openreplay.com/5095/sessions/8119041915010275', + type: 'fetch', + duration: 679, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887409000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 4, + ttfb: 4, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887410000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 230, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887410000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 465, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887410000, + url: 'https://api.openreplay.com/integrations/slack/channels', + type: 'fetch', + duration: 456, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887410000, + url: 'https://api.openreplay.com/5095/sessions/8119041915010275/notes', + type: 'fetch', + duration: 470, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887410000, + url: 'https://asayer-mobs.s3.amazonaws.com/8119041915010275/devtools.mob', + type: 'fetch', + duration: 750, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887410000, + url: 'https://asayer-mobs.s3.amazonaws.com/8119041915010275/dom.mobs', + type: 'fetch', + duration: 1194, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887411000, + url: 'https://asayer-mobs.s3.amazonaws.com/8119041915010275/dom.mobe', + type: 'fetch', + duration: 229, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887419000, + url: 'https://cadastro.pravaler.com.br/assets/images/NotFoundSimulation.svg', + type: 'img', + duration: 39, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887420000, + url: 'https://cadastro.pravaler.com.br/assets/images/NotFoundSimulation.svg', + type: 'img', + duration: 45, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887436000, + url: 'https://cadastro.pravaler.com.br/assets/images/NotFoundSimulation.svg', + type: 'img', + duration: 58, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887634000, + url: 'https://api.openreplay.com/5095/sessions/8119023158082639', + type: 'fetch', + duration: 869, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887634000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 2, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887635000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 242, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887635000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 242, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887635000, + url: 'https://api.openreplay.com/5095/sessions/8119023158082639/notes', + type: 'fetch', + duration: 472, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887635000, + url: 'https://asayer-mobs.s3.amazonaws.com/8119023158082639/devtools.mob', + type: 'fetch', + duration: 980, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887635000, + url: 'https://asayer-mobs.s3.amazonaws.com/8119023158082639/dom.mobs', + type: 'fetch', + duration: 1231, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887636000, + url: 'https://asayer-mobs.s3.amazonaws.com/8119023158082639/dom.mobe', + type: 'fetch', + duration: 235, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887636000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 44, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887639000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 8, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887639000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887639000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 4, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887639000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887641000, + url: 'https://static.amigoedu.com.br/5333fba9-1ba7-41b0-9aa4-f797817b654b.jpeg', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673887641000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 43, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887641000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 43, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887641000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 45, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887642000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 47, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887643000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 35, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887644000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 54, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887645000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 37, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887648000, + url: 'https://api.openreplay.com/5095/sessions/8118970199817356', + type: 'fetch', + duration: 700, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887648000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 1, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887649000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 249, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887649000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 253, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887649000, + url: 'https://api.openreplay.com/5095/sessions/8118970199817356/notes', + type: 'fetch', + duration: 546, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887649000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118970199817356/devtools.mob', + type: 'fetch', + duration: 906, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887649000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118970199817356/dom.mobs', + type: 'fetch', + duration: 1387, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887650000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118970199817356/dom.mobe', + type: 'fetch', + duration: 221, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887650000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 40, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 39, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://static.amigoedu.com.br/logos-universidade/12d3bdab-95cf-44a8-b23d-2f1f9bc5237a/ec9dc864-4504-412d-b3d4-bd4ee81ffb96.png', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 45, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 44, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 44, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://static.amigoedu.com.br/logos-universidade/37ee5a57-c6e0-4f2c-918a-4530d5190652/1663854154556.png', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 44, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 43, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 43, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 42, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 42, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 42, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 41, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 41, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 41, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 40, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 40, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 40, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 40, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 39, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 39, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 39, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 38, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 38, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 38, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 37, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 37, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 37, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 37, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 35, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 35, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 35, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 34, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 34, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 33, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 33, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 50, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 58, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 26, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 26, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 25, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 25, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 24, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 23, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 23, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 22, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 22, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 21, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 21, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 26, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 26, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 25, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 25, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 24, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 23, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 23, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 23, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 22, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 22, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 21, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 21, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 20, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 20, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 20, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 19, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 19, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887655000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 18, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887681000, + url: 'https://api.openreplay.com/5095/sessions/8118941288938501', + type: 'fetch', + duration: 730, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887681000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 1, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887682000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 237, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887682000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 239, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887682000, + url: 'https://api.openreplay.com/5095/sessions/8118941288938501/notes', + type: 'fetch', + duration: 467, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887682000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118941288938501/devtools.mob', + type: 'fetch', + duration: 942, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887682000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118941288938501/dom.mobs', + type: 'fetch', + duration: 1742, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887684000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118941288938501/dom.mobe', + type: 'fetch', + duration: 1320, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887684000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 1, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://cadastro.pravaler.com.br/assets/images/homem-duvida.png', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://pubads.g.doubleclick.net/activity;xsp=5059779;ord=1673885219551', + type: 'img', + duration: 108, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://pubads.g.doubleclick.net/activity;dc_iu=/8804/DFPAudiencePixel;ord=1673885226546dc_seg=7527659555', + type: 'img', + duration: 126, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://pubads.g.doubleclick.net/activity;xsp=5059782;ord=1673885226546', + type: 'img', + duration: 126, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 33, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://cadastro.pravaler.com.br/assets/images/homem-lupa.png', + type: 'img', + duration: 42, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 33, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 1, + ttfb: null, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://cadastro.pravaler.com.br/assets/images/homem-duvida.png', + type: 'img', + duration: 32, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://pubads.g.doubleclick.net/activity;xsp=5059779;ord=1673885219551', + type: 'img', + duration: 92, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://pubads.g.doubleclick.net/activity;dc_iu=/8804/DFPAudiencePixel;ord=1673885226546dc_seg=7527659555', + type: 'img', + duration: 104, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://pubads.g.doubleclick.net/activity;xsp=5059782;ord=1673885226546', + type: 'img', + duration: 104, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 34, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 34, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://cadastro.pravaler.com.br/assets/images/homem-lupa.png', + type: 'img', + duration: 38, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887689000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 0, + status: 400, + }, + { + datetime: 1673887690000, + url: 'https://static.amigoedu.com.br/logos-universidade/4c6524d6-0b59-4e73-abac-5135940afd08/1659970046509.png', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673887690000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887690000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887690000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 26, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 26, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 25, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 25, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 25, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 33, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 33, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887691000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 39, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 37, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 35, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 35, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 35, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 34, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 34, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 34, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 33, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 32, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 32, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 32, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 25, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 25, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 24, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 41, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 37, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887692000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 46, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887693000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 18, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887693000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 18, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887693000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 17, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887693000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 17, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887693000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 16, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887693000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 16, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887693000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 15, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887693000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://static.amigoedu.com.br/logos-universidade/37ee5a57-c6e0-4f2c-918a-4530d5190652/1663854154556.png', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 32, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 26, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://static.amigoedu.com.br/49cc6e34-c68c-45b5-874d-a0772831969e.png', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 26, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 26, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 26, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://static.amigoedu.com.br/0efce96e-d1cd-406a-9af0-a37788152473.png', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 26, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 25, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 25, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 25, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 24, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 24, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 23, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 23, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 22, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 22, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 22, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887694000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 42, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 43, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 42, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 42, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 42, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 41, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 41, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 41, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 40, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 40, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 40, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 40, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 40, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 40, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 39, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 39, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 39, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 39, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 38, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 38, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 37, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 37, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 35, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 35, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 35, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 34, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887695000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 33, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://static.amigoedu.com.br/logos-universidade/e4a9e102-de66-470e-845f-6cd9b7557505/1667943448716.png', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673887697000, + url: 'https://static.amigoedu.com.br/logos-universidade/df3cbfcd-e275-4516-ac2e-9e2e2a97ae75/1660158207598.jpeg', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673887697000, + url: 'https://static.amigoedu.com.br/logos-universidade/426e4f84-4181-4d15-a180-c9efd391b535/1652990351825.png', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673887697000, + url: 'https://static.amigoedu.com.br/9658f5f5-130c-462f-b712-a63758f7aada.png', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673887697000, + url: 'https://static.amigoedu.com.br/d564c1ba-5a07-4797-b314-ee9233167e91.png', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673887697000, + url: 'https://static.amigoedu.com.br/5333fba9-1ba7-41b0-9aa4-f797817b654b.jpeg', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 38, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 38, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 42, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/ies_default_icon.png', + type: 'img', + duration: 39, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 23, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 22, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 22, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 22, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 21, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 20, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 20, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 20, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 19, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 19, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 18, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 18, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 17, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 17, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 17, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 16, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 16, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 16, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 15, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 15, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 15, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 14, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887697000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887698000, + url: 'https://api.openreplay.com/5095/sessions/8118857356000307', + type: 'fetch', + duration: 751, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887698000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 1, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887699000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 237, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887699000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 239, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887699000, + url: 'https://api.openreplay.com/5095/sessions/8118857356000307/notes', + type: 'fetch', + duration: 467, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887699000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118857356000307/devtools.mob', + type: 'fetch', + duration: 927, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887699000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118857356000307/dom.mobs', + type: 'fetch', + duration: 1217, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887700000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118857356000307/dom.mobe', + type: 'fetch', + duration: 218, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887700000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 44, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887702000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887702000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 32, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887706000, + url: 'https://cadastro.pravaler.com.br/assets/images/homem-duvida.png', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887706000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887706000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 33, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887706000, + url: 'https://cadastro.pravaler.com.br/assets/images/homem-lupa.png', + type: 'img', + duration: 39, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887706000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 32, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887706000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 2, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887712000, + url: 'https://api.openreplay.com/5095/sessions/8118843021704432', + type: 'fetch', + duration: 830, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887712000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 2, + ttfb: 2, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887713000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 238, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887713000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 241, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887713000, + url: 'https://api.openreplay.com/5095/sessions/8118843021704432/notes', + type: 'fetch', + duration: 466, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887713000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118843021704432/devtools.mob', + type: 'fetch', + duration: 683, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887713000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118843021704432/dom.mobs', + type: 'fetch', + duration: 1059, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887714000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118843021704432/dom.mobe', + type: 'fetch', + duration: 218, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887714000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887909000, + url: 'https://cadastro.pravaler.com.br/assets/images/NotFoundSimulation.svg', + type: 'img', + duration: 38, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887910000, + url: 'https://cadastro.pravaler.com.br/assets/images/NotFoundSimulation.svg', + type: 'img', + duration: 34, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887912000, + url: 'https://api.openreplay.com/5095/sessions/8118806248420922', + type: 'fetch', + duration: 791, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887912000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 4, + ttfb: 3, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887913000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 255, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887913000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 270, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887913000, + url: 'https://api.openreplay.com/5095/sessions/8118806248420922/notes', + type: 'fetch', + duration: 474, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887913000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118806248420922/devtools.mob', + type: 'fetch', + duration: 738, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887913000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118806248420922/dom.mobs', + type: 'fetch', + duration: 964, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887914000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118806248420922/dom.mobe', + type: 'fetch', + duration: 235, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887914000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887921000, + url: 'https://cadastro.pravaler.com.br/assets/images/NotFoundSimulation.svg', + type: 'img', + duration: 42, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887921000, + url: 'https://cadastro.pravaler.com.br/assets/images/NotFoundSimulation.svg', + type: 'img', + duration: 50, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887923000, + url: 'https://api.openreplay.com/5095/sessions/8118804409742859', + type: 'fetch', + duration: 742, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887923000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 2, + ttfb: 2, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887924000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 236, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887924000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 237, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887924000, + url: 'https://api.openreplay.com/5095/sessions/8118804409742859/notes', + type: 'fetch', + duration: 462, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887924000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118804409742859/devtools.mob', + type: 'fetch', + duration: 854, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887924000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118804409742859/dom.mobs', + type: 'fetch', + duration: 1168, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887925000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118804409742859/dom.mobe', + type: 'fetch', + duration: 216, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887925000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 39, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887939000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 20, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887939000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 19, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887939000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 19, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887939000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 18, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887939000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 18, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887939000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 42, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887939000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 12, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887939000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 11, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887939000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 11, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887939000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 11, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887939000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 10, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887939000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887940000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 14, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887940000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 14, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887940000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 13, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887940000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 12, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887940000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 12, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887940000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887942000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 48, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887942000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 69, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887947000, + url: 'https://api.openreplay.com/5095/sessions/8118789878445746', + type: 'fetch', + duration: 760, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887947000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 240, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887947000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 243, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887947000, + url: 'https://api.openreplay.com/5095/sessions/8118789878445746/notes', + type: 'fetch', + duration: 465, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887947000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118789878445746/devtools.mob', + type: 'fetch', + duration: 1113, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887947000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118789878445746/dom.mobs', + type: 'fetch', + duration: 1243, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887947000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 2, + ttfb: 2, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887949000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118789878445746/dom.mobe', + type: 'fetch', + duration: 233, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887949000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887952000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887953000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 13, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887953000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 26, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887953000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 6, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887953000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 6, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887964000, + url: 'https://api.openreplay.com/5095/sessions/8118778337868907', + type: 'fetch', + duration: 702, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887964000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 2, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887965000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 235, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887965000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 476, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887965000, + url: 'https://api.openreplay.com/5095/sessions/8118778337868907/notes', + type: 'fetch', + duration: 480, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887965000, + url: 'https://api.openreplay.com/5095/sessions/search/ids', + type: 'fetch', + duration: 487, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887965000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118778337868907/devtools.mob', + type: 'fetch', + duration: 918, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887965000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118778337868907/dom.mobs', + type: 'fetch', + duration: 1211, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887966000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118778337868907/dom.mobe', + type: 'fetch', + duration: 231, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887966000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 72, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887969000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 43, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887969000, + url: 'https://pubads.g.doubleclick.net/activity;dc_iu=/8804/DFPAudiencePixel;ord=1673882715700dc_seg=7527870968', + type: 'img', + duration: 70, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887969000, + url: 'https://pubads.g.doubleclick.net/activity;xsp=5059779;ord=1673882715697', + type: 'img', + duration: 70, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887969000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 1, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887970000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 38, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887970000, + url: 'https://pubads.g.doubleclick.net/activity;dc_iu=/8804/DFPAudiencePixel;ord=1673882715700dc_seg=7527870968', + type: 'img', + duration: 90, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887970000, + url: 'https://pubads.g.doubleclick.net/activity;xsp=5059779;ord=1673882715697', + type: 'img', + duration: 90, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887970000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 46, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887970000, + url: 'https://pubads.g.doubleclick.net/activity;dc_iu=/8804/DFPAudiencePixel;ord=1673882715700dc_seg=7527870968', + type: 'img', + duration: 85, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887970000, + url: 'https://pubads.g.doubleclick.net/activity;xsp=5059779;ord=1673882715697', + type: 'img', + duration: 85, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887971000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 38, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887971000, + url: 'https://pubads.g.doubleclick.net/activity;dc_iu=/8804/DFPAudiencePixel;ord=1673882715700dc_seg=7527870968', + type: 'img', + duration: 66, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887971000, + url: 'https://pubads.g.doubleclick.net/activity;xsp=5059779;ord=1673882715697', + type: 'img', + duration: 65, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887973000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 7, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887973000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 34, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887973000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 4, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887973000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 4, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887973000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 7, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887973000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 7, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887974000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 11, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887974000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 34, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887974000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 9, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887974000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 42, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887978000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 37, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887978000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 77, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887979000, + url: 'https://api.openreplay.com/5095/sessions/8118750395677903', + type: 'fetch', + duration: 833, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887980000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 239, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887980000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 241, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887980000, + url: 'https://api.openreplay.com/5095/sessions/8118750395677903/notes', + type: 'fetch', + duration: 465, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887980000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118750395677903/devtools.mob', + type: 'fetch', + duration: 1054, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887980000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118750395677903/dom.mobs', + type: 'fetch', + duration: 1277, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887980000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 2, + ttfb: 2, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887982000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118750395677903/dom.mobe', + type: 'fetch', + duration: 217, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887982000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887982000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887982000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 32, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887996000, + url: 'https://api.openreplay.com/5095/sessions/8118746485800060', + type: 'fetch', + duration: 754, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887996000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 2, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673887997000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 242, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887997000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 245, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887997000, + url: 'https://api.openreplay.com/5095/sessions/8118746485800060/notes', + type: 'fetch', + duration: 468, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887997000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118746485800060/devtools.mob', + type: 'fetch', + duration: 1019, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887997000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118746485800060/dom.mobs', + type: 'fetch', + duration: 1190, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887998000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118746485800060/dom.mobe', + type: 'fetch', + duration: 236, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887998000, + url: 'https://cadastro.pravaler.com.br/assets/images/homem-duvida.png', + type: 'img', + duration: 41, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887998000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 41, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887998000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 39, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887998000, + url: 'https://cadastro.pravaler.com.br/assets/images/homem-lupa.png', + type: 'img', + duration: 40, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673887998000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 39, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 50, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 17, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 17, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 16, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 16, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 16, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 15, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 15, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 15, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 18, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 17, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 17, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 17, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 17, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 16, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 16, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 16, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888005000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 15, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888007000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 35, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888007000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 38, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888010000, + url: 'https://api.openreplay.com/5095/sessions/8118633985734291', + type: 'fetch', + duration: 722, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888010000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 242, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888010000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 469, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888010000, + url: 'https://api.openreplay.com/5095/sessions/8118633985734291/notes', + type: 'fetch', + duration: 469, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888010000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118633985734291/devtools.mob', + type: 'fetch', + duration: 937, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888010000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118633985734291/dom.mobs', + type: 'fetch', + duration: 1220, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888010000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 1, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673888012000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118633985734291/dom.mobe', + type: 'fetch', + duration: 219, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888012000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 45, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888012000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888012000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888023000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 8, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888023000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 34, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888023000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 9, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888023000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 33, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888023000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 5, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888023000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 37, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888025000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 3, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888025000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888025000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 5, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888025000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888027000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 41, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888027000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 41, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888028000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888028000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 41, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888029000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 40, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888029000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 71, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888032000, + url: 'https://api.openreplay.com/5095/sessions/8118630504330810', + type: 'fetch', + duration: 774, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888032000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 2, + ttfb: 2, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673888033000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 237, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888033000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 240, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888033000, + url: 'https://api.openreplay.com/5095/sessions/8118630504330810/notes', + type: 'fetch', + duration: 463, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888033000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118630504330810/devtools.mob', + type: 'fetch', + duration: 891, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888033000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118630504330810/dom.mobs', + type: 'fetch', + duration: 1216, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888034000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118630504330810/dom.mobe', + type: 'fetch', + duration: 232, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888055000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 1, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673888057000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 5, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888057000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 26, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888058000, + url: 'https://cadastro.pravaler.com.br/assets/images/homem-duvida.png', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888058000, + url: 'https://pubads.g.doubleclick.net/activity;dc_iu=/8804/DFPAudiencePixel;ord=1673880463599dc_seg=7527870968', + type: 'img', + duration: 77, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888058000, + url: 'https://pubads.g.doubleclick.net/activity;xsp=5059779;ord=1673880463599', + type: 'img', + duration: 76, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888058000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888058000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 34, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888058000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888058000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888058000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888058000, + url: 'https://cadastro.pravaler.com.br/assets/images/homem-lupa.png', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888058000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888058000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 2, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673888058000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 39, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888058000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 41, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888060000, + url: 'https://api.openreplay.com/5095/sessions/8118613089434832', + type: 'fetch', + duration: 805, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888060000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 1, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673888061000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 238, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888061000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 241, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888061000, + url: 'https://api.openreplay.com/5095/sessions/8118613089434832/notes', + type: 'fetch', + duration: 465, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888061000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118613089434832/devtools.mob', + type: 'fetch', + duration: 1069, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888061000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118613089434832/dom.mobs', + type: 'fetch', + duration: 1293, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888062000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118613089434832/dom.mobe', + type: 'fetch', + duration: 227, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888063000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 33, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888063000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 40, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888063000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 69, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888086000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888086000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 34, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888087000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 4, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888087000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888087000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 41, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888087000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888088000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 32, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888089000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888089000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 73, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888090000, + url: 'https://api.openreplay.com/5095/sessions/8118592777665791', + type: 'fetch', + duration: 686, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888090000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 4, + ttfb: 4, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673888091000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 244, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888091000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 251, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888091000, + url: 'https://api.openreplay.com/5095/sessions/8118592777665791/notes', + type: 'fetch', + duration: 470, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888091000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118592777665791/devtools.mob', + type: 'fetch', + duration: 1044, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888091000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118592777665791/dom.mobs', + type: 'fetch', + duration: 1139, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888092000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118592777665791/dom.mobe', + type: 'fetch', + duration: 220, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888106000, + url: 'https://api.openreplay.com/5095/sessions/8118562697246396', + type: 'fetch', + duration: 689, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888106000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 1, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673888107000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 237, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888107000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 239, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888107000, + url: 'https://api.openreplay.com/5095/sessions/8118562697246396/notes', + type: 'fetch', + duration: 462, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888107000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118562697246396/devtools.mob', + type: 'fetch', + duration: 889, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888107000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118562697246396/dom.mobs', + type: 'fetch', + duration: 1179, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888108000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118562697246396/dom.mobe', + type: 'fetch', + duration: 219, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888108000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 35, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888111000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888112000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 35, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888112000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888113000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 27, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888114000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888114000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 33, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888117000, + url: 'https://api.openreplay.com/5095/sessions/8118559548241523', + type: 'fetch', + duration: 719, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888117000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 1, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673888118000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 237, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888118000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 239, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888118000, + url: 'https://api.openreplay.com/5095/sessions/8118559548241523/notes', + type: 'fetch', + duration: 464, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888118000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118559548241523/devtools.mob', + type: 'fetch', + duration: 1162, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888118000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118559548241523/dom.mobs', + type: 'fetch', + duration: 1346, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888119000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118559548241523/dom.mobe', + type: 'fetch', + duration: 233, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888119000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 53, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 9, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 9, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 9, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 9, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 8, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 8, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 8, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 8, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 43, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 14, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 14, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 14, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 14, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 13, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 13, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 12, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 12, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888122000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 12, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/images/homem-duvida.png', + type: 'img', + duration: 31, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 35, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://static.amigoedu.com.br/logos-universidade/37ee5a57-c6e0-4f2c-918a-4530d5190652/1663854154556.png', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673888125000, + url: 'https://static.amigoedu.com.br/logos-universidade/426e4f84-4181-4d15-a180-c9efd391b535/1652990351825.png', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673888125000, + url: 'https://static.amigoedu.com.br/88cc0647-cbad-4811-b45f-4758b469bdd0.png', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/images/ies_default_icon.png', + type: 'img', + duration: 38, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://static.amigoedu.com.br/logos-universidade/43ab3b89-4274-4127-99f4-2e7b1d2b228f/1658502191843.jpeg', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673888125000, + url: 'https://static.amigoedu.com.br/b9af77d6-bf8b-476c-9b3a-51447140630b.jpeg', + type: 'img', + duration: null, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 0, + status: 400, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 37, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 48, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/images/homem-lupa.png', + type: 'img', + duration: 33, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 2, + ttfb: 2, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 19, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 18, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 18, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 18, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 17, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 17, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 17, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 16, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888125000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 33, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888126000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888126000, + url: 'https://cadastro.pravaler.com.br/assets/icons/book-icon.svg', + type: 'img', + duration: 32, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888126000, + url: 'https://cadastro.pravaler.com.br/assets/icons/point-icon.svg', + type: 'img', + duration: 37, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888126000, + url: 'https://cadastro.pravaler.com.br/assets/images/ies_default_icon.png', + type: 'img', + duration: 34, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888127000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 16, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888127000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 16, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888127000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 15, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888127000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 15, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888127000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 15, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888127000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 15, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888127000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 15, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888127000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 15, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888127000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 35, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888128000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 28, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888128000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888130000, + url: 'https://api.openreplay.com/5095/sessions/8118556979885624', + type: 'fetch', + duration: 758, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888130000, + url: 'https://api.openreplay.com/integrations/issues', + type: 'fetch', + duration: 243, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888130000, + url: 'https://api.openreplay.com/client/members', + type: 'fetch', + duration: 242, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888130000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118556979885624/devtools.mob', + type: 'fetch', + duration: 944, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888130000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118556979885624/dom.mobs', + type: 'fetch', + duration: 1220, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888130000, + url: 'https://app.openreplay.com/3c091395cdc66aaa862e731865a1e1c4.svg', + type: 'img', + duration: 1, + ttfb: 1, + headerSize: null, + encodedBodySize: 771, + decodedBodySize: 2330, + success: 1, + status: 200, + }, + { + datetime: 1673888131000, + url: 'https://api.openreplay.com/5095/sessions/search/ids', + type: 'fetch', + duration: 262, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888131000, + url: 'https://api.openreplay.com/5095/sessions/8118556979885624/notes', + type: 'fetch', + duration: 463, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888132000, + url: 'https://asayer-mobs.s3.amazonaws.com/8118556979885624/dom.mobe', + type: 'fetch', + duration: 231, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888132000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image-studying-men.png', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888132000, + url: 'https://cadastro.pravaler.com.br/assets/images/dsprv-image_group-clipart-doctor.png', + type: 'img', + duration: 29, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888132000, + url: 'https://cadastro.pravaler.com.br/_next/static/media/logo-pravaler.a40af452.svg', + type: 'img', + duration: 30, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + { + datetime: 1673888139000, + url: 'https://cadastro.pravaler.com.br/assets/images/dropdown-icon.svg', + type: 'img', + duration: 36, + ttfb: null, + headerSize: null, + encodedBodySize: null, + decodedBodySize: null, + success: 1, + status: 200, + }, + ], + notes: [], + metadata: { plan: 'pay_as_you_go' }, + issues: [ + { + issueId: '9158adad14bcb1e0db18384c778a18f5ef2', + sessionId: 8119081922378909, + timestamp: 1673887658753, + seqIndex: 26901, + payload: { Rate: 71, Duration: 20804 }, + projectId: 2325, + type: 'cpu', + contextString: 'https://app.openreplay.com/5095/session/8118970199817356', + context: null, + }, + { + issueId: '915b2c49d8e08176a84c0d8e58732995fb8', + sessionId: 8119081922378909, + timestamp: 1673888064761, + seqIndex: 74984, + payload: { Rate: 80, Duration: 9684 }, + projectId: 2325, + type: 'cpu', + contextString: 'https://app.openreplay.com/5095/session/8118613089434832', + context: null, + }, + ], + live: false, + inDB: true, + }, +}; diff --git a/frontend/tests/session.test.js b/frontend/tests/session.test.js new file mode 100644 index 000000000..923bca55f --- /dev/null +++ b/frontend/tests/session.test.js @@ -0,0 +1,32 @@ +import { describe, expect, test } from '@jest/globals'; + +import Session from '../app/types/session'; +import { Click, Location } from '../app/types/session/event'; +import Issue from '../app/types/session/issue'; +import { session } from './mocks/sessionResponse'; +import { issues, events } from "./mocks/sessionData"; + +describe('Testing Session class', () => { + const sessionInfo = new Session(session.data); + + test('checking type instances', () => { + expect(sessionInfo).toBeInstanceOf(Session); + expect(sessionInfo.issues[0]).toBeInstanceOf(Issue); + expect(sessionInfo.events[0]).toBeInstanceOf(Location); + expect(sessionInfo.events[1]).toBeInstanceOf(Click); + }); + test('checking basic session info(id, userId, issues and events lengths to match)', () => { + expect(sessionInfo.sessionId).toBe('8119081922378909'); + expect(sessionInfo.isMobile).toBe(false); + expect(sessionInfo.userNumericHash).toBe(55003039); + expect(sessionInfo.userId).toBe('fernando.dufour@pravaler.com.br'); + expect(sessionInfo.issues.length).toBe(2); + expect(sessionInfo.notesWithEvents.length).toBe(362); + }); + test('checking issue mapping', () => { + expect([...sessionInfo.issues]).toMatchObject(issues); + }); + test('checking events mapping', () => { + expect([...sessionInfo.events.slice(0, 10)]).toMatchObject(events) + }) +}); diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 49fa191a5..bfb9303da 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -3,6 +3,8 @@ "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "noImplicitAny": true, + "alwaysStrict": true, + "strictNullChecks": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, diff --git a/frontend/webpack.config.ts b/frontend/webpack.config.ts index 404531b55..6dd02728f 100644 --- a/frontend/webpack.config.ts +++ b/frontend/webpack.config.ts @@ -1,6 +1,6 @@ import webpack from "webpack"; import path from "path"; -import { Configuration as WebpackConfiguration, HotModuleReplacementPlugin } from "webpack"; +import { Configuration as WebpackConfiguration } from "webpack"; import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server'; import CopyWebpackPlugin from 'copy-webpack-plugin'; import HtmlWebpackPlugin from "html-webpack-plugin"; @@ -123,7 +123,7 @@ const config: Configuration = { { from: "./app/assets", to: "assets" }, ], }), - new MiniCssExtractPlugin(), + new MiniCssExtractPlugin({ ignoreOrder: true }), ], devtool: isDevelopment ? "inline-source-map" : false, performance: { @@ -132,10 +132,12 @@ const config: Configuration = { devServer: { // static: path.join(__dirname, "public"), historyApiFallback: true, - host: 'localhost', + host: '0.0.0.0', open: true, port: 3333, hot: true, + compress: true, + allowedHosts: "all", }, }; diff --git a/frontend/yarn.lock b/frontend/yarn.lock new file mode 100644 index 000000000..b67995034 --- /dev/null +++ b/frontend/yarn.lock @@ -0,0 +1,24676 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + cacheKey: 8c0 + +"@ampproject/remapping@npm:^2.1.0": + version: 2.2.0 + resolution: "@ampproject/remapping@npm:2.2.0" + dependencies: + "@jridgewell/gen-mapping": ^0.1.0 + "@jridgewell/trace-mapping": ^0.3.9 + checksum: d267d8def81d75976bed4f1f81418a234a75338963ed0b8565342ef3918b07e9043806eb3a1736df7ac0774edb98e2890f880bba42817f800495e4ae3fac995e + languageName: node + linkType: hard + +"@apideck/better-ajv-errors@npm:^0.3.1": + version: 0.3.6 + resolution: "@apideck/better-ajv-errors@npm:0.3.6" + dependencies: + json-schema: ^0.4.0 + jsonpointer: ^5.0.0 + leven: ^3.1.0 + peerDependencies: + ajv: ">=8" + checksum: f89a1e16ecbc2ada91c56d4391c8345471e385f0b9c38d62c3bccac40ec94482cdfa496d4c2fe0af411e9851a9931c0d5042a8040f52213f603ba6b6fd7f949b + languageName: node + linkType: hard + +"@babel/cli@npm:^7.10.5": + version: 7.20.7 + resolution: "@babel/cli@npm:7.20.7" + dependencies: + "@jridgewell/trace-mapping": ^0.3.8 + "@nicolo-ribaudo/chokidar-2": 2.1.8-no-fsevents.3 + chokidar: ^3.4.0 + commander: ^4.0.1 + convert-source-map: ^1.1.0 + fs-readdir-recursive: ^1.1.0 + glob: ^7.2.0 + make-dir: ^2.1.0 + slash: ^2.0.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + dependenciesMeta: + "@nicolo-ribaudo/chokidar-2": + optional: true + chokidar: + optional: true + bin: + babel: ./bin/babel.js + babel-external-helpers: ./bin/babel-external-helpers.js + checksum: 7907f916e3fdf39e08cdb38559285ba69e6eb0adce47224ef5b380507565682151e1a76f74fd7802a58e7db5efd2090e628684cd0fd901fba2d9125492e43c25 + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.18.6, @babel/code-frame@npm:^7.5.5, @babel/code-frame@npm:^7.8.3": + version: 7.18.6 + resolution: "@babel/code-frame@npm:7.18.6" + dependencies: + "@babel/highlight": ^7.18.6 + checksum: e3966f2717b7ebd9610524730e10b75ee74154f62617e5e115c97dbbbabc5351845c9aa850788012cb4d9aee85c3dc59fe6bef36690f244e8dcfca34bd35e9c9 + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.17.7, @babel/compat-data@npm:^7.20.1, @babel/compat-data@npm:^7.20.5": + version: 7.20.10 + resolution: "@babel/compat-data@npm:7.20.10" + checksum: 5394197084af5118287e20ea8e4942c43bb4047943ddb12cb19d44c19eeeaf038459b087adb2e6b7d46780543d10b3a1a415441fc8fb98f6dc9d7e902a19e325 + languageName: node + linkType: hard + +"@babel/core@npm:7.12.9": + version: 7.12.9 + resolution: "@babel/core@npm:7.12.9" + dependencies: + "@babel/code-frame": ^7.10.4 + "@babel/generator": ^7.12.5 + "@babel/helper-module-transforms": ^7.12.1 + "@babel/helpers": ^7.12.5 + "@babel/parser": ^7.12.7 + "@babel/template": ^7.12.7 + "@babel/traverse": ^7.12.9 + "@babel/types": ^7.12.7 + convert-source-map: ^1.7.0 + debug: ^4.1.0 + gensync: ^1.0.0-beta.1 + json5: ^2.1.2 + lodash: ^4.17.19 + resolve: ^1.3.2 + semver: ^5.4.1 + source-map: ^0.5.0 + checksum: c11d26f5a33a29c94fdd1c492dfd723f48926c51e975448dda57c081c0d74c7b03298642b2651559e0d330ec868b5757b60f9648c71cf7f89fddf79a17cf006f + languageName: node + linkType: hard + +"@babel/core@npm:^7.1.0, @babel/core@npm:^7.11.1, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.10, @babel/core@npm:^7.12.3, @babel/core@npm:^7.17.12, @babel/core@npm:^7.19.6, @babel/core@npm:^7.7.5": + version: 7.20.12 + resolution: "@babel/core@npm:7.20.12" + dependencies: + "@ampproject/remapping": ^2.1.0 + "@babel/code-frame": ^7.18.6 + "@babel/generator": ^7.20.7 + "@babel/helper-compilation-targets": ^7.20.7 + "@babel/helper-module-transforms": ^7.20.11 + "@babel/helpers": ^7.20.7 + "@babel/parser": ^7.20.7 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.20.12 + "@babel/types": ^7.20.7 + convert-source-map: ^1.7.0 + debug: ^4.1.0 + gensync: ^1.0.0-beta.2 + json5: ^2.2.2 + semver: ^6.3.0 + checksum: 190f5e144396692e163d62f17ea715a4cc3cfc22ea8052424e20a5e2bdf162195eac71440244689b2e6d4d61dfdeab1d7f475d77ab31904832c844fe572fbee2 + languageName: node + linkType: hard + +"@babel/generator@npm:^7.12.11, @babel/generator@npm:^7.12.5, @babel/generator@npm:^7.20.7, @babel/generator@npm:^7.7.2": + version: 7.20.7 + resolution: "@babel/generator@npm:7.20.7" + dependencies: + "@babel/types": ^7.20.7 + "@jridgewell/gen-mapping": ^0.3.2 + jsesc: ^2.5.1 + checksum: b22032867dfad3115404ea74fd063079883428cf838ec490a1f64d2e5e4dd82f94f77df90eb95a57740fb387a115b5ffe655e768cb50862832c6f9f6ffb4be79 + languageName: node + linkType: hard + +"@babel/helper-annotate-as-pure@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-annotate-as-pure@npm:7.18.6" + dependencies: + "@babel/types": ^7.18.6 + checksum: e413cd022e1e21232c1ce98f3e1198ec5f4774c7eceb81155a45f9cb6d8481f3983c52f83252309856668e728c751f0340d29854b604530a694899208df6bcc3 + languageName: node + linkType: hard + +"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.18.6": + version: 7.18.9 + resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.18.9" + dependencies: + "@babel/helper-explode-assignable-expression": ^7.18.6 + "@babel/types": ^7.18.9 + checksum: 8571b3cebdd3b80349aaa04e0c1595d8fc283aea7f3d7153dfba0d5fcb090e53f3fe98ca4c19ffa185e642a14ea2b97f11eccefc9be9185acca8916e68612c3f + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.13.0, @babel/helper-compilation-targets@npm:^7.17.7, @babel/helper-compilation-targets@npm:^7.18.9, @babel/helper-compilation-targets@npm:^7.20.0, @babel/helper-compilation-targets@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/helper-compilation-targets@npm:7.20.7" + dependencies: + "@babel/compat-data": ^7.20.5 + "@babel/helper-validator-option": ^7.18.6 + browserslist: ^4.21.3 + lru-cache: ^5.1.1 + semver: ^6.3.0 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 68c3e12e04c8f26c82a1aabb8003610b818d4171e0b885d1ca87c700acd7f0c50a7f4f1d3c0044947e327cb5670294b55c666d09109144b3b01021c587401e4c + languageName: node + linkType: hard + +"@babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.20.5, @babel/helper-create-class-features-plugin@npm:^7.20.7": + version: 7.20.12 + resolution: "@babel/helper-create-class-features-plugin@npm:7.20.12" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-function-name": ^7.19.0 + "@babel/helper-member-expression-to-functions": ^7.20.7 + "@babel/helper-optimise-call-expression": ^7.18.6 + "@babel/helper-replace-supers": ^7.20.7 + "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 + "@babel/helper-split-export-declaration": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: e17a6e3afa92c5b286093f754efa692a76a5893fe39e66c7b246e3c37db5be43012973975ed1548f1ee6c2713dd88cdb369672460e29be2c072c3cdf930879ef + languageName: node + linkType: hard + +"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.20.5": + version: 7.20.5 + resolution: "@babel/helper-create-regexp-features-plugin@npm:7.20.5" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + regexpu-core: ^5.2.1 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 567132405fc79cd97a656a966d97a76d22cb05dd82b9293952f51ba849b849ba829cf6715bc7c8aa3f3510e1b5aaa798e3216cd92a612e353004c55a407b35cd + languageName: node + linkType: hard + +"@babel/helper-define-polyfill-provider@npm:^0.1.5": + version: 0.1.5 + resolution: "@babel/helper-define-polyfill-provider@npm:0.1.5" + dependencies: + "@babel/helper-compilation-targets": ^7.13.0 + "@babel/helper-module-imports": ^7.12.13 + "@babel/helper-plugin-utils": ^7.13.0 + "@babel/traverse": ^7.13.0 + debug: ^4.1.1 + lodash.debounce: ^4.0.8 + resolve: ^1.14.2 + semver: ^6.1.2 + peerDependencies: + "@babel/core": ^7.4.0-0 + checksum: b83aa728cc2fd4882a82ce67c300407024a05adc3f88c461a02438b2ab50c66e711f8ba36ef622637eb7af4d40aaafa0d70e77e0da9adc5710f190a686082f94 + languageName: node + linkType: hard + +"@babel/helper-define-polyfill-provider@npm:^0.3.3": + version: 0.3.3 + resolution: "@babel/helper-define-polyfill-provider@npm:0.3.3" + dependencies: + "@babel/helper-compilation-targets": ^7.17.7 + "@babel/helper-plugin-utils": ^7.16.7 + debug: ^4.1.1 + lodash.debounce: ^4.0.8 + resolve: ^1.14.2 + semver: ^6.1.2 + peerDependencies: + "@babel/core": ^7.4.0-0 + checksum: c3668f9ee2b76bfc08398756c504a8823e18bad05d0c2ee039b821c839e2b70f3b6ad8b7a3d3a6be434d981ed2af845a490aafecc50eaefb9b5099f2da156527 + languageName: node + linkType: hard + +"@babel/helper-environment-visitor@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/helper-environment-visitor@npm:7.18.9" + checksum: a69dd50ea91d8143b899a40ca7a387fa84dbaa02e606d8692188c7c59bd4007bcd632c189f7b7dab72cb7a016e159557a6fccf7093ab9b584d87cf2ea8cf36b7 + languageName: node + linkType: hard + +"@babel/helper-explode-assignable-expression@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-explode-assignable-expression@npm:7.18.6" + dependencies: + "@babel/types": ^7.18.6 + checksum: 6e2fc5841fd849c840634e55b3a3f373167179bddb3d1c5fa2d7f63c3959425b8f87cd5c5ce5dcbb96e877a5033687840431b84a8e922c323f8e6aac9645db0b + languageName: node + linkType: hard + +"@babel/helper-function-name@npm:^7.18.9, @babel/helper-function-name@npm:^7.19.0": + version: 7.19.0 + resolution: "@babel/helper-function-name@npm:7.19.0" + dependencies: + "@babel/template": ^7.18.10 + "@babel/types": ^7.19.0 + checksum: a4181d23274d926df3a8032fb2ff210b8a27c83fedd9e7bd148a6877cb4070be4caf69ddae1bf29447e1e84da807ff769a31ca661ef55ecd4d4d672073a68c48 + languageName: node + linkType: hard + +"@babel/helper-hoist-variables@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-hoist-variables@npm:7.18.6" + dependencies: + "@babel/types": ^7.18.6 + checksum: 830aa7ca663b0d2a025513ab50a9a10adb2a37d8cf3ba40bb74b8ac14d45fbc3d08c37b1889b10d36558edfbd34ff914909118ae156c2f0915f2057901b90eff + languageName: node + linkType: hard + +"@babel/helper-member-expression-to-functions@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/helper-member-expression-to-functions@npm:7.20.7" + dependencies: + "@babel/types": ^7.20.7 + checksum: f2cdaf0b8a280f59904551bf3f1fe39eedf5952a8a9ac61333470f8ee3ef036cd60500401a22494fd10b8ffdb7853d0ac1708870afb2255ebc73d8c43b9a8267 + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.12.13, @babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-module-imports@npm:7.18.6" + dependencies: + "@babel/types": ^7.18.6 + checksum: a92e28fc4b5dbb0d0afd4a313efc0cf5b26ce1adc0c01fc22724c997789ac7d7f4f30bc9143d94a6ba8b0a035933cf63a727a365ce1c57dbca0935f48de96244 + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.12.1, @babel/helper-module-transforms@npm:^7.18.6, @babel/helper-module-transforms@npm:^7.20.11": + version: 7.20.11 + resolution: "@babel/helper-module-transforms@npm:7.20.11" + dependencies: + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-module-imports": ^7.18.6 + "@babel/helper-simple-access": ^7.20.2 + "@babel/helper-split-export-declaration": ^7.18.6 + "@babel/helper-validator-identifier": ^7.19.1 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.20.10 + "@babel/types": ^7.20.7 + checksum: a6cc533c3c9a2ed939f041002c142611a657a6defffda195f56936793f7ceb6c9abcc0c5e77e49da9e1584f60442e04107937394dbd6560d1094cfd7f3a9a152 + languageName: node + linkType: hard + +"@babel/helper-optimise-call-expression@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-optimise-call-expression@npm:7.18.6" + dependencies: + "@babel/types": ^7.18.6 + checksum: f1352ebc5d9abae6088e7d9b4b6b445c406ba552ef61e967ec77d005ff65752265b002b6faaf16cc293f9e37753760ef05c1f4b26cda1039256917022ba5669c + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:7.10.4": + version: 7.10.4 + resolution: "@babel/helper-plugin-utils@npm:7.10.4" + checksum: 113d0405281f5490658f7c1c3a81b4a37927375e1ebcccd2fd90be538a102da0c2d6024561aaf26bd1c71ef7688b5a8b96a87d938db8d9774454ab635011fc7f + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.13.0, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.16.7, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.18.9, @babel/helper-plugin-utils@npm:^7.19.0, @babel/helper-plugin-utils@npm:^7.20.2, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": + version: 7.20.2 + resolution: "@babel/helper-plugin-utils@npm:7.20.2" + checksum: bf4de040e57b7ddff36ea599e963c391eb246d5a95207bb9ef3e33073c451bcc0821e3a9cc08dfede862a6dcc110d7e6e7d9a483482f852be358c5b60add499c + languageName: node + linkType: hard + +"@babel/helper-remap-async-to-generator@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/helper-remap-async-to-generator@npm:7.18.9" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-wrap-function": ^7.18.9 + "@babel/types": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: e6b2a906bdb3ec40d9cee7b7f8d02a561334603a0c57406a37c77d301ca77412ff33f2cef9d89421d7c3b1359604d613c596621a2ff22129612213198c5d1527 + languageName: node + linkType: hard + +"@babel/helper-replace-supers@npm:^7.18.6, @babel/helper-replace-supers@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/helper-replace-supers@npm:7.20.7" + dependencies: + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-member-expression-to-functions": ^7.20.7 + "@babel/helper-optimise-call-expression": ^7.18.6 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.20.7 + "@babel/types": ^7.20.7 + checksum: 6d44965bdc24b61df89d8d92e3b86afe48d6a5932d7c8c059fb8bf53b9cf2845ed627e8261fac9b369b9a4dd1621e8e60a19f19902dc27e005f254d7a8cbffda + languageName: node + linkType: hard + +"@babel/helper-simple-access@npm:^7.20.2": + version: 7.20.2 + resolution: "@babel/helper-simple-access@npm:7.20.2" + dependencies: + "@babel/types": ^7.20.2 + checksum: 79cea28155536c74b37839748caea534bc413fac8c512e6101e9eecfe83f670db77bc782bdb41114caecbb1e2a73007ff6015d6a5ce58cae5363b8c5bd2dcee9 + languageName: node + linkType: hard + +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.20.0": + version: 7.20.0 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.20.0" + dependencies: + "@babel/types": ^7.20.0 + checksum: 8529fb760ffbc3efc22ec5a079039fae65f40a90e9986642a85c1727aabdf6a79929546412f6210593970d2f97041f73bdd316e481d61110d6edcac1f97670a9 + languageName: node + linkType: hard + +"@babel/helper-split-export-declaration@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-split-export-declaration@npm:7.18.6" + dependencies: + "@babel/types": ^7.18.6 + checksum: 1335b510a9aefcbf60d89648e622715774e56040d72302dc5e176c8d837c9ab81414ccfa9ed771a9f98da7192579bb12ab7a95948bfdc69b03b4a882b3983e48 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.19.4": + version: 7.19.4 + resolution: "@babel/helper-string-parser@npm:7.19.4" + checksum: e20c81582e75df2a020a1c547376668a6e1e1c2ca535a6b7abb25b83d5536c99c0d113184bbe87c1a26e923a9bb0c6e5279fca8db6bd609cd3499fafafc01598 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1": + version: 7.19.1 + resolution: "@babel/helper-validator-identifier@npm:7.19.1" + checksum: f978ecfea840f65b64ab9e17fac380625a45f4fe1361eeb29867fcfd1c9eaa72abd7023f2f40ac3168587d7e5153660d16cfccb352a557be2efd347a051b4b20 + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-validator-option@npm:7.18.6" + checksum: 7a1452725b87e6b0d26e8a981ad1e19a24d3bb8b17fb25d1254d6d1f3f2f2efd675135417d44f704ea4dd88f854e7a0a31967322dcb3e06fa80fc4fec71853a5 + languageName: node + linkType: hard + +"@babel/helper-wrap-function@npm:^7.18.9": + version: 7.20.5 + resolution: "@babel/helper-wrap-function@npm:7.20.5" + dependencies: + "@babel/helper-function-name": ^7.19.0 + "@babel/template": ^7.18.10 + "@babel/traverse": ^7.20.5 + "@babel/types": ^7.20.5 + checksum: b5ea154778f6dbeb3cb9917933ea364f8f643aa79665c51f4a4b903bc451b3d18a738ab9952bdb43a81647f301a9be305bfcf02f2222b1235197e52c525703d6 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.12.5, @babel/helpers@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/helpers@npm:7.20.7" + dependencies: + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.20.7 + "@babel/types": ^7.20.7 + checksum: 3b84879d243c64c7ce5abf2f1a845236f443a3c70beb5897075641a9a1deaa841697b0aeaf9963c471a7e817ca4bed8a8af7677cc3d65904eb7bdffed3e8bcf9 + languageName: node + linkType: hard + +"@babel/highlight@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/highlight@npm:7.18.6" + dependencies: + "@babel/helper-validator-identifier": ^7.18.6 + chalk: ^2.0.0 + js-tokens: ^4.0.0 + checksum: a6a6928d25099ef04c337fcbb829fab8059bb67d31ac37212efd611bdbe247d0e71a5096c4524272cb56399f40251fac57c025e42d3bc924db0183a6435a60ac + languageName: node + linkType: hard + +"@babel/node@npm:^7.16.8": + version: 7.20.7 + resolution: "@babel/node@npm:7.20.7" + dependencies: + "@babel/register": ^7.18.9 + commander: ^4.0.1 + core-js: ^3.26.0 + node-environment-flags: ^1.0.5 + regenerator-runtime: ^0.13.11 + v8flags: ^3.1.1 + peerDependencies: + "@babel/core": ^7.0.0-0 + bin: + babel-node: ./bin/babel-node.js + checksum: c0d0917bc8bf5bdce46020eede2848c8e0696c6f0495523ce92d43542dbdf1271f50abbd89e184d5dbc4581278bf8c8e2987b505670f4a31b2cadc0e073b0d7e + languageName: node + linkType: hard + +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.12.11, @babel/parser@npm:^7.12.7, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/parser@npm:7.20.7" + bin: + parser: ./bin/babel-parser.js + checksum: fd75ef65d5b98b88f8cfa14b72909473ddb783d536399a8a911eff4b3a5022b71d12725ddf6ee796de7b3d2243ce33b991efaab3921a28ece91668c7887dce18 + languageName: node + linkType: hard + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: be2cccfc101824428a860f8c71d2cd118a691a9ace5525197f3f0cba19a522006dc4f870405beece836452353076ac687aefda20d9d1491ea72ce51179057988 + languageName: node + linkType: hard + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.18.9": + version: 7.20.7 + resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 + "@babel/plugin-proposal-optional-chaining": ^7.20.7 + peerDependencies: + "@babel/core": ^7.13.0 + checksum: afdbed7555bec6f338cb46a6e8b39c7620bc0fce0f530d15c5e49a6eef103607600346b3f35f6bc32b7c9930564e801d7f0a000ecb9b44ff628156f894606cfb + languageName: node + linkType: hard + +"@babel/plugin-proposal-async-generator-functions@npm:^7.20.1": + version: 7.20.7 + resolution: "@babel/plugin-proposal-async-generator-functions@npm:7.20.7" + dependencies: + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-remap-async-to-generator": ^7.18.9 + "@babel/plugin-syntax-async-generators": ^7.8.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 0f4bc01805704ae4840536acc9888c50a32250e9188d025063bd17fe77ed171a12361c3dc83ce99664dcd73aec612accb8da95b0d8b825c854931b2860c0bfb5 + languageName: node + linkType: hard + +"@babel/plugin-proposal-class-properties@npm:^7.12.1, @babel/plugin-proposal-class-properties@npm:^7.17.12, @babel/plugin-proposal-class-properties@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-proposal-class-properties@npm:7.18.6" + dependencies: + "@babel/helper-create-class-features-plugin": ^7.18.6 + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: d5172ac6c9948cdfc387e94f3493ad86cb04035cf7433f86b5d358270b1b9752dc25e176db0c5d65892a246aca7bdb4636672e15626d7a7de4bc0bd0040168d9 + languageName: node + linkType: hard + +"@babel/plugin-proposal-class-static-block@npm:^7.18.6": + version: 7.20.7 + resolution: "@babel/plugin-proposal-class-static-block@npm:7.20.7" + dependencies: + "@babel/helper-create-class-features-plugin": ^7.20.7 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/plugin-syntax-class-static-block": ^7.14.5 + peerDependencies: + "@babel/core": ^7.12.0 + checksum: 57a47a77a2d3e2506b8eed14f47bb3d495e834ae9bcbc7681f3011dcdf720533fbc9605b61c8711efeded0065ea059f6a2acca708fbc6262a52f284a0328f443 + languageName: node + linkType: hard + +"@babel/plugin-proposal-decorators@npm:^7.12.12, @babel/plugin-proposal-decorators@npm:^7.17.12": + version: 7.20.7 + resolution: "@babel/plugin-proposal-decorators@npm:7.20.7" + dependencies: + "@babel/helper-create-class-features-plugin": ^7.20.7 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-replace-supers": ^7.20.7 + "@babel/helper-split-export-declaration": ^7.18.6 + "@babel/plugin-syntax-decorators": ^7.19.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 4bc78a05063970c4b0611cf6d7601a1a4df0ce861a8417aa2f8a1d4fabc51c75f3a07a66e00b7f59099885b9dda0cfbfd471a52693fce53d4a66d5698530c964 + languageName: node + linkType: hard + +"@babel/plugin-proposal-dynamic-import@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-proposal-dynamic-import@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + "@babel/plugin-syntax-dynamic-import": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 99be9865edfd65a46afb97d877ea247a8e881b4d0246a1ea0adf6db04c92f4f0959bd2f6f706d73248a2a7167c34f2464c4863137ddb94deadc5c7cc8bfc3e72 + languageName: node + linkType: hard + +"@babel/plugin-proposal-export-default-from@npm:^7.12.1": + version: 7.18.10 + resolution: "@babel/plugin-proposal-export-default-from@npm:7.18.10" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + "@babel/plugin-syntax-export-default-from": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 9ac33f8784e7a6ad0e74dabc5f45ff3aff06b57154e83f3e7fa867c45653820ec4796bf87af886bfebbf4d0762fa9efa8bfa03f7485710bbdca1c595bf698a58 + languageName: node + linkType: hard + +"@babel/plugin-proposal-export-namespace-from@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/plugin-proposal-export-namespace-from@npm:7.18.9" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + "@babel/plugin-syntax-export-namespace-from": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: b90346bd3628ebd44138d0628a5aba1e6b11748893fb48e87008cac30f3bc7cd3161362e49433156737350318174164436357a66fbbfdbe952606b460bd8a0e4 + languageName: node + linkType: hard + +"@babel/plugin-proposal-json-strings@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-proposal-json-strings@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + "@babel/plugin-syntax-json-strings": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 83f2ce41262a538ee43450044b9b0de320002473e4849421a7318c0500f9b0385c03d228f1be777ad71fd358aef13392e3551f0be52b5c423b0c34f7c9e5a06d + languageName: node + linkType: hard + +"@babel/plugin-proposal-logical-assignment-operators@npm:^7.18.9": + version: 7.20.7 + resolution: "@babel/plugin-proposal-logical-assignment-operators@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 436c1ee9f983813fc52788980a7231414351bd34d80b16b83bddb09115386292fe4912cc6d172304eabbaf0c4813625331b9b5bc798acb0e8925cf0d2b394d4d + languageName: node + linkType: hard + +"@babel/plugin-proposal-nullish-coalescing-operator@npm:^7.12.1, @babel/plugin-proposal-nullish-coalescing-operator@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-proposal-nullish-coalescing-operator@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: f6629158196ee9f16295d16db75825092ef543f8b98f4dfdd516e642a0430c7b1d69319ee676d35485d9b86a53ade6de0b883490d44de6d4336d38cdeccbe0bf + languageName: node + linkType: hard + +"@babel/plugin-proposal-numeric-separator@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-proposal-numeric-separator@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + "@babel/plugin-syntax-numeric-separator": ^7.10.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: a83a65c6ec0d2293d830e9db61406d246f22d8ea03583d68460cb1b6330c6699320acce1b45f66ba3c357830720e49267e3d99f95088be457c66e6450fbfe3fa + languageName: node + linkType: hard + +"@babel/plugin-proposal-object-rest-spread@npm:7.12.1": + version: 7.12.1 + resolution: "@babel/plugin-proposal-object-rest-spread@npm:7.12.1" + dependencies: + "@babel/helper-plugin-utils": ^7.10.4 + "@babel/plugin-syntax-object-rest-spread": ^7.8.0 + "@babel/plugin-transform-parameters": ^7.12.1 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: f773d59ead8b056b646d585e95d610cca2f0aeaa2eeaad74b3eb9e25821b06f27e361dd0aac9a088a10c22fee1ead8863f82a2be073e28eb04ca9a330a00941e + languageName: node + linkType: hard + +"@babel/plugin-proposal-object-rest-spread@npm:^7.12.1, @babel/plugin-proposal-object-rest-spread@npm:^7.20.2": + version: 7.20.7 + resolution: "@babel/plugin-proposal-object-rest-spread@npm:7.20.7" + dependencies: + "@babel/compat-data": ^7.20.5 + "@babel/helper-compilation-targets": ^7.20.7 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/plugin-syntax-object-rest-spread": ^7.8.3 + "@babel/plugin-transform-parameters": ^7.20.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: b9818749bb49d8095df64c45db682448d04743d96722984cbfd375733b2585c26d807f84b4fdb28474f2d614be6a6ffe3d96ffb121840e9e5345b2ccc0438bd8 + languageName: node + linkType: hard + +"@babel/plugin-proposal-optional-catch-binding@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-proposal-optional-catch-binding@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + "@babel/plugin-syntax-optional-catch-binding": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: ab20153d9e95e0b73004fdf86b6a2d219be2a0ace9ca76cd9eccddb680c913fec173bca54d761b1bc6044edde0a53811f3e515908c3b16d2d81cfec1e2e17391 + languageName: node + linkType: hard + +"@babel/plugin-proposal-optional-chaining@npm:^7.12.7, @babel/plugin-proposal-optional-chaining@npm:^7.18.9, @babel/plugin-proposal-optional-chaining@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/plugin-proposal-optional-chaining@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 + "@babel/plugin-syntax-optional-chaining": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 8aa2b9691a61e9780f05b5fc247a9b2944fa0f7841c575b459631cd72a828c4d8062bd12c60859409b4219198c291954e3a03bc570587235f6123728a23cc3ab + languageName: node + linkType: hard + +"@babel/plugin-proposal-private-methods@npm:^7.10.4, @babel/plugin-proposal-private-methods@npm:^7.12.1, @babel/plugin-proposal-private-methods@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-proposal-private-methods@npm:7.18.6" + dependencies: + "@babel/helper-create-class-features-plugin": ^7.18.6 + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 1c273d0ec3d49d0fe80bd754ec0191016e5b3ab4fb1e162ac0c014e9d3c1517a5d973afbf8b6dc9f9c98a8605c79e5f9e8b5ee158a4313fa68d1ff7b02084b6a + languageName: node + linkType: hard + +"@babel/plugin-proposal-private-property-in-object@npm:^7.12.1, @babel/plugin-proposal-private-property-in-object@npm:^7.18.6": + version: 7.20.5 + resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.20.5" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-create-class-features-plugin": ^7.20.5 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/plugin-syntax-private-property-in-object": ^7.14.5 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 1788a19b305f06db40448af5725d626562b1ba6bdf1de04fb07a75f595dd1c9463649b8fecd32953f80ebff31a71dad97d58bf5a616abaa317195d39c21c0cff + languageName: node + linkType: hard + +"@babel/plugin-proposal-unicode-property-regex@npm:^7.18.6, @babel/plugin-proposal-unicode-property-regex@npm:^7.4.4": + version: 7.18.6 + resolution: "@babel/plugin-proposal-unicode-property-regex@npm:7.18.6" + dependencies: + "@babel/helper-create-regexp-features-plugin": ^7.18.6 + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: c68feae57d9b1f4d98ecc2da63bda1993980deb509ccb08f6eace712ece8081032eb6532c304524b544c2dd577e2f9c2fe5c5bfd73d1955c946300def6fc7493 + languageName: node + linkType: hard + +"@babel/plugin-syntax-async-generators@npm:^7.8.4": + version: 7.8.4 + resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: d13efb282838481348c71073b6be6245b35d4f2f964a8f71e4174f235009f929ef7613df25f8d2338e2d3e44bc4265a9f8638c6aaa136d7a61fe95985f9725c8 + languageName: node + linkType: hard + +"@babel/plugin-syntax-bigint@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-bigint@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 686891b81af2bc74c39013655da368a480f17dd237bf9fbc32048e5865cb706d5a8f65438030da535b332b1d6b22feba336da8fa931f663b6b34e13147d12dde + languageName: node + linkType: hard + +"@babel/plugin-syntax-class-properties@npm:^7.12.13, @babel/plugin-syntax-class-properties@npm:^7.8.3": + version: 7.12.13 + resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" + dependencies: + "@babel/helper-plugin-utils": ^7.12.13 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 95168fa186416195280b1264fb18afcdcdcea780b3515537b766cb90de6ce042d42dd6a204a39002f794ae5845b02afb0fd4861a3308a861204a55e68310a120 + languageName: node + linkType: hard + +"@babel/plugin-syntax-class-static-block@npm:^7.14.5": + version: 7.14.5 + resolution: "@babel/plugin-syntax-class-static-block@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": ^7.14.5 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 4464bf9115f4a2d02ce1454411baf9cfb665af1da53709c5c56953e5e2913745b0fcce82982a00463d6facbdd93445c691024e310b91431a1e2f024b158f6371 + languageName: node + linkType: hard + +"@babel/plugin-syntax-decorators@npm:^7.19.0": + version: 7.19.0 + resolution: "@babel/plugin-syntax-decorators@npm:7.19.0" + dependencies: + "@babel/helper-plugin-utils": ^7.19.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 5b3fb71592fe91c68f13dbe7285471adb144583dd80f2274954250501e3f362ebfdab482f8886857f03c8b970c428a572ccbc3a59e2439f6ab92ec0d91c3874b + languageName: node + linkType: hard + +"@babel/plugin-syntax-dynamic-import@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-dynamic-import@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 9c50927bf71adf63f60c75370e2335879402648f468d0172bc912e303c6a3876927d8eb35807331b57f415392732ed05ab9b42c68ac30a936813ab549e0246c5 + languageName: node + linkType: hard + +"@babel/plugin-syntax-export-default-from@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-syntax-export-default-from@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 3858471e0d571ff611ff48950e98463356de6ba89f7bdfb039efd759b856c86bc47bbd153c4cef45e938c4fda38e754099f1b7af1bcdf9ab4065e6b85be8d8b6 + languageName: node + linkType: hard + +"@babel/plugin-syntax-export-namespace-from@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-export-namespace-from@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 5100d658ba563829700cd8d001ddc09f4c0187b1a13de300d729c5b3e87503f75a6d6c99c1794182f7f1a9f546ee009df4f15a0ce36376e206ed0012fa7cdc24 + languageName: node + linkType: hard + +"@babel/plugin-syntax-flow@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-syntax-flow@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 9c78c35fac8d31f30a21f30c2cd37961750f0acaf5f1fa5569a7795cd268a90d8c05aafa8015cc0ca2a554ab1348529cf49e2689b2bc5dbbd8bab31b89a30274 + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-assertions@npm:^7.20.0": + version: 7.20.0 + resolution: "@babel/plugin-syntax-import-assertions@npm:7.20.0" + dependencies: + "@babel/helper-plugin-utils": ^7.19.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 0ac0176984ad799b39264070007737c514ea95e4b3c3c515ecddef958629abcd3c8e8810fd60fb63de5a8f3f7022dd2c7af7580b819a9207acc372c8b8ec878e + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-meta@npm:^7.8.3": + version: 7.10.4 + resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": ^7.10.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 0b08b5e4c3128523d8e346f8cfc86824f0da2697b1be12d71af50a31aff7a56ceb873ed28779121051475010c28d6146a6bfea8518b150b71eeb4e46190172ee + languageName: node + linkType: hard + +"@babel/plugin-syntax-json-strings@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: e98f31b2ec406c57757d115aac81d0336e8434101c224edd9a5c93cefa53faf63eacc69f3138960c8b25401315af03df37f68d316c151c4b933136716ed6906e + languageName: node + linkType: hard + +"@babel/plugin-syntax-jsx@npm:7.12.1": + version: 7.12.1 + resolution: "@babel/plugin-syntax-jsx@npm:7.12.1" + dependencies: + "@babel/helper-plugin-utils": ^7.10.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 11d435f9e4e71c0f00e5bc295b40747c2c42341b7f38ddc5f8ac41d49ddfa247514dbe91932fa3dabd65581b4c7a9fe5b3d1c2b285e5ca32f4e5296cc185d40c + languageName: node + linkType: hard + +"@babel/plugin-syntax-jsx@npm:^7.17.12, @babel/plugin-syntax-jsx@npm:^7.18.6, @babel/plugin-syntax-jsx@npm:^7.7.2": + version: 7.18.6 + resolution: "@babel/plugin-syntax-jsx@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: d6d88b16e727bfe75c6ad6674bf7171bd5b2007ebab3f785eff96a98889cc2dd9d9b05a9ad8a265e04e67eddee81d63fcade27db033bb5aa5cc73f45cc450d6d + languageName: node + linkType: hard + +"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4, @babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": + version: 7.10.4 + resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": ^7.10.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 2594cfbe29411ad5bc2ad4058de7b2f6a8c5b86eda525a993959438615479e59c012c14aec979e538d60a584a1a799b60d1b8942c3b18468cb9d99b8fd34cd0b + languageName: node + linkType: hard + +"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 2024fbb1162899094cfc81152449b12bd0cc7053c6d4bda8ac2852545c87d0a851b1b72ed9560673cbf3ef6248257262c3c04aabf73117215c1b9cc7dd2542ce + languageName: node + linkType: hard + +"@babel/plugin-syntax-numeric-separator@npm:^7.10.4, @babel/plugin-syntax-numeric-separator@npm:^7.8.3": + version: 7.10.4 + resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": ^7.10.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: c55a82b3113480942c6aa2fcbe976ff9caa74b7b1109ff4369641dfbc88d1da348aceb3c31b6ed311c84d1e7c479440b961906c735d0ab494f688bf2fd5b9bb9 + languageName: node + linkType: hard + +"@babel/plugin-syntax-object-rest-spread@npm:7.8.3, @babel/plugin-syntax-object-rest-spread@npm:^7.8.0, @babel/plugin-syntax-object-rest-spread@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: ee1eab52ea6437e3101a0a7018b0da698545230015fc8ab129d292980ec6dff94d265e9e90070e8ae5fed42f08f1622c14c94552c77bcac784b37f503a82ff26 + languageName: node + linkType: hard + +"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 27e2493ab67a8ea6d693af1287f7e9acec206d1213ff107a928e85e173741e1d594196f99fec50e9dde404b09164f39dec5864c767212154ffe1caa6af0bc5af + languageName: node + linkType: hard + +"@babel/plugin-syntax-optional-chaining@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 46edddf2faa6ebf94147b8e8540dfc60a5ab718e2de4d01b2c0bdf250a4d642c2bd47cbcbb739febcb2bf75514dbcefad3c52208787994b8d0f8822490f55e81 + languageName: node + linkType: hard + +"@babel/plugin-syntax-private-property-in-object@npm:^7.14.5": + version: 7.14.5 + resolution: "@babel/plugin-syntax-private-property-in-object@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": ^7.14.5 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 69822772561706c87f0a65bc92d0772cea74d6bc0911537904a676d5ff496a6d3ac4e05a166d8125fce4a16605bace141afc3611074e170a994e66e5397787f3 + languageName: node + linkType: hard + +"@babel/plugin-syntax-top-level-await@npm:^7.14.5, @babel/plugin-syntax-top-level-await@npm:^7.8.3": + version: 7.14.5 + resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": ^7.14.5 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 14bf6e65d5bc1231ffa9def5f0ef30b19b51c218fcecaa78cd1bdf7939dfdf23f90336080b7f5196916368e399934ce5d581492d8292b46a2fb569d8b2da106f + languageName: node + linkType: hard + +"@babel/plugin-syntax-typescript@npm:^7.20.0, @babel/plugin-syntax-typescript@npm:^7.7.2": + version: 7.20.0 + resolution: "@babel/plugin-syntax-typescript@npm:7.20.0" + dependencies: + "@babel/helper-plugin-utils": ^7.19.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: c57bb9b717b3b7324cc0c094d411bac23f6d78ed5e4e06fb89e3e8de37437e649c53440d8c29ecb3875f398ad1a9e8acc96e3af6b3802e83f7eab855de319e80 + languageName: node + linkType: hard + +"@babel/plugin-transform-arrow-functions@npm:^7.12.1, @babel/plugin-transform-arrow-functions@npm:^7.18.6": + version: 7.20.7 + resolution: "@babel/plugin-transform-arrow-functions@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 690fc85afd273049f87e917ab75915e0c0ef19f62633d7d1706a1126dcfac9571d244b5b4eed9b64d6320a8560e8a6e17cf6ea38f4ecc6010e889953c1509b25 + languageName: node + linkType: hard + +"@babel/plugin-transform-async-to-generator@npm:^7.18.6": + version: 7.20.7 + resolution: "@babel/plugin-transform-async-to-generator@npm:7.20.7" + dependencies: + "@babel/helper-module-imports": ^7.18.6 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-remap-async-to-generator": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: c98caeafbffbdb40fd5d9d4c7a835d624ba1ada814e8e675d99a9c83bd40780ab6a52e3b873e81dc7ce045a3990427073e634f07cc2f2681d780faee0717d7e9 + languageName: node + linkType: hard + +"@babel/plugin-transform-block-scoped-functions@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 22e81b52320e6f3929110241d91499a7535d6834b86e8871470f9946b42e093fafc79e1eae4ede376e7c5fe84c5dc5e9fdbe55ff4039b323b5958167202f02e0 + languageName: node + linkType: hard + +"@babel/plugin-transform-block-scoping@npm:^7.12.12, @babel/plugin-transform-block-scoping@npm:^7.20.2": + version: 7.20.11 + resolution: "@babel/plugin-transform-block-scoping@npm:7.20.11" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 3840c342c5ef6c53c750bf3801c30b3770b016516b4589d164e227688ed2dd0aa86496ac340b0735b9fa0cee30ff5338f1e291b2a91df5cce17e585298674e8b + languageName: node + linkType: hard + +"@babel/plugin-transform-classes@npm:^7.12.1, @babel/plugin-transform-classes@npm:^7.20.2": + version: 7.20.7 + resolution: "@babel/plugin-transform-classes@npm:7.20.7" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-compilation-targets": ^7.20.7 + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-function-name": ^7.19.0 + "@babel/helper-optimise-call-expression": ^7.18.6 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-replace-supers": ^7.20.7 + "@babel/helper-split-export-declaration": ^7.18.6 + globals: ^11.1.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 920d6861b366f5abe66106c178c0ae15386b52b3bd95284db632482c217ce7883187603f4014be62dfeada1a70f6370ea6c6ed152e02b81c52a8febbb7c1e20b + languageName: node + linkType: hard + +"@babel/plugin-transform-computed-properties@npm:^7.18.9": + version: 7.20.7 + resolution: "@babel/plugin-transform-computed-properties@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/template": ^7.20.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 849c11bac3600d8afa9f3a440fc721cdf2b719480b9a0b230849092fa400099ba1e91328e168860a2ca4d2843a94ece57a894b47468aaeb83df27bb82aae5d07 + languageName: node + linkType: hard + +"@babel/plugin-transform-destructuring@npm:^7.12.1, @babel/plugin-transform-destructuring@npm:^7.20.2": + version: 7.20.7 + resolution: "@babel/plugin-transform-destructuring@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 747889ec3dfcd992b63d55faf598f152822df75cc6da299789695ef8dbe520c78a2f146152d646afd2805f9abe1c13045fd1b3ab97be5e0d6901c73ea4209c44 + languageName: node + linkType: hard + +"@babel/plugin-transform-dotall-regex@npm:^7.18.6, @babel/plugin-transform-dotall-regex@npm:^7.4.4": + version: 7.18.6 + resolution: "@babel/plugin-transform-dotall-regex@npm:7.18.6" + dependencies: + "@babel/helper-create-regexp-features-plugin": ^7.18.6 + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: cf4c3751e603996f3da0b2060c3aab3c95e267cfc702a95d025b2e9684b66ed73a318949524fad5048515f4a5142629f2c0bd3dbb83558bdbab4008486b8d9a0 + languageName: node + linkType: hard + +"@babel/plugin-transform-duplicate-keys@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/plugin-transform-duplicate-keys@npm:7.18.9" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: dfb7f7e66c0c862d205fe8f8b87f7ac174549c56937a5186b6e6cf85358ce257115fec0aa55e78fde53e5132d5aae9383e81aba8a4b70faa0e9fb64e3a66ca96 + languageName: node + linkType: hard + +"@babel/plugin-transform-exponentiation-operator@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.18.6" + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor": ^7.18.6 + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 96d300ca3e55dbc98609df2d70c2b343202faca307b3152a04eab77600f6b1dc00b5b90fc3999cb9592922583c83ecbb92217e317d7c08602ca0db87a26eeed3 + languageName: node + linkType: hard + +"@babel/plugin-transform-flow-strip-types@npm:^7.18.6": + version: 7.19.0 + resolution: "@babel/plugin-transform-flow-strip-types@npm:7.19.0" + dependencies: + "@babel/helper-plugin-utils": ^7.19.0 + "@babel/plugin-syntax-flow": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 9b330e14f9e570c33ad7c99d3b250cfa8272df542dcb0cdbd8ad3c62668b651c8c0ca643063ad68a7bebb73b492cc3335a6e6276a48b82f949565c58d614be26 + languageName: node + linkType: hard + +"@babel/plugin-transform-for-of@npm:^7.12.1, @babel/plugin-transform-for-of@npm:^7.18.8": + version: 7.18.8 + resolution: "@babel/plugin-transform-for-of@npm:7.18.8" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 37708653d9ac69af31f0f5d0abebd726d6b92ba630beed8fea8e1538f035b2877abc0013f26f400ebc23af459fb8e629c83847818614d9fcca086fb5bcd35c4d + languageName: node + linkType: hard + +"@babel/plugin-transform-function-name@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/plugin-transform-function-name@npm:7.18.9" + dependencies: + "@babel/helper-compilation-targets": ^7.18.9 + "@babel/helper-function-name": ^7.18.9 + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 95100707fe00b3e388c059700fbdccf83c2cdf3b7fec8035cdd6c01dd80a1d9efb2821fec1357a62533ebbcbb3f6c361666866a3818486f1172e62f2b692de64 + languageName: node + linkType: hard + +"@babel/plugin-transform-literals@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/plugin-transform-literals@npm:7.18.9" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 7b0d59920dd5a1679a2214dde0d785ce7c0ed75cb6d46b618e7822dcd11fb347be2abb99444019262b6561369b85b95ab96603357773a75126b3d1c4c289b822 + languageName: node + linkType: hard + +"@babel/plugin-transform-member-expression-literals@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-member-expression-literals@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 346e5ac45b77f1e58a9b1686eb16c75cca40cbc1de9836b814fbe8ae0767f7d4a0fec5b88fcf26a5e3455af9e33fd3c6424e4f2661d04e38123d80e022ce6e6f + languageName: node + linkType: hard + +"@babel/plugin-transform-modules-amd@npm:^7.19.6": + version: 7.20.11 + resolution: "@babel/plugin-transform-modules-amd@npm:7.20.11" + dependencies: + "@babel/helper-module-transforms": ^7.20.11 + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 327077cc746d2ef14d0792a970058d9b7170ff480c1d1d7acf874ef7cfeae0c680e86a45896ea27066e9ebdd82dc2be09d321385eef1e0b4255659d75ea2e008 + languageName: node + linkType: hard + +"@babel/plugin-transform-modules-commonjs@npm:^7.19.6": + version: 7.20.11 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.20.11" + dependencies: + "@babel/helper-module-transforms": ^7.20.11 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-simple-access": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: f3a3281c252a978255076ff7274e4ac1ec252e0db4b3d73122c278ce9fd8318179fc804638ce726870146fa0845e2559711453ce7a391dc2a792d96dc0f6b04c + languageName: node + linkType: hard + +"@babel/plugin-transform-modules-systemjs@npm:^7.19.6": + version: 7.20.11 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.20.11" + dependencies: + "@babel/helper-hoist-variables": ^7.18.6 + "@babel/helper-module-transforms": ^7.20.11 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-validator-identifier": ^7.19.1 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 1843b2044b711765581d6130ea7901afde6e6f5af4e4219ab675033a090f4dacb6656bfada8f211a2cd9bbae256c7f4bd0b8613b750e56674feee5252de1ad76 + languageName: node + linkType: hard + +"@babel/plugin-transform-modules-umd@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-modules-umd@npm:7.18.6" + dependencies: + "@babel/helper-module-transforms": ^7.18.6 + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: e3e99aef95a3faa15bc2398a919475c9130b783ee0f2439e1622fe73466c9821a5f74f72a46bb25e84906b650b467d73b43269c8b8c13372e97d3f2d96d109c7 + languageName: node + linkType: hard + +"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.19.1": + version: 7.20.5 + resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.20.5" + dependencies: + "@babel/helper-create-regexp-features-plugin": ^7.20.5 + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 0ca94f716c70f96a0d5e79211ab7e7614efc9aa2940e6009086b16136f2558ae27b7acf9f88bc0a241882ca3192cc66c477fa0eb1cfdda54974ffc2b8846d3e4 + languageName: node + linkType: hard + +"@babel/plugin-transform-new-target@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-new-target@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: ea9186087b72d0adff0b9e7ef5769cb7806bc4755ce7b75c323d65053d453fd801a64f97b65c033d89370866e76e8d526dd186acede2fdcd2667fa056b11149b + languageName: node + linkType: hard + +"@babel/plugin-transform-object-super@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-object-super@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + "@babel/helper-replace-supers": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 44a1f5a62c6821a4653e23a38a61bed494138a0f12945a1d8b55ff7b83904e7c5615f4ebda8268c6ea877d1ec6b00f7c92a08cf93f4f77dc777e71145342aaf5 + languageName: node + linkType: hard + +"@babel/plugin-transform-parameters@npm:^7.12.1, @babel/plugin-transform-parameters@npm:^7.20.1, @babel/plugin-transform-parameters@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/plugin-transform-parameters@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: faef20aaebfbbbcd00bffbe75b20c4953852843c0f22eee0177194025e0980fd8c435655a6178ecfdd4f4b3b8677dde41aa6c32394f290b2526519074dbbe33a + languageName: node + linkType: hard + +"@babel/plugin-transform-property-literals@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-property-literals@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: b76239098127ee39031db54e4eb9e55cb8a616abc0fc6abba4b22d00e443ec00d7aaa58c7cdef45b224b5e017905fc39a5e1802577a82396acabb32fe9cff7dd + languageName: node + linkType: hard + +"@babel/plugin-transform-react-constant-elements@npm:^7.18.12": + version: 7.20.2 + resolution: "@babel/plugin-transform-react-constant-elements@npm:7.20.2" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: c04003cf5061f0e0c642febc7bf2c88b94e3f43767f54b55c087006e706a6ffe732d1021dbb332b80cc900f55830d4b9acf189885e4f782f5c1e6c6ec292e1e5 + languageName: node + linkType: hard + +"@babel/plugin-transform-react-display-name@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-react-display-name@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 2c5f44f653604b800145ebad74e11ad6ec06bf96741b69a404e1409afb36abe34b27621b64ddba138813ad957fb8130dc15bd60ecd3b58380115edcccbdeb2ab + languageName: node + linkType: hard + +"@babel/plugin-transform-react-jsx-development@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-react-jsx-development@npm:7.18.6" + dependencies: + "@babel/plugin-transform-react-jsx": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 95b37b76754288bb4de28a04f709306686ff80da57937421df9a520f9c2d8b59a2327962a8fd3bb109857790732d3cc767d86d106866e62521cee22d29f721df + languageName: node + linkType: hard + +"@babel/plugin-transform-react-jsx@npm:^7.12.12, @babel/plugin-transform-react-jsx@npm:^7.18.6": + version: 7.20.7 + resolution: "@babel/plugin-transform-react-jsx@npm:7.20.7" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-module-imports": ^7.18.6 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/plugin-syntax-jsx": ^7.18.6 + "@babel/types": ^7.20.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 30b997aec0be9d6b882ce873f1d9d94556f4768012567629b284072173ff0d6631e1cbd507c060f2e5f67ebee3fe4c11e100871a342dbf818875a67e61e0096b + languageName: node + linkType: hard + +"@babel/plugin-transform-react-pure-annotations@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.18.6" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: e830b5d66c9c48ef287d84e453a495ad43cee9abf484f0d4d8e6ec601d0d019ffe031cdb086872f08a2de848cad34d9d193a49c36c9f5c61aff48158f40459ec + languageName: node + linkType: hard + +"@babel/plugin-transform-regenerator@npm:^7.18.6": + version: 7.20.5 + resolution: "@babel/plugin-transform-regenerator@npm:7.20.5" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + regenerator-transform: ^0.15.1 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 4f390ec2687d34d11a8154244d246704be19eeb2ac50b38730ba02ee9adde8a4a4110c79cab0d0778ab3e023034b26fe8745752a9a7624d613e2267b86906b64 + languageName: node + linkType: hard + +"@babel/plugin-transform-reserved-words@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-reserved-words@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: cbd6a86743c270a1e2a7caa19f6da22112c9dfa28fe08aea46ec9cb79fc1bc48df6b5b12819ae0e53227d4ca4adaee13f80216c03fff3082d3a88c55b4cddeba + languageName: node + linkType: hard + +"@babel/plugin-transform-runtime@npm:^7.17.12": + version: 7.19.6 + resolution: "@babel/plugin-transform-runtime@npm:7.19.6" + dependencies: + "@babel/helper-module-imports": ^7.18.6 + "@babel/helper-plugin-utils": ^7.19.0 + babel-plugin-polyfill-corejs2: ^0.3.3 + babel-plugin-polyfill-corejs3: ^0.6.0 + babel-plugin-polyfill-regenerator: ^0.4.1 + semver: ^6.3.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 39c1a7a6421dbd00d599082b4c38ed1b3ba5844af1249d3860d7de7ce7e6451641ee0fc5b237af4a02f5cd77c7896a2b50799d0f90b1b30b6d2cd92061b2fdff + languageName: node + linkType: hard + +"@babel/plugin-transform-shorthand-properties@npm:^7.12.1, @babel/plugin-transform-shorthand-properties@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-shorthand-properties@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: e60e02dca182d6ec0e7b571d7e99a0528743692fb911826600374b77832922bf7c4b154194d4fe4a0e8a15c2acad3ea44dbaff5189aaeab59124e4c7ee0b8c30 + languageName: node + linkType: hard + +"@babel/plugin-transform-spread@npm:^7.12.1, @babel/plugin-transform-spread@npm:^7.19.0": + version: 7.20.7 + resolution: "@babel/plugin-transform-spread@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 6abd206942e1fd322791707e7e15aa823f9829d8965facbed4abb0f85d51355d0bb21ac8d7184dea22de3bb5853e807ae6b5b74c621507b912c345cbce4a37b4 + languageName: node + linkType: hard + +"@babel/plugin-transform-sticky-regex@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-sticky-regex@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: efbcf8f0acdac5757cce8d79c0259e3e5142cf3c782d71675802e97709dfb3cbc3dc08202c3ea950ddc23c8f74cae7c334aa05ec095e3cc6d642fa8b30d8e31c + languageName: node + linkType: hard + +"@babel/plugin-transform-template-literals@npm:^7.12.1, @babel/plugin-transform-template-literals@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/plugin-transform-template-literals@npm:7.18.9" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: d1a5e55ed8c3b1186fbba2a7b3e9d880cb3987b846376f51a73216a8894b9c9d6f6c6e2d3cadb17d76f2477552db5383d817169d5b92fcf08ee0fa5b88213c15 + languageName: node + linkType: hard + +"@babel/plugin-transform-typeof-symbol@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/plugin-transform-typeof-symbol@npm:7.18.9" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: c42e00635aa9d1c597d339c9023e0f9bfa3cd7af55c00cb8a6461036102b0facdcd3059456d4fee0a63675aeecca62fc84ee01f28b23139c6ae03e6d61c86906 + languageName: node + linkType: hard + +"@babel/plugin-transform-typescript@npm:^7.18.6": + version: 7.20.7 + resolution: "@babel/plugin-transform-typescript@npm:7.20.7" + dependencies: + "@babel/helper-create-class-features-plugin": ^7.20.7 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/plugin-syntax-typescript": ^7.20.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 751c5f60ed38d6eeb10c3bae6e5215d58eda4405ec2da4c8b6be1126a41dee985e4119543faedb4e3b5c06785664f0eaef3f69b2e2db84449b6cecde8a97f822 + languageName: node + linkType: hard + +"@babel/plugin-transform-unicode-escapes@npm:^7.18.10": + version: 7.18.10 + resolution: "@babel/plugin-transform-unicode-escapes@npm:7.18.10" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 1587c3497549a4ad1b75d5b63f1d6ced839d4078dc7df3b5df362c8669f3e9cbed975d5c55632bf8c574847d8df03553851e1b85d1e81a568fdfd2466fcd4198 + languageName: node + linkType: hard + +"@babel/plugin-transform-unicode-regex@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-unicode-regex@npm:7.18.6" + dependencies: + "@babel/helper-create-regexp-features-plugin": ^7.18.6 + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 2f71b5b79df7f8de81c52011d64203b7021f7d23af2470782aef8e8a3be6ca3a208679de8078a12e707342dde1175e5ab44abf8f63c219c997e147118d356dea + languageName: node + linkType: hard + +"@babel/preset-env@npm:^7.11.0, @babel/preset-env@npm:^7.12.11, @babel/preset-env@npm:^7.17.12, @babel/preset-env@npm:^7.19.4": + version: 7.20.2 + resolution: "@babel/preset-env@npm:7.20.2" + dependencies: + "@babel/compat-data": ^7.20.1 + "@babel/helper-compilation-targets": ^7.20.0 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-validator-option": ^7.18.6 + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ^7.18.6 + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ^7.18.9 + "@babel/plugin-proposal-async-generator-functions": ^7.20.1 + "@babel/plugin-proposal-class-properties": ^7.18.6 + "@babel/plugin-proposal-class-static-block": ^7.18.6 + "@babel/plugin-proposal-dynamic-import": ^7.18.6 + "@babel/plugin-proposal-export-namespace-from": ^7.18.9 + "@babel/plugin-proposal-json-strings": ^7.18.6 + "@babel/plugin-proposal-logical-assignment-operators": ^7.18.9 + "@babel/plugin-proposal-nullish-coalescing-operator": ^7.18.6 + "@babel/plugin-proposal-numeric-separator": ^7.18.6 + "@babel/plugin-proposal-object-rest-spread": ^7.20.2 + "@babel/plugin-proposal-optional-catch-binding": ^7.18.6 + "@babel/plugin-proposal-optional-chaining": ^7.18.9 + "@babel/plugin-proposal-private-methods": ^7.18.6 + "@babel/plugin-proposal-private-property-in-object": ^7.18.6 + "@babel/plugin-proposal-unicode-property-regex": ^7.18.6 + "@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.20.0 + "@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-transform-arrow-functions": ^7.18.6 + "@babel/plugin-transform-async-to-generator": ^7.18.6 + "@babel/plugin-transform-block-scoped-functions": ^7.18.6 + "@babel/plugin-transform-block-scoping": ^7.20.2 + "@babel/plugin-transform-classes": ^7.20.2 + "@babel/plugin-transform-computed-properties": ^7.18.9 + "@babel/plugin-transform-destructuring": ^7.20.2 + "@babel/plugin-transform-dotall-regex": ^7.18.6 + "@babel/plugin-transform-duplicate-keys": ^7.18.9 + "@babel/plugin-transform-exponentiation-operator": ^7.18.6 + "@babel/plugin-transform-for-of": ^7.18.8 + "@babel/plugin-transform-function-name": ^7.18.9 + "@babel/plugin-transform-literals": ^7.18.9 + "@babel/plugin-transform-member-expression-literals": ^7.18.6 + "@babel/plugin-transform-modules-amd": ^7.19.6 + "@babel/plugin-transform-modules-commonjs": ^7.19.6 + "@babel/plugin-transform-modules-systemjs": ^7.19.6 + "@babel/plugin-transform-modules-umd": ^7.18.6 + "@babel/plugin-transform-named-capturing-groups-regex": ^7.19.1 + "@babel/plugin-transform-new-target": ^7.18.6 + "@babel/plugin-transform-object-super": ^7.18.6 + "@babel/plugin-transform-parameters": ^7.20.1 + "@babel/plugin-transform-property-literals": ^7.18.6 + "@babel/plugin-transform-regenerator": ^7.18.6 + "@babel/plugin-transform-reserved-words": ^7.18.6 + "@babel/plugin-transform-shorthand-properties": ^7.18.6 + "@babel/plugin-transform-spread": ^7.19.0 + "@babel/plugin-transform-sticky-regex": ^7.18.6 + "@babel/plugin-transform-template-literals": ^7.18.9 + "@babel/plugin-transform-typeof-symbol": ^7.18.9 + "@babel/plugin-transform-unicode-escapes": ^7.18.10 + "@babel/plugin-transform-unicode-regex": ^7.18.6 + "@babel/preset-modules": ^0.1.5 + "@babel/types": ^7.20.2 + babel-plugin-polyfill-corejs2: ^0.3.3 + babel-plugin-polyfill-corejs3: ^0.6.0 + babel-plugin-polyfill-regenerator: ^0.4.1 + core-js-compat: ^3.25.1 + semver: ^6.3.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 8e4c86d9acf2557eaca4c55ffe69bb76f30d675f2576e5e1b872ef671acec7c80df3759d77cd33ff934d5a49f26950c4d9e63718c4c3295455bc2df88788d7ad + languageName: node + linkType: hard + +"@babel/preset-flow@npm:^7.10.4, @babel/preset-flow@npm:^7.12.1": + version: 7.18.6 + resolution: "@babel/preset-flow@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + "@babel/helper-validator-option": ^7.18.6 + "@babel/plugin-transform-flow-strip-types": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 9fcc9b4a4ade598af0253e22e56a32431502fd92f208305a340a9f35e3ca43c3b472399c55d615092dbdab9d86fe2fc9b866926fdb30ffd9704b77d1b2e2c85d + languageName: node + linkType: hard + +"@babel/preset-modules@npm:^0.1.5": + version: 0.1.5 + resolution: "@babel/preset-modules@npm:0.1.5" + dependencies: + "@babel/helper-plugin-utils": ^7.0.0 + "@babel/plugin-proposal-unicode-property-regex": ^7.4.4 + "@babel/plugin-transform-dotall-regex": ^7.4.4 + "@babel/types": ^7.4.4 + esutils: ^2.0.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: bd90081d96b746c1940dc1ce056dee06ed3a128d20936aee1d1795199f789f9a61293ef738343ae10c6d53970c17285d5e147a945dded35423aacb75083b8a89 + languageName: node + linkType: hard + +"@babel/preset-react@npm:^7.12.10, @babel/preset-react@npm:^7.17.12, @babel/preset-react@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/preset-react@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + "@babel/helper-validator-option": ^7.18.6 + "@babel/plugin-transform-react-display-name": ^7.18.6 + "@babel/plugin-transform-react-jsx": ^7.18.6 + "@babel/plugin-transform-react-jsx-development": ^7.18.6 + "@babel/plugin-transform-react-pure-annotations": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 19a5b238809e85875488e06f415fde175852ff2361f29ff60233053e3c9914afbaf8befe80cf636d5a49821e8b13067e60c85636deb8e1d6ac543643f5ef2559 + languageName: node + linkType: hard + +"@babel/preset-typescript@npm:^7.12.7, @babel/preset-typescript@npm:^7.17.12, @babel/preset-typescript@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/preset-typescript@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + "@babel/helper-validator-option": ^7.18.6 + "@babel/plugin-transform-typescript": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 2314e0c1fd5d188ca4bdc35f8ab1e9caec3c662673949cf16ae5b29ed27855a5f354a19b736b50e54e099d580f825e39b58db7fd8f8e2c2d38eb22c9fa5910ea + languageName: node + linkType: hard + +"@babel/register@npm:^7.12.1, @babel/register@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/register@npm:7.18.9" + 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 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: b19c1445adf202732a2e0d554749257da22e56f0fc159709200d962413fbd4e7bd1d684222e60c08a2b8ad8fe511d8699fbc978d92816953fc9cbb6cbcc40d63 + languageName: node + linkType: hard + +"@babel/runtime@npm:7.7.2": + version: 7.7.2 + resolution: "@babel/runtime@npm:7.7.2" + dependencies: + regenerator-runtime: ^0.13.2 + checksum: a1bab397bf1ae0117a7ac1fb67885833368b065425960ed7e5d8856efad5fc46765ae27d82d3d6ede2ade35c901d56745e1ae8ad75b36ca309a9dfd0fca5a712 + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.10.4, @babel/runtime@npm:^7.10.5, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.17.9, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.5.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": + version: 7.20.7 + resolution: "@babel/runtime@npm:7.20.7" + dependencies: + regenerator-runtime: ^0.13.11 + checksum: 60ff1a1452d0f88b766211604610b92d5e063d7024150b6dab87af238e2a6634c01eff4add9e14b4335ced966640af34196ee4cd63a0c181c2d4edd387795c0f + languageName: node + linkType: hard + +"@babel/runtime@npm:~7.5.4": + version: 7.5.5 + resolution: "@babel/runtime@npm:7.5.5" + dependencies: + regenerator-runtime: ^0.13.2 + checksum: ee5789200681462dc668ce4a0720914b16b872b4f71b79931a343fd4114154a3ec64e7e93ec38f634b7e3f54767005883402af86d60eb5172e897cae275ef8da + languageName: node + linkType: hard + +"@babel/template@npm:^7.12.7, @babel/template@npm:^7.18.10, @babel/template@npm:^7.20.7, @babel/template@npm:^7.3.3": + version: 7.20.7 + resolution: "@babel/template@npm:7.20.7" + dependencies: + "@babel/code-frame": ^7.18.6 + "@babel/parser": ^7.20.7 + "@babel/types": ^7.20.7 + checksum: 1c6dcf9ac92769e6ab5e3d9048975537d26ab00b869646462ab4583d45e419c01db5144715ec0d70548835a3098c5d5416148c4a0b996a95e8e0b9dc8d042dd3 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.1.0, @babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.12.9, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.20.10, @babel/traverse@npm:^7.20.12, @babel/traverse@npm:^7.20.5, @babel/traverse@npm:^7.20.7, @babel/traverse@npm:^7.7.2": + version: 7.20.12 + resolution: "@babel/traverse@npm:7.20.12" + dependencies: + "@babel/code-frame": ^7.18.6 + "@babel/generator": ^7.20.7 + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-function-name": ^7.19.0 + "@babel/helper-hoist-variables": ^7.18.6 + "@babel/helper-split-export-declaration": ^7.18.6 + "@babel/parser": ^7.20.7 + "@babel/types": ^7.20.7 + debug: ^4.1.0 + globals: ^11.1.0 + checksum: 66e6b25785f9eec1b488dfb45118fc2c3f51e78455600fa60fc5441ea7f66467d6bfe56e935257d3f34a19d7eca353348b2d971c7671e020fee3664356b38e0c + languageName: node + linkType: hard + +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.1.0, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.7, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.19.0, @babel/types@npm:^7.2.0, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.2, @babel/types@npm:^7.20.5, @babel/types@npm:^7.20.7, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": + version: 7.20.7 + resolution: "@babel/types@npm:7.20.7" + dependencies: + "@babel/helper-string-parser": ^7.19.4 + "@babel/helper-validator-identifier": ^7.19.1 + to-fast-properties: ^2.0.0 + checksum: df0061f306bd95389604075ba5a88e984a801635c70c77b3b6ae8ab44675064b9ef4088c6c78dbf786a28efc662ad37f9c09f8658ba44c12cb8dd6f450a8bde7 + languageName: node + linkType: hard + +"@base2/pretty-print-object@npm:1.0.1": + version: 1.0.1 + resolution: "@base2/pretty-print-object@npm:1.0.1" + checksum: 98f77ea185a30c854897feb2a68fe51be8451a1a0b531bac61a5dd67033926a0ba0c9be6e0f819b8cb72ca349b3e7648bf81c12fd21df0b45219c75a3a75784b + languageName: node + linkType: hard + +"@bcoe/v8-coverage@npm:^0.2.3": + version: 0.2.3 + resolution: "@bcoe/v8-coverage@npm:0.2.3" + checksum: 6b80ae4cb3db53f486da2dc63b6e190a74c8c3cca16bb2733f234a0b6a9382b09b146488ae08e2b22cf00f6c83e20f3e040a2f7894f05c045c946d6a090b1d52 + languageName: node + linkType: hard + +"@cnakazawa/watch@npm:^1.0.3": + version: 1.0.4 + resolution: "@cnakazawa/watch@npm:1.0.4" + dependencies: + exec-sh: ^0.3.2 + minimist: ^1.2.0 + bin: + watch: cli.js + checksum: 8678b6f582bdc5ffe59c0d45c2ad21f4ea1d162ec7ddb32e85078fca481c26958f27bcdef6007b8e9a066da090ccf9d31e1753f8de1e5f32466a04227d70dc31 + languageName: node + linkType: hard + +"@colors/colors@npm:1.5.0": + version: 1.5.0 + resolution: "@colors/colors@npm:1.5.0" + checksum: eb42729851adca56d19a08e48d5a1e95efd2a32c55ae0323de8119052be0510d4b7a1611f2abcbf28c044a6c11e6b7d38f99fccdad7429300c37a8ea5fb95b44 + languageName: node + linkType: hard + +"@cspotcode/source-map-support@npm:^0.8.0": + version: 0.8.1 + resolution: "@cspotcode/source-map-support@npm:0.8.1" + dependencies: + "@jridgewell/trace-mapping": 0.3.9 + checksum: 05c5368c13b662ee4c122c7bfbe5dc0b613416672a829f3e78bc49a357a197e0218d6e74e7c66cfcd04e15a179acab080bd3c69658c9fbefd0e1ccd950a07fc6 + languageName: node + linkType: hard + +"@csstools/selector-specificity@npm:^2.0.0": + version: 2.0.2 + resolution: "@csstools/selector-specificity@npm:2.0.2" + peerDependencies: + postcss: ^8.2 + postcss-selector-parser: ^6.0.10 + checksum: c0553a1293d3bbd5e59ad1426c8cd812350589cc3188c4b1190ec7949192c7bc5b67c8b862b8f5402e226da5f474dbdbbf1453b49ae65550f33db616a0bfd982 + languageName: node + linkType: hard + +"@cypress/request@npm:^2.88.10": + version: 2.88.11 + resolution: "@cypress/request@npm:2.88.11" + dependencies: + aws-sign2: ~0.7.0 + aws4: ^1.8.0 + caseless: ~0.12.0 + combined-stream: ~1.0.6 + extend: ~3.0.2 + forever-agent: ~0.6.1 + form-data: ~2.3.2 + http-signature: ~1.3.6 + is-typedarray: ~1.0.0 + isstream: ~0.1.2 + json-stringify-safe: ~5.0.1 + mime-types: ~2.1.19 + performance-now: ^2.1.0 + qs: ~6.10.3 + safe-buffer: ^5.1.2 + tough-cookie: ~2.5.0 + tunnel-agent: ^0.6.0 + uuid: ^8.3.2 + checksum: d1d47fee93ce599cdbaaab611ce8aa41514cc384415c2b55f6df444aa496552835e2971e8f7a931024b4887bd178def115bceecec5e5a552e1d67aca105dfaff + languageName: node + linkType: hard + +"@cypress/xvfb@npm:^1.2.4": + version: 1.2.4 + resolution: "@cypress/xvfb@npm:1.2.4" + dependencies: + debug: ^3.1.0 + lodash.once: ^4.1.1 + checksum: 1bf6224b244f6093033d77f04f6bef719280542656de063cf8ac3f38957b62aa633e6918af0b9673a8bf0123b42a850db51d9729a3ae3da885ac179bc7fc1d26 + languageName: node + linkType: hard + +"@dabh/diagnostics@npm:^2.0.2": + version: 2.0.3 + resolution: "@dabh/diagnostics@npm:2.0.3" + dependencies: + colorspace: 1.1.x + enabled: 2.0.x + kuler: ^2.0.0 + checksum: a5133df8492802465ed01f2f0a5784585241a1030c362d54a602ed1839816d6c93d71dde05cf2ddb4fd0796238c19774406bd62fa2564b637907b495f52425fe + languageName: node + linkType: hard + +"@design-systems/utils@npm:2.12.0": + version: 2.12.0 + resolution: "@design-systems/utils@npm:2.12.0" + dependencies: + "@babel/runtime": ^7.11.2 + clsx: ^1.0.4 + focus-lock: ^0.8.0 + react-merge-refs: ^1.0.0 + peerDependencies: + "@types/react": "*" + react: ">= 16.8.6" + react-dom: ">= 16.8.6" + checksum: 1b729cd9817ac08d8c18245f9fb0e8a6dd1511602b9d0c1b82f137be60642a5649497fd6c7b938770f2666c90bfc172c86fa575867da570165ae63db898d2029 + languageName: node + linkType: hard + +"@devtools-ds/object-inspector@npm:^1.1.2": + version: 1.2.1 + resolution: "@devtools-ds/object-inspector@npm:1.2.1" + dependencies: + "@babel/runtime": 7.7.2 + "@devtools-ds/object-parser": ^1.2.1 + "@devtools-ds/themes": ^1.2.1 + "@devtools-ds/tree": ^1.2.1 + clsx: 1.1.0 + peerDependencies: + react: ">= 16.8.6" + checksum: e7248151d66b65f2481caecb765895ded2759264b85cd99c4780e975028f0752ea170842c464aebf2911ed9bfee5fef7f13cf5091db17960b86767cd3e416725 + languageName: node + linkType: hard + +"@devtools-ds/object-parser@npm:^1.2.1": + version: 1.2.1 + resolution: "@devtools-ds/object-parser@npm:1.2.1" + dependencies: + "@babel/runtime": ~7.5.4 + checksum: 3feefaa4fac05e178c3dea990d51d86d29fe5f18210ab686799e4f85a6c38a3f9482c4d1d743be36c9e1277d5e1f13fce7e823f88f9c80fd8965559fd3562ad8 + languageName: node + linkType: hard + +"@devtools-ds/themes@npm:^1.2.1": + version: 1.2.1 + resolution: "@devtools-ds/themes@npm:1.2.1" + dependencies: + "@babel/runtime": ~7.5.4 + "@design-systems/utils": 2.12.0 + clsx: 1.1.0 + peerDependencies: + react: ">= 16.8.6" + checksum: f984ca35398eab2e09f286017f358f98986cd876b4392231b0687152f0c40e280085bb98ecb739859c5e52fc5d2892ece5ec5aa6eab96e51edb96789377358d7 + languageName: node + linkType: hard + +"@devtools-ds/tree@npm:^1.2.1": + version: 1.2.1 + resolution: "@devtools-ds/tree@npm:1.2.1" + dependencies: + "@babel/runtime": 7.7.2 + "@devtools-ds/themes": ^1.2.1 + clsx: 1.1.0 + peerDependencies: + react: ">= 16.8.6" + checksum: 072f867e8efe953e63c7421bafe366f1ca9ca2b3f59afdc42b6a11d399c69cdde0ad9b50f368efae0e1239d03a1164e7874771d23752970c22e8247718ddf471 + languageName: node + linkType: hard + +"@discoveryjs/json-ext@npm:^0.5.0, @discoveryjs/json-ext@npm:^0.5.3": + version: 0.5.7 + resolution: "@discoveryjs/json-ext@npm:0.5.7" + checksum: e10f1b02b78e4812646ddf289b7d9f2cb567d336c363b266bd50cd223cf3de7c2c74018d91cd2613041568397ef3a4a2b500aba588c6e5bd78c38374ba68f38c + languageName: node + linkType: hard + +"@emotion/babel-plugin@npm:^11.10.5": + version: 11.10.5 + resolution: "@emotion/babel-plugin@npm:11.10.5" + dependencies: + "@babel/helper-module-imports": ^7.16.7 + "@babel/plugin-syntax-jsx": ^7.17.12 + "@babel/runtime": ^7.18.3 + "@emotion/hash": ^0.9.0 + "@emotion/memoize": ^0.8.0 + "@emotion/serialize": ^1.1.1 + babel-plugin-macros: ^3.1.0 + convert-source-map: ^1.5.0 + escape-string-regexp: ^4.0.0 + find-root: ^1.1.0 + source-map: ^0.5.7 + stylis: 4.1.3 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: b877d089c07ad7e571f7d1b8393c21b8ab54dc24b4b7533827c00549a4fe5345af55869f57c139f7dec09615b93ca66195aa31023bbc5af89cf3ec85e80a2281 + languageName: node + linkType: hard + +"@emotion/cache@npm:^11.10.5, @emotion/cache@npm:^11.4.0": + version: 11.10.5 + resolution: "@emotion/cache@npm:11.10.5" + dependencies: + "@emotion/memoize": ^0.8.0 + "@emotion/sheet": ^1.2.1 + "@emotion/utils": ^1.2.0 + "@emotion/weak-memoize": ^0.3.0 + stylis: 4.1.3 + checksum: eeb6891ab04cf17ace0e175742550b97c30df777d6c5b145e91c4c9fbd783c29b4dabe12a8c786b78f37176313a8295c9b90c69d875e6caab5f7e4677a18be91 + languageName: node + linkType: hard + +"@emotion/hash@npm:^0.9.0": + version: 0.9.0 + resolution: "@emotion/hash@npm:0.9.0" + checksum: 0910d3e9ec46cc780f691c96fb6f6f67b4f080b50ecf4f441bc4b33b5906e28099f530a368fe0b31c6bad38a857ac44df3c36f8978be603789d71330ac01af12 + languageName: node + linkType: hard + +"@emotion/memoize@npm:^0.8.0": + version: 0.8.0 + resolution: "@emotion/memoize@npm:0.8.0" + checksum: 246087ec09b32b295af67a094253831f398aabd953d03d14f186acb8607ed2a755e944f5e20b5ccebb461f15c2e5ccbf8fe977bcf3be951cf10961c504e1e65b + languageName: node + linkType: hard + +"@emotion/react@npm:^11.8.1": + version: 11.10.5 + resolution: "@emotion/react@npm:11.10.5" + dependencies: + "@babel/runtime": ^7.18.3 + "@emotion/babel-plugin": ^11.10.5 + "@emotion/cache": ^11.10.5 + "@emotion/serialize": ^1.1.1 + "@emotion/use-insertion-effect-with-fallbacks": ^1.0.0 + "@emotion/utils": ^1.2.0 + "@emotion/weak-memoize": ^0.3.0 + hoist-non-react-statics: ^3.3.1 + peerDependencies: + "@babel/core": ^7.0.0 + react: ">=16.8.0" + peerDependenciesMeta: + "@babel/core": + optional: true + "@types/react": + optional: true + checksum: 911fdc54a44304e70e8f2721ba2c323695171856d5337c761ff5f952f2e651d54b1b5b68319573a0d9a5a847d13f622c2d951317176ea701607d349f8a9cd0f5 + languageName: node + linkType: hard + +"@emotion/serialize@npm:^1.1.1": + version: 1.1.1 + resolution: "@emotion/serialize@npm:1.1.1" + dependencies: + "@emotion/hash": ^0.9.0 + "@emotion/memoize": ^0.8.0 + "@emotion/unitless": ^0.8.0 + "@emotion/utils": ^1.2.0 + csstype: ^3.0.2 + checksum: ea353abbf530ede8b74fe4df30eb626f245f710ce0bfcb9d34e72630a1dede688fddf02b1143f33a1a4ef5b66b70715a3c1cd6a12ec43f5b585ed60d4f3e8712 + languageName: node + linkType: hard + +"@emotion/sheet@npm:^1.2.1": + version: 1.2.1 + resolution: "@emotion/sheet@npm:1.2.1" + checksum: 88268c00005d310df3ebb249b839ad0b234943da5a0cc614b232b9bd4ae600292dca9b0f61c45cde3a592c77459e880d77a2aa73af20ec3c0d579afccc3f71af + languageName: node + linkType: hard + +"@emotion/unitless@npm:^0.8.0": + version: 0.8.0 + resolution: "@emotion/unitless@npm:0.8.0" + checksum: 1f2cfb7c0ccb83c20b1c6d8d92a74a93da4b2a440f9a0d49ded08647faf299065a2ffde17e1335920fa10397b85f8635bbfe14f3cd29222a59ea81d978478072 + languageName: node + linkType: hard + +"@emotion/use-insertion-effect-with-fallbacks@npm:^1.0.0": + version: 1.0.0 + resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.0.0" + peerDependencies: + react: ">=16.8.0" + checksum: 0c5fbd36a4f416a5abaf428ba3dca6e79018c4c74016ecb4e3991a11cf8b5dbd306d7770fee09692971335e33f97e3b555deda595e5ae7831841505078bd07d7 + languageName: node + linkType: hard + +"@emotion/utils@npm:^1.2.0": + version: 1.2.0 + resolution: "@emotion/utils@npm:1.2.0" + checksum: 7051cec83bb49688549667484058d3a19a30001fa3692c23f7a2e727c05121f952854e1196feb9ece4fa36914705ebf474edba833a2178bdc133c654b5e3ca7d + languageName: node + linkType: hard + +"@emotion/weak-memoize@npm:^0.3.0": + version: 0.3.0 + resolution: "@emotion/weak-memoize@npm:0.3.0" + checksum: 1771687cc3b3280371de12698f1b78756c64654fc7d15ce76e1fb5d4adf9fd49d4411e41276bbfd5b521ef9cef647196aa9dca26f936c466fb80bf48491fa844 + languageName: node + linkType: hard + +"@eslint/eslintrc@npm:^1.4.1": + version: 1.4.1 + resolution: "@eslint/eslintrc@npm:1.4.1" + dependencies: + ajv: ^6.12.4 + debug: ^4.3.2 + espree: ^9.4.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 + checksum: 1030e1a4a355f8e4629e19d3d45448a05a8e65ecf49154bebc66599d038f155e830498437cbfc7246e8084adc1f814904f696c2461707cc8c73be961e2e8ae5a + languageName: node + linkType: hard + +"@floating-ui/core@npm:^1.0.5": + version: 1.1.0 + resolution: "@floating-ui/core@npm:1.1.0" + checksum: 427c95953f99c58647c3eee50d75770fddc19416cea152403010527135e8c88479352a8cbb4aeb09949cf283522861d815bf77dff5207b64dc8dd3b623bf57a3 + languageName: node + linkType: hard + +"@floating-ui/dom@npm:^1.0.1, @floating-ui/dom@npm:^1.1.0": + version: 1.1.0 + resolution: "@floating-ui/dom@npm:1.1.0" + dependencies: + "@floating-ui/core": ^1.0.5 + checksum: 8cae2d59f32bce8d742a414477ee62854affbcb7ae318598adebadec36d43c01dccaf5004d73a9dd26683d393bb69fb1e8181970b8d7939f529512ab1ad672d2 + languageName: node + linkType: hard + +"@floating-ui/react-dom-interactions@npm:^0.10.3": + version: 0.10.3 + resolution: "@floating-ui/react-dom-interactions@npm:0.10.3" + dependencies: + "@floating-ui/react-dom": ^1.0.0 + aria-hidden: ^1.1.3 + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 01e4dbb93bde171bea173fe4d1690f1945889931df8c8e4dc0b64c3591c1c2ab169b3eee6b1e640778d3fafe12fa7b25b463ffe8624101c9f4c75280f2627254 + languageName: node + linkType: hard + +"@floating-ui/react-dom@npm:^1.0.0": + version: 1.2.1 + resolution: "@floating-ui/react-dom@npm:1.2.1" + dependencies: + "@floating-ui/dom": ^1.1.0 + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 234d2a0fee7704db8f2cafb99b352be08270797e4874078ac6d639269a5ae35126f5ecc6dbebeea01041789dde0a9d6595e3442ffe83511603ad4ba39fcff861 + languageName: node + linkType: hard + +"@fluentui/react-component-event-listener@npm:~0.63.0": + version: 0.63.1 + resolution: "@fluentui/react-component-event-listener@npm:0.63.1" + dependencies: + "@babel/runtime": ^7.10.4 + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + react-dom: ^16.8.0 || ^17 || ^18 + checksum: 4305e02a24a3960b2e345aabe36964fe4e6ac5f403d81882545632c4bfc035d0b52804f1365bc027062e0bf0fa7d5c74088d75d7a3da33d3acc9f792faa699d2 + languageName: node + linkType: hard + +"@fluentui/react-component-ref@npm:~0.63.0": + version: 0.63.1 + resolution: "@fluentui/react-component-ref@npm:0.63.1" + dependencies: + "@babel/runtime": ^7.10.4 + react-is: ^16.6.3 + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + react-dom: ^16.8.0 || ^17 || ^18 + checksum: da9c46539bf1eb4b24056975614338c0472eb9fb4839ded752c78614e2c57b402e4d6422e04b8c2e753a1d0f12a341ab0510cdbf5e86a21be5b63b8680ad59ed + languageName: node + linkType: hard + +"@gar/promisify@npm:^1.0.1, @gar/promisify@npm:^1.1.3": + version: 1.1.3 + resolution: "@gar/promisify@npm:1.1.3" + checksum: 0b3c9958d3cd17f4add3574975e3115ae05dc7f1298a60810414b16f6f558c137b5fb3cd3905df380bacfd955ec13f67c1e6710cbb5c246a7e8d65a8289b2bff + languageName: node + linkType: hard + +"@humanwhocodes/config-array@npm:^0.11.8": + version: 0.11.8 + resolution: "@humanwhocodes/config-array@npm:0.11.8" + dependencies: + "@humanwhocodes/object-schema": ^1.2.1 + debug: ^4.1.1 + minimatch: ^3.0.5 + checksum: 441223496cc5ae3ae443e11e2ba05f03f6418d1e0233e3d160b027dda742d7a957fa9e1d56125d5829079419c797c13e1ae8ffe3454f268901ac18f68e0198f1 + languageName: node + linkType: hard + +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 + languageName: node + linkType: hard + +"@humanwhocodes/object-schema@npm:^1.2.1": + version: 1.2.1 + resolution: "@humanwhocodes/object-schema@npm:1.2.1" + checksum: c3c35fdb70c04a569278351c75553e293ae339684ed75895edc79facc7276e351115786946658d78133130c0cca80e57e2203bc07f8fa7fe7980300e8deef7db + languageName: node + linkType: hard + +"@iarna/cli@npm:^2.1.0": + version: 2.1.0 + resolution: "@iarna/cli@npm:2.1.0" + dependencies: + glob: ^7.1.2 + signal-exit: ^3.0.2 + checksum: ee8f2529105c15417cfcb3ce4ffc3ad9cffb96bbe0575b6dc945a1748959729831ece73f1ade1941e3ed480e1ab24d3ecd91770820288db8bfc842912e800c7d + languageName: node + linkType: hard + +"@istanbuljs/load-nyc-config@npm:^1.0.0": + version: 1.1.0 + resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" + 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 + checksum: dd2a8b094887da5a1a2339543a4933d06db2e63cbbc2e288eb6431bd832065df0c099d091b6a67436e71b7d6bf85f01ce7c15f9253b4cbebcc3b9a496165ba42 + languageName: node + linkType: hard + +"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a + languageName: node + linkType: hard + +"@jest/console@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/console@npm:29.3.1" + dependencies: + "@jest/types": ^29.3.1 + "@types/node": "*" + chalk: ^4.0.0 + jest-message-util: ^29.3.1 + jest-util: ^29.3.1 + slash: ^3.0.0 + checksum: f4773d50b6588eb44daffdf808891f32a827e3f99ae8cca74efb5a9fc117655e418e97e13fcbbf530062321fa940ba88d028e66d27bdf97e41231b2e5b114626 + languageName: node + linkType: hard + +"@jest/core@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/core@npm:29.3.1" + dependencies: + "@jest/console": ^29.3.1 + "@jest/reporters": ^29.3.1 + "@jest/test-result": ^29.3.1 + "@jest/transform": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/node": "*" + ansi-escapes: ^4.2.1 + chalk: ^4.0.0 + ci-info: ^3.2.0 + exit: ^0.1.2 + graceful-fs: ^4.2.9 + jest-changed-files: ^29.2.0 + jest-config: ^29.3.1 + jest-haste-map: ^29.3.1 + jest-message-util: ^29.3.1 + jest-regex-util: ^29.2.0 + jest-resolve: ^29.3.1 + jest-resolve-dependencies: ^29.3.1 + jest-runner: ^29.3.1 + jest-runtime: ^29.3.1 + jest-snapshot: ^29.3.1 + jest-util: ^29.3.1 + jest-validate: ^29.3.1 + jest-watcher: ^29.3.1 + micromatch: ^4.0.4 + pretty-format: ^29.3.1 + slash: ^3.0.0 + strip-ansi: ^6.0.0 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 06a930365498e9cf902537746090c6ce2d98c1c394d81a458f0a74c287a5321306cad25c1aec1f55222b9e3d514d7f8de6e8d44eea3bc6d63ae75b618eb473bc + languageName: node + linkType: hard + +"@jest/environment@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/environment@npm:29.3.1" + dependencies: + "@jest/fake-timers": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/node": "*" + jest-mock: ^29.3.1 + checksum: 46982c52649854e7766b8129a81a59fefefb898f853fe2a1394e72c5492a183f4e596eb91f3d4ba614a7117869ccf2c509ba190747c96085de4fa8300bb65226 + languageName: node + linkType: hard + +"@jest/expect-utils@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/expect-utils@npm:29.3.1" + dependencies: + jest-get-type: ^29.2.0 + checksum: dc58ff9c5c7e893c056f3560cb1445771dcc1555df0b5aeff4808c6425ca9b921eae5b4f92b433b89c401e445694f5484b352f06620bac9e7cb97b8f56ee3e21 + languageName: node + linkType: hard + +"@jest/expect@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/expect@npm:29.3.1" + dependencies: + expect: ^29.3.1 + jest-snapshot: ^29.3.1 + checksum: 705bdad3f5af7c87d252a26dab890ac7f560d53439b364b4260f68b2dba271464bd7de7cfe2b03db7abaccc409da4da338075f870a1c31a1b4770bab6e67c53f + languageName: node + linkType: hard + +"@jest/fake-timers@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/fake-timers@npm:29.3.1" + dependencies: + "@jest/types": ^29.3.1 + "@sinonjs/fake-timers": ^9.1.2 + "@types/node": "*" + jest-message-util: ^29.3.1 + jest-mock: ^29.3.1 + jest-util: ^29.3.1 + checksum: b2fe1ea5d8ff3aa9ef099550d5897adba0ed53f8971e134ad589a52346b1f6914df986ef5c847264f7446a7dcae66946f4107cb1087b630a447cf13ad334b59b + languageName: node + linkType: hard + +"@jest/globals@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/globals@npm:29.3.1" + dependencies: + "@jest/environment": ^29.3.1 + "@jest/expect": ^29.3.1 + "@jest/types": ^29.3.1 + jest-mock: ^29.3.1 + checksum: cda0fc6e1f8fd5a72f576c227db7a0b5ec47baa168e7aae0aca2b8f8d08d97b0c563a154460b96dcbaf3991584111a852ce783ceb66fc526cf440faa668b3893 + languageName: node + linkType: hard + +"@jest/reporters@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/reporters@npm:29.3.1" + dependencies: + "@bcoe/v8-coverage": ^0.2.3 + "@jest/console": ^29.3.1 + "@jest/test-result": ^29.3.1 + "@jest/transform": ^29.3.1 + "@jest/types": ^29.3.1 + "@jridgewell/trace-mapping": ^0.3.15 + "@types/node": "*" + chalk: ^4.0.0 + collect-v8-coverage: ^1.0.0 + exit: ^0.1.2 + glob: ^7.1.3 + graceful-fs: ^4.2.9 + istanbul-lib-coverage: ^3.0.0 + istanbul-lib-instrument: ^5.1.0 + istanbul-lib-report: ^3.0.0 + istanbul-lib-source-maps: ^4.0.0 + istanbul-reports: ^3.1.3 + jest-message-util: ^29.3.1 + jest-util: ^29.3.1 + jest-worker: ^29.3.1 + slash: ^3.0.0 + string-length: ^4.0.1 + strip-ansi: ^6.0.0 + v8-to-istanbul: ^9.0.1 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: c961d44d868d019f6a722e31370785ffe91f1746ee65337c5bb1c557cc053bfc0a54fc07d703c6d3d2ad16e87241f2d22ee59895161049fafd4f94f893cdb9bf + languageName: node + linkType: hard + +"@jest/schemas@npm:^29.0.0": + version: 29.0.0 + resolution: "@jest/schemas@npm:29.0.0" + dependencies: + "@sinclair/typebox": ^0.24.1 + checksum: 08c2f6b0237f52ab9448eb6633561ee1e499871082ac41a51b581e91571f6da317b4be0529307caf4cb3fd50798f7c096665db6bb2b5dde999a2c0c08b8775c9 + languageName: node + linkType: hard + +"@jest/source-map@npm:^29.2.0": + version: 29.2.0 + resolution: "@jest/source-map@npm:29.2.0" + dependencies: + "@jridgewell/trace-mapping": ^0.3.15 + callsites: ^3.0.0 + graceful-fs: ^4.2.9 + checksum: 6b63d7a1deb59f59fdd7c6f5700069401ec6228e788f3446a6f9ead2e4926c394e1d00eced3f77296a218089c0a173d2aeb5c7b9fd6205ddb160a9f28e644ea8 + languageName: node + linkType: hard + +"@jest/test-result@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/test-result@npm:29.3.1" + dependencies: + "@jest/console": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/istanbul-lib-coverage": ^2.0.0 + collect-v8-coverage: ^1.0.0 + checksum: 6433a278119d5cdca1f92d1727850c9092a816a95bd5f3efb86b413599f1281d3f4e44ce564e25428ee1759c46cf8916e86fe077c0d94026a4b9ca40cb6722ed + languageName: node + linkType: hard + +"@jest/test-sequencer@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/test-sequencer@npm:29.3.1" + dependencies: + "@jest/test-result": ^29.3.1 + graceful-fs: ^4.2.9 + jest-haste-map: ^29.3.1 + slash: ^3.0.0 + checksum: 30399c44fcbacbe2b538b720d4857f2edf5be29b7ce7ea9c1a4af87d7526b5a0896d379bcf7a61608f18f86732edfbade24ec3b7091f9e26bb4bd0fe8a68fb79 + languageName: node + linkType: hard + +"@jest/transform@npm:^26.6.2": + version: 26.6.2 + resolution: "@jest/transform@npm:26.6.2" + dependencies: + "@babel/core": ^7.1.0 + "@jest/types": ^26.6.2 + babel-plugin-istanbul: ^6.0.0 + chalk: ^4.0.0 + convert-source-map: ^1.4.0 + fast-json-stable-stringify: ^2.0.0 + graceful-fs: ^4.2.4 + jest-haste-map: ^26.6.2 + jest-regex-util: ^26.0.0 + jest-util: ^26.6.2 + micromatch: ^4.0.2 + pirates: ^4.0.1 + slash: ^3.0.0 + source-map: ^0.6.1 + write-file-atomic: ^3.0.0 + checksum: 1a1d636528d9b122b87b870633763c67f131533fce61e5db536dfbbea0bbfe8fe130daededb686ccc230389473a2b8ece5d0e1eaf380066d8902bde48579de31 + languageName: node + linkType: hard + +"@jest/transform@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/transform@npm:29.3.1" + dependencies: + "@babel/core": ^7.11.6 + "@jest/types": ^29.3.1 + "@jridgewell/trace-mapping": ^0.3.15 + 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.3.1 + jest-regex-util: ^29.2.0 + jest-util: ^29.3.1 + micromatch: ^4.0.4 + pirates: ^4.0.4 + slash: ^3.0.0 + write-file-atomic: ^4.0.1 + checksum: 5e43dea16b6985f7f28bb7f1d8f7c8e1a980dd3325265ef48e8bbc7ba02530e26094541693ac1fb8dd791b7615adf3ef0b537d60ee8fe8299b1ab84f445451e0 + languageName: node + linkType: hard + +"@jest/types@npm:^26.6.2": + version: 26.6.2 + resolution: "@jest/types@npm:26.6.2" + dependencies: + "@types/istanbul-lib-coverage": ^2.0.0 + "@types/istanbul-reports": ^3.0.0 + "@types/node": "*" + "@types/yargs": ^15.0.0 + chalk: ^4.0.0 + checksum: 5b9b957f38a002895eb04bbb8c3dda6fccce8e2551f3f44b02f1f43063a78e8bedce73cd4330b53ede00ae005de5cd805982fbb2ec6ab9feacf96344240d5db2 + languageName: node + linkType: hard + +"@jest/types@npm:^27.5.1": + version: 27.5.1 + resolution: "@jest/types@npm:27.5.1" + dependencies: + "@types/istanbul-lib-coverage": ^2.0.0 + "@types/istanbul-reports": ^3.0.0 + "@types/node": "*" + "@types/yargs": ^16.0.0 + chalk: ^4.0.0 + checksum: 4598b302398db0eb77168b75a6c58148ea02cc9b9f21c5d1bbe985c1c9257110a5653cf7b901c3cab87fba231e3fed83633687f1c0903b4bc6939ab2a8452504 + languageName: node + linkType: hard + +"@jest/types@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/types@npm:29.3.1" + dependencies: + "@jest/schemas": ^29.0.0 + "@types/istanbul-lib-coverage": ^2.0.0 + "@types/istanbul-reports": ^3.0.0 + "@types/node": "*" + "@types/yargs": ^17.0.8 + chalk: ^4.0.0 + checksum: c1ae1a66fbe403c82578d55cc5a061bffce2426f830c9365d0ab033f48580f3beb378631efe85e420709ff898fca6f7dd8fca9eb412dfed3d88a80c422065188 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.1.0": + version: 0.1.1 + resolution: "@jridgewell/gen-mapping@npm:0.1.1" + dependencies: + "@jridgewell/set-array": ^1.0.0 + "@jridgewell/sourcemap-codec": ^1.4.10 + checksum: 3d784d87aee604bc4d48d3d9e547e0466d9f4a432cd9b3a4f3e55d104313bf3945e7e970cd5fa767bc145df11f1d568a01ab6659696be41f0ed2a817f3b583a3 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.2": + version: 0.3.2 + resolution: "@jridgewell/gen-mapping@npm:0.3.2" + dependencies: + "@jridgewell/set-array": ^1.0.1 + "@jridgewell/sourcemap-codec": ^1.4.10 + "@jridgewell/trace-mapping": ^0.3.9 + checksum: 82685c8735c63fe388badee45e2970a6bc83eed1c84d46d8652863bafeca22a6c6cc15812f5999a4535366f4668ccc9ba6d5c67dfb72e846fa8a063806f10afd + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:3.1.0, @jridgewell/resolve-uri@npm:^3.0.3": + version: 3.1.0 + resolution: "@jridgewell/resolve-uri@npm:3.1.0" + checksum: 78055e2526108331126366572045355051a930f017d1904a4f753d3f4acee8d92a14854948095626f6163cffc24ea4e3efa30637417bb866b84743dec7ef6fd9 + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.0.0, @jridgewell/set-array@npm:^1.0.1": + version: 1.1.2 + resolution: "@jridgewell/set-array@npm:1.1.2" + checksum: bc7ab4c4c00470de4e7562ecac3c0c84f53e7ee8a711e546d67c47da7febe7c45cd67d4d84ee3c9b2c05ae8e872656cdded8a707a283d30bd54fbc65aef821ab + languageName: node + linkType: hard + +"@jridgewell/source-map@npm:^0.3.2": + version: 0.3.2 + resolution: "@jridgewell/source-map@npm:0.3.2" + dependencies: + "@jridgewell/gen-mapping": ^0.3.0 + "@jridgewell/trace-mapping": ^0.3.9 + checksum: 1540da323456878281c8e03fc4edc444ea151aa441eb38a43d84d39df8fec9446e375202cd999b54637f4627e42e2a38b3ab07195e5e49616fc6b7eee1b7119f + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10": + version: 1.4.14 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" + checksum: 3fbaff1387c1338b097eeb6ff92890d7838f7de0dde259e4983763b44540bfd5ca6a1f7644dc8ad003a57f7e80670d5b96a8402f1386ba9aee074743ae9bad51 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:0.3.9": + version: 0.3.9 + resolution: "@jridgewell/trace-mapping@npm:0.3.9" + dependencies: + "@jridgewell/resolve-uri": ^3.0.3 + "@jridgewell/sourcemap-codec": ^1.4.10 + checksum: fa425b606d7c7ee5bfa6a31a7b050dd5814b4082f318e0e4190f991902181b4330f43f4805db1dd4f2433fd0ed9cc7a7b9c2683f1deeab1df1b0a98b1e24055b + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.14, @jridgewell/trace-mapping@npm:^0.3.15, @jridgewell/trace-mapping@npm:^0.3.8, @jridgewell/trace-mapping@npm:^0.3.9": + version: 0.3.17 + resolution: "@jridgewell/trace-mapping@npm:0.3.17" + dependencies: + "@jridgewell/resolve-uri": 3.1.0 + "@jridgewell/sourcemap-codec": 1.4.14 + checksum: 40b65fcbdd7cc5a60dbe0a2780b6670ebbc1a31c96e43833e0bf2fee0773b1ba5137ab7d137b28fc3f215567bd5f9d06b7b30634ba15636c13bd8a863c20ae9a + languageName: node + linkType: hard + +"@leichtgewicht/ip-codec@npm:^2.0.1": + version: 2.0.4 + resolution: "@leichtgewicht/ip-codec@npm:2.0.4" + checksum: 3b0d8844d1d47c0a5ed7267c2964886adad3a642b85d06f95c148eeefd80cdabbd6aa0d63ccde8239967a2e9b6bb734a16bd57e1fda3d16bf56d50a7e7ec131b + languageName: node + linkType: hard + +"@mdx-js/mdx@npm:^1.6.22": + version: 1.6.22 + resolution: "@mdx-js/mdx@npm:1.6.22" + dependencies: + "@babel/core": 7.12.9 + "@babel/plugin-syntax-jsx": 7.12.1 + "@babel/plugin-syntax-object-rest-spread": 7.8.3 + "@mdx-js/util": 1.6.22 + babel-plugin-apply-mdx-type-prop: 1.6.22 + babel-plugin-extract-import-names: 1.6.22 + camelcase-css: 2.0.1 + detab: 2.0.4 + hast-util-raw: 6.0.1 + lodash.uniq: 4.5.0 + mdast-util-to-hast: 10.0.1 + remark-footnotes: 2.0.0 + remark-mdx: 1.6.22 + remark-parse: 8.0.3 + remark-squeeze-paragraphs: 4.0.0 + style-to-object: 0.3.0 + unified: 9.2.0 + unist-builder: 2.0.3 + unist-util-visit: 2.0.3 + checksum: 7f4c38911fc269159834240d3cc9279839145022a992bd61657530750c7ab5d0f674e8d6319b6e2e426d0e1adc6cc5ab1876e57548208783d8a3d1b8ef73ebca + languageName: node + linkType: hard + +"@mdx-js/react@npm:^1.6.22": + version: 1.6.22 + resolution: "@mdx-js/react@npm:1.6.22" + peerDependencies: + react: ^16.13.1 || ^17.0.0 + checksum: ed896671ffab04c1f11cdba45bfb2786acff58cd0b749b0a13d9b7a7022ac75cc036bec067ca946e6540e2934727e0ba8bf174e4ae10c916f30cda6aecac8992 + languageName: node + linkType: hard + +"@mdx-js/util@npm:1.6.22": + version: 1.6.22 + resolution: "@mdx-js/util@npm:1.6.22" + checksum: 2ee8da6afea0f42297ea31f52b1d50d228744d2895cce7cc9571b7d5ce97c7c96037c80b6dbcded9caa8099c9a994eda62980099eabe1c000aaa792816c66f10 + languageName: node + linkType: hard + +"@mrmlnc/readdir-enhanced@npm:^2.2.1": + version: 2.2.1 + resolution: "@mrmlnc/readdir-enhanced@npm:2.2.1" + dependencies: + call-me-maybe: ^1.0.1 + glob-to-regexp: ^0.3.0 + checksum: 01840f3c85e9a7cd0ed5e038cc00e7518809b9edda950598e22b1c9804832e39a75707aaa6eb0b023e72182a85e00041c7a01483e425b16257bd3d5e4c788d86 + languageName: node + linkType: hard + +"@nicolo-ribaudo/chokidar-2@npm:2.1.8-no-fsevents.3": + version: 2.1.8-no-fsevents.3 + resolution: "@nicolo-ribaudo/chokidar-2@npm:2.1.8-no-fsevents.3" + checksum: 27dcabaa0c9a29b3a60217bd3fff87a22cb43ed77863da570c6828e4d0b8f1c6ee52582cd3d439275a2b1f2051005e648ed866b981f2a03b61c645b7e4806ba7 + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": 2.0.5 + run-parallel: ^1.1.9 + checksum: 732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:^1.1.2": + version: 1.1.3 + resolution: "@nodelib/fs.stat@npm:1.1.3" + checksum: dc28ccae626e817a61b1544285b0f86c4e94a4a23db777c2949f78866ec57b1e1ccd5554bc3ed8e965df0646b1019e184315d32e98428c15eef7409974b17598 + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": 2.1.5 + fastq: ^1.6.0 + checksum: db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^1.0.0": + version: 1.1.1 + resolution: "@npmcli/fs@npm:1.1.1" + dependencies: + "@gar/promisify": ^1.0.1 + semver: ^7.3.5 + checksum: 4143c317a7542af9054018b71601e3c3392e6704e884561229695f099a71336cbd580df9a9ffb965d0024bf0ed593189ab58900fd1714baef1c9ee59c738c3e2 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^2.1.0": + version: 2.1.2 + resolution: "@npmcli/fs@npm:2.1.2" + dependencies: + "@gar/promisify": ^1.1.3 + semver: ^7.3.5 + checksum: c50d087733d0d8df23be24f700f104b19922a28677aa66fdbe06ff6af6431cc4a5bb1e27683cbc661a5dafa9bafdc603e6a0378121506dfcd394b2b6dd76a187 + languageName: node + linkType: hard + +"@npmcli/move-file@npm:^1.0.1": + version: 1.1.2 + resolution: "@npmcli/move-file@npm:1.1.2" + dependencies: + mkdirp: ^1.0.4 + rimraf: ^3.0.2 + checksum: 02e946f3dafcc6743132fe2e0e2b585a96ca7265653a38df5a3e53fcf26c7c7a57fc0f861d7c689a23fdb6d6836c7eea5050c8086abf3c994feb2208d1514ff0 + languageName: node + linkType: hard + +"@npmcli/move-file@npm:^2.0.0": + version: 2.0.1 + resolution: "@npmcli/move-file@npm:2.0.1" + dependencies: + mkdirp: ^1.0.4 + rimraf: ^3.0.2 + checksum: 11b2151e6d1de6f6eb23128de5aa8a429fd9097d839a5190cb77aa47a6b627022c42d50fa7c47a00f1c9f8f0c1560092b09b061855d293fa0741a2a94cfb174d + languageName: node + linkType: hard + +"@openreplay/sourcemap-uploader@npm:^3.0.0": + version: 3.0.8 + resolution: "@openreplay/sourcemap-uploader@npm:3.0.8" + dependencies: + argparse: ^2.0.1 + glob-promise: ^5.0.0 + bin: + sourcemap-uploader: cli.js + checksum: 4c84afca41ac97d175a824334b4e57fe57449385d394c93077d0f83894630ce04f15fc0869813b6740f48a4ca78dd2dc05419161a5442a45268b1588edb2a47c + languageName: node + linkType: hard + +"@pmmmwh/react-refresh-webpack-plugin@npm:^0.5.3": + version: 0.5.10 + resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.10" + dependencies: + ansi-html-community: ^0.0.8 + common-path-prefix: ^3.0.0 + core-js-pure: ^3.23.3 + error-stack-parser: ^2.0.6 + find-up: ^5.0.0 + html-entities: ^2.1.0 + loader-utils: ^2.0.4 + schema-utils: ^3.0.0 + source-map: ^0.7.3 + peerDependencies: + "@types/webpack": 4.x || 5.x + react-refresh: ">=0.10.0 <1.0.0" + sockjs-client: ^1.4.0 + type-fest: ">=0.17.0 <4.0.0" + webpack: ">=4.43.0 <6.0.0" + webpack-dev-server: 3.x || 4.x + webpack-hot-middleware: 2.x + webpack-plugin-serve: 0.x || 1.x + peerDependenciesMeta: + "@types/webpack": + optional: true + sockjs-client: + optional: true + type-fest: + optional: true + webpack-dev-server: + optional: true + webpack-hot-middleware: + optional: true + webpack-plugin-serve: + optional: true + checksum: e470b543c5e8d73eeaa73636e1976e6719db6ae29c93fa62818f5796c1883051f379a3cb1ff85d909ef2c6bb9ef13ca46c36f1878c48d143b1355fea6660e547 + languageName: node + linkType: hard + +"@polka/url@npm:^1.0.0-next.20": + version: 1.0.0-next.21 + resolution: "@polka/url@npm:1.0.0-next.21" + checksum: 53c1f28683a075aac41f8ce2a54eb952b6bc67a03494b2dca1cb63d833a6da898cea6a92df8e1e6b680db985fb7f9c16e11c20afa6584bcdda68a16fb4c18737 + languageName: node + linkType: hard + +"@popperjs/core@npm:^2.6.0": + version: 2.11.6 + resolution: "@popperjs/core@npm:2.11.6" + checksum: 90b1361ab1a19cd351e482a88cb6cf13cf25973e20797bf9b97223e64b87bde8d9668ab2f33be24475e2a092d362ac256c361b16ea4e4ab5b21f2aeaef5f9bb7 + languageName: node + linkType: hard + +"@react-dnd/asap@npm:4.0.1": + version: 4.0.1 + resolution: "@react-dnd/asap@npm:4.0.1" + checksum: 9e78ea5a281c87c35f08839d100f0fddf6001f37da2366ff6d8ea0f426302e213675fe7f70825660603cc8bc81e41768bd18000df966564787fd64d7c4478c7c + languageName: node + linkType: hard + +"@react-dnd/asap@npm:^5.0.1": + version: 5.0.2 + resolution: "@react-dnd/asap@npm:5.0.2" + checksum: 0063db616db9801c9be18f11a912c3e214f87e714b1e4bf9462952af7ead65cba0b43e1f7c34bc8748811b6926e74d929e5e126f85ebb91b870faf809ceb5177 + languageName: node + linkType: hard + +"@react-dnd/invariant@npm:3.0.1": + version: 3.0.1 + resolution: "@react-dnd/invariant@npm:3.0.1" + checksum: ebd086f62b80bb8faabab66119e63bc77e0bb25291e82e96e51493d079fc0bfaa52990c31069d9c27d470a81c22713f3fd373daf106da61bdccec8d0d2de0f2a + languageName: node + linkType: hard + +"@react-dnd/invariant@npm:^4.0.1": + version: 4.0.2 + resolution: "@react-dnd/invariant@npm:4.0.2" + checksum: b303cc53fc5074cefb2a76b45b9c73ebb5d35630b18f5dc282ed9a9ac9b0287b9da1f6ac63acfdea2341b8f8187f615afc12d5eb14ec6015964f5c1b167332e2 + languageName: node + linkType: hard + +"@react-dnd/shallowequal@npm:^4.0.1": + version: 4.0.2 + resolution: "@react-dnd/shallowequal@npm:4.0.2" + checksum: 9a352fd176752e5d9c2797d598aca034b7829111ae0c992d80f40d5f068fcd6a039b1841c741dfa1ab67a36a00664310aec4f0ce216e4112f80875c9fe6fd8dc + languageName: node + linkType: hard + +"@rollup/plugin-babel@npm:^5.2.0": + version: 5.3.1 + resolution: "@rollup/plugin-babel@npm:5.3.1" + dependencies: + "@babel/helper-module-imports": ^7.10.4 + "@rollup/pluginutils": ^3.1.0 + peerDependencies: + "@babel/core": ^7.0.0 + "@types/babel__core": ^7.1.9 + rollup: ^1.20.0||^2.0.0 + peerDependenciesMeta: + "@types/babel__core": + optional: true + checksum: 2766134dd5567c0d4fd6909d1f511ce9bf3bd9d727e1bc5ffdd6097a3606faca324107ae8e0961839ee4dbb45e5e579ae601efe472fc0a271259aea79920cafa + languageName: node + linkType: hard + +"@rollup/plugin-node-resolve@npm:^11.2.1": + version: 11.2.1 + resolution: "@rollup/plugin-node-resolve@npm:11.2.1" + dependencies: + "@rollup/pluginutils": ^3.1.0 + "@types/resolve": 1.17.1 + builtin-modules: ^3.1.0 + deepmerge: ^4.2.2 + is-module: ^1.0.0 + resolve: ^1.19.0 + peerDependencies: + rollup: ^1.20.0||^2.0.0 + checksum: a8226b01352ee1f7133b1b59b3906267e11c99020a55e3b7a313e03889f790d1cd94e7f7769d3963261e897c3265082533ba595976f8e3f08cf70aa88bf1ddd7 + languageName: node + linkType: hard + +"@rollup/plugin-replace@npm:^2.4.1": + version: 2.4.2 + resolution: "@rollup/plugin-replace@npm:2.4.2" + dependencies: + "@rollup/pluginutils": ^3.1.0 + magic-string: ^0.25.7 + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 + checksum: ea3d27291c791661638b91809d0247dde1ee71be0b16fa7060078c2700db3669eada2c3978ea979b917b29ebe06f3fddc8797feae554da966264a22142b5771a + languageName: node + linkType: hard + +"@rollup/pluginutils@npm:^3.1.0": + version: 3.1.0 + resolution: "@rollup/pluginutils@npm:3.1.0" + dependencies: + "@types/estree": 0.0.39 + estree-walker: ^1.0.1 + picomatch: ^2.2.2 + peerDependencies: + rollup: ^1.20.0||^2.0.0 + checksum: 7151753160d15ba2b259461a6c25b3932150994ea52dba8fd3144f634c7647c2e56733d986e2c15de67c4d96a9ee7d6278efa6d2e626a7169898fd64adc0f90c + languageName: node + linkType: hard + +"@semantic-ui-react/event-stack@npm:^3.1.3": + version: 3.1.3 + resolution: "@semantic-ui-react/event-stack@npm:3.1.3" + dependencies: + exenv: ^1.2.2 + prop-types: ^15.6.2 + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 9f96516c16c8e4d985e9d9ed02c92bc2857563c434ee8b2703b7dbd2fefe9f4e84d5b9e108e5d172ed6002db2978c46c6c6eeee6100552b743be4f3a51ed78e8 + languageName: node + linkType: hard + +"@sentry/browser@npm:^5.21.1": + version: 5.30.0 + resolution: "@sentry/browser@npm:5.30.0" + dependencies: + "@sentry/core": 5.30.0 + "@sentry/types": 5.30.0 + "@sentry/utils": 5.30.0 + tslib: ^1.9.3 + checksum: 6793e1b49a8cdb1f025115bcc591bf67c97b6515f62a33ffcbb7b1ab66e459ebc471797d02e471be1ebf14092b56eb25ed914f043962388cc224bc961e334a17 + languageName: node + linkType: hard + +"@sentry/core@npm:5.30.0": + version: 5.30.0 + resolution: "@sentry/core@npm:5.30.0" + dependencies: + "@sentry/hub": 5.30.0 + "@sentry/minimal": 5.30.0 + "@sentry/types": 5.30.0 + "@sentry/utils": 5.30.0 + tslib: ^1.9.3 + checksum: 6407b9c2a6a56f90c198f5714b3257df24d89d1b4ca6726bd44760d0adabc25798b69fef2c88ccea461c7e79e3c78861aaebfd51fd3cb892aee656c3f7e11801 + languageName: node + linkType: hard + +"@sentry/hub@npm:5.30.0": + version: 5.30.0 + resolution: "@sentry/hub@npm:5.30.0" + dependencies: + "@sentry/types": 5.30.0 + "@sentry/utils": 5.30.0 + tslib: ^1.9.3 + checksum: 386c91d06aa44be0465fc11330d748a113e464d41cd562a9e1d222a682cbcb14e697a3e640953e7a0239997ad8a02b223a0df3d9e1d8816cb823fd3613be3e2f + languageName: node + linkType: hard + +"@sentry/minimal@npm:5.30.0": + version: 5.30.0 + resolution: "@sentry/minimal@npm:5.30.0" + dependencies: + "@sentry/hub": 5.30.0 + "@sentry/types": 5.30.0 + tslib: ^1.9.3 + checksum: 34ec05503de46d01f98c94701475d5d89cc044892c86ccce30e01f62f28344eb23b718e7cf573815e46f30a4ac9da3129bed9b3d20c822938acfb40cbe72437b + languageName: node + linkType: hard + +"@sentry/types@npm:5.30.0": + version: 5.30.0 + resolution: "@sentry/types@npm:5.30.0" + checksum: 99c6e55c0a82c8ca95be2e9dbb35f581b29e4ff7af74b23bc62b690de4e35febfa15868184a2303480ef86babd4fea5273cf3b5ddf4a27685b841a72f13a0c88 + languageName: node + linkType: hard + +"@sentry/utils@npm:5.30.0": + version: 5.30.0 + resolution: "@sentry/utils@npm:5.30.0" + dependencies: + "@sentry/types": 5.30.0 + tslib: ^1.9.3 + checksum: ca8eebfea7ac7db6d16f6c0b8a66ac62587df12a79ce9d0d8393f4d69880bb8d40d438f9810f7fb107a9880fe0d68bbf797b89cbafd113e89a0829eb06b205f8 + languageName: node + linkType: hard + +"@sinclair/typebox@npm:^0.24.1": + version: 0.24.51 + resolution: "@sinclair/typebox@npm:0.24.51" + checksum: 458131e83ca59ad3721f0abeef2aa5220aff2083767e1143d75c67c85d55ef7a212f48f394471ee6bdd2e860ba30f09a489cdd2a28a2824d5b0d1014bdfb2552 + languageName: node + linkType: hard + +"@sinonjs/commons@npm:^1.7.0": + version: 1.8.6 + resolution: "@sinonjs/commons@npm:1.8.6" + dependencies: + type-detect: 4.0.8 + checksum: 93b4d4e27e93652b83467869c2fe09cbd8f37cd5582327f0e081fbf9b93899e2d267db7b668c96810c63dc229867614ced825e5512b47db96ca6f87cb3ec0f61 + languageName: node + linkType: hard + +"@sinonjs/fake-timers@npm:^9.1.2": + version: 9.1.2 + resolution: "@sinonjs/fake-timers@npm:9.1.2" + dependencies: + "@sinonjs/commons": ^1.7.0 + checksum: d9187f9130f03272562ff9845867299c6f7cf15157bbb3e6aca4a1f06d885b0eef54259d0ad41e2f8043dc530b4db730b6c9415b169033e7ba8fed0ad449ceec + languageName: node + linkType: hard + +"@socket.io/component-emitter@npm:~3.1.0": + version: 3.1.0 + resolution: "@socket.io/component-emitter@npm:3.1.0" + checksum: b838ccccf74c36fa7d3ed89a7efb5858cba1a84db4d08250c2fc44d8235140f10d31875bde71517d8503cb3fb08fcd34d3b7a3d0d89058ca3f74f7c816f0fb9c + languageName: node + linkType: hard + +"@storybook/addon-actions@npm:6.5.15, @storybook/addon-actions@npm:^6.5.12": + version: 6.5.15 + resolution: "@storybook/addon-actions@npm:6.5.15" + dependencies: + "@storybook/addons": 6.5.15 + "@storybook/api": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/components": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/theming": 6.5.15 + core-js: ^3.8.2 + fast-deep-equal: ^3.1.3 + global: ^4.4.0 + lodash: ^4.17.21 + polished: ^4.2.2 + prop-types: ^15.7.2 + react-inspector: ^5.1.0 + regenerator-runtime: ^0.13.7 + telejson: ^6.0.8 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + uuid-browser: ^3.1.0 + 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 + checksum: 1fb3c5f72ab658a136984ab792ef78c959eaf6e01f5a1f247be153974278c23cd8bae25e10054cdceddebfdfc02383276dadf684642d90e748ccc22005f7c95d + languageName: node + linkType: hard + +"@storybook/addon-backgrounds@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/addon-backgrounds@npm:6.5.15" + dependencies: + "@storybook/addons": 6.5.15 + "@storybook/api": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/components": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/theming": 6.5.15 + core-js: ^3.8.2 + global: ^4.4.0 + memoizerific: ^1.11.3 + regenerator-runtime: ^0.13.7 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + 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 + checksum: d8dc734fccdfd68ba237eb1a6f1993813ef3f3e8c7478056f7d8f2e1a1dde28a5642bb4e115964eee8a87f2a7fc4e77984be9835a72f6cadf2292b5b8c0e83fd + languageName: node + linkType: hard + +"@storybook/addon-controls@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/addon-controls@npm:6.5.15" + dependencies: + "@storybook/addons": 6.5.15 + "@storybook/api": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/components": 6.5.15 + "@storybook/core-common": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/node-logger": 6.5.15 + "@storybook/store": 6.5.15 + "@storybook/theming": 6.5.15 + core-js: ^3.8.2 + lodash: ^4.17.21 + ts-dedent: ^2.0.0 + 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 + checksum: 557294d4ad3da6bd1a56156344f0a3e6a2eb5f11dfbd44808232f7c8151b77e92740e4ef21f5c5fa25fe7de35790c24d0fcabf1d992fa19829dcb06866ef0843 + languageName: node + linkType: hard + +"@storybook/addon-docs@npm:6.5.15, @storybook/addon-docs@npm:^6.5.12": + version: 6.5.15 + resolution: "@storybook/addon-docs@npm:6.5.15" + dependencies: + "@babel/plugin-transform-react-jsx": ^7.12.12 + "@babel/preset-env": ^7.12.11 + "@jest/transform": ^26.6.2 + "@mdx-js/react": ^1.6.22 + "@storybook/addons": 6.5.15 + "@storybook/api": 6.5.15 + "@storybook/components": 6.5.15 + "@storybook/core-common": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/docs-tools": 6.5.15 + "@storybook/mdx1-csf": ^0.0.1 + "@storybook/node-logger": 6.5.15 + "@storybook/postinstall": 6.5.15 + "@storybook/preview-web": 6.5.15 + "@storybook/source-loader": 6.5.15 + "@storybook/store": 6.5.15 + "@storybook/theming": 6.5.15 + babel-loader: ^8.0.0 + core-js: ^3.8.2 + fast-deep-equal: ^3.1.3 + global: ^4.4.0 + lodash: ^4.17.21 + regenerator-runtime: ^0.13.7 + remark-external-links: ^8.0.0 + remark-slug: ^6.0.0 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + peerDependencies: + "@storybook/mdx2-csf": ^0.0.3 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@storybook/mdx2-csf": + optional: true + react: + optional: true + react-dom: + optional: true + checksum: 40df648c3087a526ceee2eeb5123021ca07ebddd8002ad3fa821e376621f7c8d716ead759c905d3a5923d808948bd8b7c411d96a1867d267d48c311b373cf59d + languageName: node + linkType: hard + +"@storybook/addon-essentials@npm:^6.5.12": + version: 6.5.15 + resolution: "@storybook/addon-essentials@npm:6.5.15" + dependencies: + "@storybook/addon-actions": 6.5.15 + "@storybook/addon-backgrounds": 6.5.15 + "@storybook/addon-controls": 6.5.15 + "@storybook/addon-docs": 6.5.15 + "@storybook/addon-measure": 6.5.15 + "@storybook/addon-outline": 6.5.15 + "@storybook/addon-toolbars": 6.5.15 + "@storybook/addon-viewport": 6.5.15 + "@storybook/addons": 6.5.15 + "@storybook/api": 6.5.15 + "@storybook/core-common": 6.5.15 + "@storybook/node-logger": 6.5.15 + core-js: ^3.8.2 + regenerator-runtime: ^0.13.7 + ts-dedent: ^2.0.0 + peerDependencies: + "@babel/core": ^7.9.6 + peerDependenciesMeta: + "@storybook/angular": + optional: true + "@storybook/builder-manager4": + optional: true + "@storybook/builder-manager5": + optional: true + "@storybook/builder-webpack4": + optional: true + "@storybook/builder-webpack5": + optional: true + "@storybook/html": + optional: true + "@storybook/vue": + optional: true + "@storybook/vue3": + optional: true + "@storybook/web-components": + optional: true + lit: + optional: true + lit-html: + optional: true + react: + optional: true + react-dom: + optional: true + svelte: + optional: true + sveltedoc-parser: + optional: true + vue: + optional: true + webpack: + optional: true + checksum: 46c0e7bc7f51430b2b5eb538aa3aa6fa86ab754db38e95ea7d230661550e660d20ae8c6456ade73dd80b469481e24b2846349c664e7bd120004f1562f8a4dc4d + languageName: node + linkType: hard + +"@storybook/addon-interactions@npm:^6.5.12": + version: 6.5.15 + resolution: "@storybook/addon-interactions@npm:6.5.15" + dependencies: + "@devtools-ds/object-inspector": ^1.1.2 + "@storybook/addons": 6.5.15 + "@storybook/api": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/components": 6.5.15 + "@storybook/core-common": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/instrumenter": 6.5.15 + "@storybook/theming": 6.5.15 + core-js: ^3.8.2 + global: ^4.4.0 + jest-mock: ^27.0.6 + polished: ^4.2.2 + ts-dedent: ^2.2.0 + 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 + checksum: 4f566e7aecbc232b3a22f1fa78c51560562826c81e55ec8f96d7d4489f38b6c7fb5e8d9fe55eb2cc1d801b6823b4c523ac2e72fdc9584419065fcbfaa64bda30 + languageName: node + linkType: hard + +"@storybook/addon-links@npm:^6.5.12": + version: 6.5.15 + resolution: "@storybook/addon-links@npm:6.5.15" + dependencies: + "@storybook/addons": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/router": 6.5.15 + "@types/qs": ^6.9.5 + core-js: ^3.8.2 + global: ^4.4.0 + prop-types: ^15.7.2 + qs: ^6.10.0 + regenerator-runtime: ^0.13.7 + ts-dedent: ^2.0.0 + 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 + checksum: c0f655eb743a36dd0546ee01b69c1e0195d0871cb88f112c009699b29101dafff011b477803465259ac43ab6191876c18280ccabd3d8462cde5ac1c88b8a7761 + languageName: node + linkType: hard + +"@storybook/addon-measure@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/addon-measure@npm:6.5.15" + dependencies: + "@storybook/addons": 6.5.15 + "@storybook/api": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/components": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + core-js: ^3.8.2 + global: ^4.4.0 + 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 + checksum: e5c4e67f5bb734b7e74298eeb7e93f7332ca971a91ed60e370582019199accc37f6e6297192157a9e00c0efdbe68e043c277eab6ce2ca03cd63b1f29c7cffa62 + languageName: node + linkType: hard + +"@storybook/addon-outline@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/addon-outline@npm:6.5.15" + dependencies: + "@storybook/addons": 6.5.15 + "@storybook/api": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/components": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + core-js: ^3.8.2 + global: ^4.4.0 + regenerator-runtime: ^0.13.7 + ts-dedent: ^2.0.0 + 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 + checksum: 3fe8f4473d08cabb89af0afbf70701f45bc8c6d2e94f88e0dfdfab8d9c639a8ba0175d8730b824d519f97651ad932f5729da7f6b09e62defd5e4e2ca18f23694 + languageName: node + linkType: hard + +"@storybook/addon-toolbars@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/addon-toolbars@npm:6.5.15" + dependencies: + "@storybook/addons": 6.5.15 + "@storybook/api": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/components": 6.5.15 + "@storybook/theming": 6.5.15 + core-js: ^3.8.2 + regenerator-runtime: ^0.13.7 + 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 + checksum: a85f5811122960ca035bbab3d289f0d3341167113426d18a11a8f9d7815b3ac7c16077c270567da24f5dc53699efe2ba9b48f57f1e0b3e64a736e0e361c2eb4d + languageName: node + linkType: hard + +"@storybook/addon-viewport@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/addon-viewport@npm:6.5.15" + dependencies: + "@storybook/addons": 6.5.15 + "@storybook/api": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/components": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/theming": 6.5.15 + core-js: ^3.8.2 + global: ^4.4.0 + memoizerific: ^1.11.3 + prop-types: ^15.7.2 + regenerator-runtime: ^0.13.7 + 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 + checksum: d84119a30c4d7f46d068707ee4bd1cb1e2a9e3382a436322131e9961c03eeb42d227daad89e39338554baab6b6b394dcdd50072a124e0ee43a6f9e4407298e09 + languageName: node + linkType: hard + +"@storybook/addons@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/addons@npm:6.5.15" + dependencies: + "@storybook/api": 6.5.15 + "@storybook/channels": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/router": 6.5.15 + "@storybook/theming": 6.5.15 + "@types/webpack-env": ^1.16.0 + core-js: ^3.8.2 + global: ^4.4.0 + regenerator-runtime: ^0.13.7 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 11ccd0bb415731fc532925e7ac00363d3ceca37074490c80736c23e081c33c122ec0c6d21f04cbce31d6c2f09080309a471051d6f834637bf96f426f35aa1925 + languageName: node + linkType: hard + +"@storybook/api@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/api@npm:6.5.15" + dependencies: + "@storybook/channels": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/router": 6.5.15 + "@storybook/semver": ^7.3.2 + "@storybook/theming": 6.5.15 + core-js: ^3.8.2 + fast-deep-equal: ^3.1.3 + global: ^4.4.0 + lodash: ^4.17.21 + memoizerific: ^1.11.3 + regenerator-runtime: ^0.13.7 + store2: ^2.12.0 + telejson: ^6.0.8 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 64dc745f58739799d02e44bdf5ad8f1fcb418e27046a94d8dd8bc0d434fb9938efc3c638332c68bf8d09db8e291a07ef8e4af01fdb67acac0e24f43583742dd4 + languageName: node + linkType: hard + +"@storybook/builder-webpack4@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/builder-webpack4@npm:6.5.15" + dependencies: + "@babel/core": ^7.12.10 + "@storybook/addons": 6.5.15 + "@storybook/api": 6.5.15 + "@storybook/channel-postmessage": 6.5.15 + "@storybook/channels": 6.5.15 + "@storybook/client-api": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/components": 6.5.15 + "@storybook/core-common": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/node-logger": 6.5.15 + "@storybook/preview-web": 6.5.15 + "@storybook/router": 6.5.15 + "@storybook/semver": ^7.3.2 + "@storybook/store": 6.5.15 + "@storybook/theming": 6.5.15 + "@storybook/ui": 6.5.15 + "@types/node": ^14.0.10 || ^16.0.0 + "@types/webpack": ^4.41.26 + autoprefixer: ^9.8.6 + babel-loader: ^8.0.0 + case-sensitive-paths-webpack-plugin: ^2.3.0 + core-js: ^3.8.2 + css-loader: ^3.6.0 + file-loader: ^6.2.0 + find-up: ^5.0.0 + fork-ts-checker-webpack-plugin: ^4.1.6 + glob: ^7.1.6 + glob-promise: ^3.4.0 + global: ^4.4.0 + html-webpack-plugin: ^4.0.0 + pnp-webpack-plugin: 1.6.4 + postcss: ^7.0.36 + postcss-flexbugs-fixes: ^4.2.1 + postcss-loader: ^4.2.0 + raw-loader: ^4.0.2 + stable: ^0.1.8 + style-loader: ^1.3.0 + terser-webpack-plugin: ^4.2.3 + ts-dedent: ^2.0.0 + url-loader: ^4.1.1 + util-deprecate: ^1.0.2 + webpack: 4 + webpack-dev-middleware: ^3.7.3 + webpack-filter-warnings-plugin: ^1.2.1 + webpack-hot-middleware: ^2.25.1 + webpack-virtual-modules: ^0.2.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 39621ee16d1999ee02e12595dbc30547dc3dd49e8c49ea09bc8f485a87a3e25dae18227d180e58c3f2afcf316d65ec2e9c8c92efd875a0f8649fd0054c5eef5e + languageName: node + linkType: hard + +"@storybook/builder-webpack5@npm:^6.5.12": + version: 6.5.15 + resolution: "@storybook/builder-webpack5@npm:6.5.15" + dependencies: + "@babel/core": ^7.12.10 + "@storybook/addons": 6.5.15 + "@storybook/api": 6.5.15 + "@storybook/channel-postmessage": 6.5.15 + "@storybook/channels": 6.5.15 + "@storybook/client-api": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/components": 6.5.15 + "@storybook/core-common": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/node-logger": 6.5.15 + "@storybook/preview-web": 6.5.15 + "@storybook/router": 6.5.15 + "@storybook/semver": ^7.3.2 + "@storybook/store": 6.5.15 + "@storybook/theming": 6.5.15 + "@types/node": ^14.0.10 || ^16.0.0 + babel-loader: ^8.0.0 + babel-plugin-named-exports-order: ^0.0.2 + browser-assert: ^1.2.1 + case-sensitive-paths-webpack-plugin: ^2.3.0 + core-js: ^3.8.2 + css-loader: ^5.0.1 + fork-ts-checker-webpack-plugin: ^6.0.4 + glob: ^7.1.6 + glob-promise: ^3.4.0 + html-webpack-plugin: ^5.0.0 + path-browserify: ^1.0.1 + process: ^0.11.10 + stable: ^0.1.8 + style-loader: ^2.0.0 + terser-webpack-plugin: ^5.0.3 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + webpack: ^5.9.0 + webpack-dev-middleware: ^4.1.0 + webpack-hot-middleware: ^2.25.1 + webpack-virtual-modules: ^0.4.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 6aa910e95c6d8a2975e05e8fc0c48bb5b087b9f335dfac12fc926d44918ffa2465238a2b037e290f14375a07c64ff7c67d8ed27e9f17c982574e96692fe9e637 + languageName: node + linkType: hard + +"@storybook/channel-postmessage@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/channel-postmessage@npm:6.5.15" + dependencies: + "@storybook/channels": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/core-events": 6.5.15 + core-js: ^3.8.2 + global: ^4.4.0 + qs: ^6.10.0 + telejson: ^6.0.8 + checksum: f6d924e5f764b1b0ac973dd9b06dd4c853553a316021d3f8b3b955ad2210ac1a3e4563c08ffb8a62be7836d695f8952562b3496fe93de413274105d602c2c9d5 + languageName: node + linkType: hard + +"@storybook/channel-websocket@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/channel-websocket@npm:6.5.15" + dependencies: + "@storybook/channels": 6.5.15 + "@storybook/client-logger": 6.5.15 + core-js: ^3.8.2 + global: ^4.4.0 + telejson: ^6.0.8 + checksum: 56af62ea9b9bef62f8d44e663c9cba7a891e93d443c58d74648992d7f6896334055b5445a2697e4b9aeb7a423fa47892e1f72d6575fbc0dc81d2396c8357ba28 + languageName: node + linkType: hard + +"@storybook/channels@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/channels@npm:6.5.15" + dependencies: + core-js: ^3.8.2 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + checksum: 6510df456471ea574181d6bbd306e4f1742a0437b45935bc164ee6e638018cf3b66e709925d2bd1b741a38bd1c48df86d497079a68f40b08d83f10e8ac6bf65e + languageName: node + linkType: hard + +"@storybook/client-api@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/client-api@npm:6.5.15" + dependencies: + "@storybook/addons": 6.5.15 + "@storybook/channel-postmessage": 6.5.15 + "@storybook/channels": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/store": 6.5.15 + "@types/qs": ^6.9.5 + "@types/webpack-env": ^1.16.0 + core-js: ^3.8.2 + fast-deep-equal: ^3.1.3 + global: ^4.4.0 + lodash: ^4.17.21 + memoizerific: ^1.11.3 + qs: ^6.10.0 + regenerator-runtime: ^0.13.7 + store2: ^2.12.0 + synchronous-promise: ^2.0.15 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 79c9be600e74d2141966d812fb3c54c1df2fc354808c891ac9701aa34d82cef227d905f19ae988501ad4d9aaf97e915b20057dc97673d18fc4f63097c37a8b15 + languageName: node + linkType: hard + +"@storybook/client-logger@npm:6.5.15, @storybook/client-logger@npm:^6.4.0": + version: 6.5.15 + resolution: "@storybook/client-logger@npm:6.5.15" + dependencies: + core-js: ^3.8.2 + global: ^4.4.0 + checksum: 00754c471cbc527cc6d01dd8c87ab21e5d4c44f46ec6bae23e3f1271a83d6e39b939e5b6c5d0dc638eeeab0938bf2bd0130c6c73b78476264be75999d402fd50 + languageName: node + linkType: hard + +"@storybook/components@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/components@npm:6.5.15" + dependencies: + "@storybook/client-logger": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/theming": 6.5.15 + core-js: ^3.8.2 + memoizerific: ^1.11.3 + qs: ^6.10.0 + regenerator-runtime: ^0.13.7 + util-deprecate: ^1.0.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: ea41dfe4553663393b05f0e56b3b9d0551042a8b9f76a9eceeecfa84c415f031dbce2276bd016e3f8cd0713838a650f86cdf3c280c52bf8143ce19d21af28b09 + languageName: node + linkType: hard + +"@storybook/core-client@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/core-client@npm:6.5.15" + dependencies: + "@storybook/addons": 6.5.15 + "@storybook/channel-postmessage": 6.5.15 + "@storybook/channel-websocket": 6.5.15 + "@storybook/client-api": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/preview-web": 6.5.15 + "@storybook/store": 6.5.15 + "@storybook/ui": 6.5.15 + airbnb-js-shims: ^2.2.1 + ansi-to-html: ^0.6.11 + core-js: ^3.8.2 + global: ^4.4.0 + lodash: ^4.17.21 + qs: ^6.10.0 + regenerator-runtime: ^0.13.7 + ts-dedent: ^2.0.0 + unfetch: ^4.2.0 + util-deprecate: ^1.0.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + webpack: "*" + peerDependenciesMeta: + typescript: + optional: true + checksum: 27961afc15c9a203942dd963ed8c7f769bf67673fcc3730620038c921c173e81e9205c6e73080b0644b1d3ec6e70458cd531b8c9870232cc2a1292fc0558f3f4 + languageName: node + linkType: hard + +"@storybook/core-common@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/core-common@npm:6.5.15" + dependencies: + "@babel/core": ^7.12.10 + "@babel/plugin-proposal-class-properties": ^7.12.1 + "@babel/plugin-proposal-decorators": ^7.12.12 + "@babel/plugin-proposal-export-default-from": ^7.12.1 + "@babel/plugin-proposal-nullish-coalescing-operator": ^7.12.1 + "@babel/plugin-proposal-object-rest-spread": ^7.12.1 + "@babel/plugin-proposal-optional-chaining": ^7.12.7 + "@babel/plugin-proposal-private-methods": ^7.12.1 + "@babel/plugin-proposal-private-property-in-object": ^7.12.1 + "@babel/plugin-syntax-dynamic-import": ^7.8.3 + "@babel/plugin-transform-arrow-functions": ^7.12.1 + "@babel/plugin-transform-block-scoping": ^7.12.12 + "@babel/plugin-transform-classes": ^7.12.1 + "@babel/plugin-transform-destructuring": ^7.12.1 + "@babel/plugin-transform-for-of": ^7.12.1 + "@babel/plugin-transform-parameters": ^7.12.1 + "@babel/plugin-transform-shorthand-properties": ^7.12.1 + "@babel/plugin-transform-spread": ^7.12.1 + "@babel/preset-env": ^7.12.11 + "@babel/preset-react": ^7.12.10 + "@babel/preset-typescript": ^7.12.7 + "@babel/register": ^7.12.1 + "@storybook/node-logger": 6.5.15 + "@storybook/semver": ^7.3.2 + "@types/node": ^14.0.10 || ^16.0.0 + "@types/pretty-hrtime": ^1.0.0 + babel-loader: ^8.0.0 + babel-plugin-macros: ^3.0.1 + babel-plugin-polyfill-corejs3: ^0.1.0 + chalk: ^4.1.0 + core-js: ^3.8.2 + express: ^4.17.1 + file-system-cache: ^1.0.5 + find-up: ^5.0.0 + fork-ts-checker-webpack-plugin: ^6.0.4 + fs-extra: ^9.0.1 + glob: ^7.1.6 + handlebars: ^4.7.7 + interpret: ^2.2.0 + json5: ^2.1.3 + lazy-universal-dotenv: ^3.0.1 + picomatch: ^2.3.0 + pkg-dir: ^5.0.0 + pretty-hrtime: ^1.0.3 + resolve-from: ^5.0.0 + slash: ^3.0.0 + telejson: ^6.0.8 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + webpack: 4 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 03ce0c1a3d07990b7955b441421d715091281349205a675a460edf3b0b1be00c3b62b36e42dbbdd450aa3cb49d3981ada3b01359516f41e3e314e0baf7faed7c + languageName: node + linkType: hard + +"@storybook/core-events@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/core-events@npm:6.5.15" + dependencies: + core-js: ^3.8.2 + checksum: b790913be35a67e65b565ac6ef3c56ef5940916f1e5b850c65f8acb155ac6365f1fc752b10a29fa853e58581e4eb7e9a75624ace2eb427c7deedee258500c905 + languageName: node + linkType: hard + +"@storybook/core-server@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/core-server@npm:6.5.15" + dependencies: + "@discoveryjs/json-ext": ^0.5.3 + "@storybook/builder-webpack4": 6.5.15 + "@storybook/core-client": 6.5.15 + "@storybook/core-common": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/csf-tools": 6.5.15 + "@storybook/manager-webpack4": 6.5.15 + "@storybook/node-logger": 6.5.15 + "@storybook/semver": ^7.3.2 + "@storybook/store": 6.5.15 + "@storybook/telemetry": 6.5.15 + "@types/node": ^14.0.10 || ^16.0.0 + "@types/node-fetch": ^2.5.7 + "@types/pretty-hrtime": ^1.0.0 + "@types/webpack": ^4.41.26 + better-opn: ^2.1.1 + boxen: ^5.1.2 + chalk: ^4.1.0 + cli-table3: ^0.6.1 + commander: ^6.2.1 + compression: ^1.7.4 + core-js: ^3.8.2 + cpy: ^8.1.2 + detect-port: ^1.3.0 + express: ^4.17.1 + fs-extra: ^9.0.1 + global: ^4.4.0 + globby: ^11.0.2 + ip: ^2.0.0 + lodash: ^4.17.21 + node-fetch: ^2.6.7 + open: ^8.4.0 + pretty-hrtime: ^1.0.3 + prompts: ^2.4.0 + regenerator-runtime: ^0.13.7 + serve-favicon: ^2.5.0 + slash: ^3.0.0 + telejson: ^6.0.8 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + watchpack: ^2.2.0 + webpack: 4 + ws: ^8.2.3 + x-default-browser: ^0.4.0 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@storybook/builder-webpack5": + optional: true + "@storybook/manager-webpack5": + optional: true + typescript: + optional: true + checksum: 250ca8d251f79735c7e4db0e5cdb205aab8f8e46edaf5bc84c157f0dfaa7874075449da4b0f1fbe8539c501b831c2a9ac9439f135514e9f35f1456f0f1f3320f + languageName: node + linkType: hard + +"@storybook/core@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/core@npm:6.5.15" + dependencies: + "@storybook/core-client": 6.5.15 + "@storybook/core-server": 6.5.15 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + webpack: "*" + peerDependenciesMeta: + "@storybook/builder-webpack5": + optional: true + "@storybook/manager-webpack5": + optional: true + typescript: + optional: true + checksum: 5406003676fe96efb56b6920722cfc6616027b315d6f12930993191cccb5232ce2b4dc3a93429343db0967e784acca8cc06129a9f973bf5766f418f3987b90e4 + languageName: node + linkType: hard + +"@storybook/csf-tools@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/csf-tools@npm:6.5.15" + dependencies: + "@babel/core": ^7.12.10 + "@babel/generator": ^7.12.11 + "@babel/parser": ^7.12.11 + "@babel/plugin-transform-react-jsx": ^7.12.12 + "@babel/preset-env": ^7.12.11 + "@babel/traverse": ^7.12.11 + "@babel/types": ^7.12.11 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/mdx1-csf": ^0.0.1 + core-js: ^3.8.2 + fs-extra: ^9.0.1 + global: ^4.4.0 + regenerator-runtime: ^0.13.7 + ts-dedent: ^2.0.0 + peerDependencies: + "@storybook/mdx2-csf": ^0.0.3 + peerDependenciesMeta: + "@storybook/mdx2-csf": + optional: true + checksum: d8fcaa6b979211ea627f6154ea5ce15cd0796aeaa18306f380dc9afd60abc7a05b15fb00bda352513fdb3d0e630c065774e8110d3884ab3579e0bcfeb0da15a0 + languageName: node + linkType: hard + +"@storybook/csf@npm:0.0.2--canary.4566f4d.1": + version: 0.0.2--canary.4566f4d.1 + resolution: "@storybook/csf@npm:0.0.2--canary.4566f4d.1" + dependencies: + lodash: ^4.17.15 + checksum: dc0fe9940a47fbba9762275083816953da07a188f0315a631c307716b16a7073586a4d229df6b177dfb4b01604667e2bb24c13d6bfcb137d2f4d306874a590f4 + languageName: node + linkType: hard + +"@storybook/docs-tools@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/docs-tools@npm:6.5.15" + dependencies: + "@babel/core": ^7.12.10 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/store": 6.5.15 + core-js: ^3.8.2 + doctrine: ^3.0.0 + lodash: ^4.17.21 + regenerator-runtime: ^0.13.7 + checksum: 976c49d3817ea6e92199bc86ad146c55e06a3985e87aaeb230b8961b34c149362beb4c75e8383cb9ce33b261e07dbf3d761ef44d9ac1e16693dc612bfcdbefa0 + languageName: node + linkType: hard + +"@storybook/instrumenter@npm:6.5.15, @storybook/instrumenter@npm:^6.4.0": + version: 6.5.15 + resolution: "@storybook/instrumenter@npm:6.5.15" + dependencies: + "@storybook/addons": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/core-events": 6.5.15 + core-js: ^3.8.2 + global: ^4.4.0 + checksum: 9b4937d2d58c5634db16989bf6ec2f72e9d6b662549e6baa497df4e7daec1f72c0ce986123dba57622b60d394a841d26dcd7ccae7d299e1fcaa812ee80a6d577 + languageName: node + linkType: hard + +"@storybook/manager-webpack4@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/manager-webpack4@npm:6.5.15" + dependencies: + "@babel/core": ^7.12.10 + "@babel/plugin-transform-template-literals": ^7.12.1 + "@babel/preset-react": ^7.12.10 + "@storybook/addons": 6.5.15 + "@storybook/core-client": 6.5.15 + "@storybook/core-common": 6.5.15 + "@storybook/node-logger": 6.5.15 + "@storybook/theming": 6.5.15 + "@storybook/ui": 6.5.15 + "@types/node": ^14.0.10 || ^16.0.0 + "@types/webpack": ^4.41.26 + babel-loader: ^8.0.0 + case-sensitive-paths-webpack-plugin: ^2.3.0 + chalk: ^4.1.0 + core-js: ^3.8.2 + css-loader: ^3.6.0 + express: ^4.17.1 + file-loader: ^6.2.0 + find-up: ^5.0.0 + fs-extra: ^9.0.1 + html-webpack-plugin: ^4.0.0 + node-fetch: ^2.6.7 + pnp-webpack-plugin: 1.6.4 + read-pkg-up: ^7.0.1 + regenerator-runtime: ^0.13.7 + resolve-from: ^5.0.0 + style-loader: ^1.3.0 + telejson: ^6.0.8 + terser-webpack-plugin: ^4.2.3 + ts-dedent: ^2.0.0 + url-loader: ^4.1.1 + util-deprecate: ^1.0.2 + webpack: 4 + webpack-dev-middleware: ^3.7.3 + webpack-virtual-modules: ^0.2.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: c546213f58bfc56913695a38f7513bab60259478303e7e492ba7e85086caa0fe40e62f0bda3e53a819eeeaeaccef94b0b563760bc2dd2670faf70f1e93f5db30 + languageName: node + linkType: hard + +"@storybook/manager-webpack5@npm:^6.5.12": + version: 6.5.15 + resolution: "@storybook/manager-webpack5@npm:6.5.15" + dependencies: + "@babel/core": ^7.12.10 + "@babel/plugin-transform-template-literals": ^7.12.1 + "@babel/preset-react": ^7.12.10 + "@storybook/addons": 6.5.15 + "@storybook/core-client": 6.5.15 + "@storybook/core-common": 6.5.15 + "@storybook/node-logger": 6.5.15 + "@storybook/theming": 6.5.15 + "@storybook/ui": 6.5.15 + "@types/node": ^14.0.10 || ^16.0.0 + babel-loader: ^8.0.0 + case-sensitive-paths-webpack-plugin: ^2.3.0 + chalk: ^4.1.0 + core-js: ^3.8.2 + css-loader: ^5.0.1 + express: ^4.17.1 + find-up: ^5.0.0 + fs-extra: ^9.0.1 + html-webpack-plugin: ^5.0.0 + node-fetch: ^2.6.7 + process: ^0.11.10 + read-pkg-up: ^7.0.1 + regenerator-runtime: ^0.13.7 + resolve-from: ^5.0.0 + style-loader: ^2.0.0 + telejson: ^6.0.8 + terser-webpack-plugin: ^5.0.3 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + webpack: ^5.9.0 + webpack-dev-middleware: ^4.1.0 + webpack-virtual-modules: ^0.4.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 8c6350ab63bb3d287f72aafa67dae56121d5155ec3f55ba66879bbeebc8ef92f41abab1a4f87c15ab855cd477c5d5961aae5b6905495534274316a4a254f9c4d + languageName: node + linkType: hard + +"@storybook/mdx1-csf@npm:^0.0.1": + version: 0.0.1 + resolution: "@storybook/mdx1-csf@npm:0.0.1" + dependencies: + "@babel/generator": ^7.12.11 + "@babel/parser": ^7.12.11 + "@babel/preset-env": ^7.12.11 + "@babel/types": ^7.12.11 + "@mdx-js/mdx": ^1.6.22 + "@types/lodash": ^4.14.167 + js-string-escape: ^1.0.1 + loader-utils: ^2.0.0 + lodash: ^4.17.21 + prettier: ">=2.2.1 <=2.3.0" + ts-dedent: ^2.0.0 + checksum: c25a4ad1356ce65950483bd85f37ba93237149fd782360e3548a86dafd9753674ddebb03a8665ed78f99bc1533e87ba2a0605c2fc984a5ad19662fd4b930db78 + languageName: node + linkType: hard + +"@storybook/node-logger@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/node-logger@npm:6.5.15" + dependencies: + "@types/npmlog": ^4.1.2 + chalk: ^4.1.0 + core-js: ^3.8.2 + npmlog: ^5.0.1 + pretty-hrtime: ^1.0.3 + checksum: 37e99aa1c9ab748befdbcbb21913538eb2c23c30d604409af365aa6fe7bb56dac5115ecbaf93a1ff52c9675d3b1e2dfc27163a1ddbb3eb2cb005e790fa6ca16d + languageName: node + linkType: hard + +"@storybook/postinstall@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/postinstall@npm:6.5.15" + dependencies: + core-js: ^3.8.2 + checksum: 386bdb470b46a136cf4d4455416e833173c08744bb59485a0e4261940bea93450d67f49523aa3bfec3762540ae94856dd08ed283af15170d11e6045ab09ec2c4 + languageName: node + linkType: hard + +"@storybook/preview-web@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/preview-web@npm:6.5.15" + dependencies: + "@storybook/addons": 6.5.15 + "@storybook/channel-postmessage": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/store": 6.5.15 + ansi-to-html: ^0.6.11 + core-js: ^3.8.2 + global: ^4.4.0 + lodash: ^4.17.21 + qs: ^6.10.0 + regenerator-runtime: ^0.13.7 + synchronous-promise: ^2.0.15 + ts-dedent: ^2.0.0 + unfetch: ^4.2.0 + util-deprecate: ^1.0.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 61b427c202ebe2bc26e1996230e326a07af643aa7c6611285c13393ee5e0d7ec712c55eb8db6366d9298f9e6f14b0a02b04e2b1ed464572f8dd2843da0505759 + languageName: node + linkType: hard + +"@storybook/react-docgen-typescript-plugin@npm:1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0": + version: 1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0 + resolution: "@storybook/react-docgen-typescript-plugin@npm:1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0" + dependencies: + debug: ^4.1.1 + endent: ^2.0.1 + find-cache-dir: ^3.3.1 + flat-cache: ^3.0.4 + micromatch: ^4.0.2 + react-docgen-typescript: ^2.1.1 + tslib: ^2.0.0 + peerDependencies: + typescript: ">= 3.x" + webpack: ">= 4" + checksum: 2d3ab49e4858d5f28f36b5bd0e30f3d3450bc7d9865cd4fbe65a35085ae63feff9556a3265b594a2c84b03c66f009dc8b057802f3ca0f76b961d51536835cb8f + languageName: node + linkType: hard + +"@storybook/react@npm:^6.5.12": + version: 6.5.15 + resolution: "@storybook/react@npm:6.5.15" + dependencies: + "@babel/preset-flow": ^7.12.1 + "@babel/preset-react": ^7.12.10 + "@pmmmwh/react-refresh-webpack-plugin": ^0.5.3 + "@storybook/addons": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/core": 6.5.15 + "@storybook/core-common": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/docs-tools": 6.5.15 + "@storybook/node-logger": 6.5.15 + "@storybook/react-docgen-typescript-plugin": 1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0 + "@storybook/semver": ^7.3.2 + "@storybook/store": 6.5.15 + "@types/estree": ^0.0.51 + "@types/node": ^14.14.20 || ^16.0.0 + "@types/webpack-env": ^1.16.0 + acorn: ^7.4.1 + acorn-jsx: ^5.3.1 + acorn-walk: ^7.2.0 + babel-plugin-add-react-displayname: ^0.0.5 + babel-plugin-react-docgen: ^4.2.1 + core-js: ^3.8.2 + escodegen: ^2.0.0 + fs-extra: ^9.0.1 + global: ^4.4.0 + html-tags: ^3.1.0 + lodash: ^4.17.21 + prop-types: ^15.7.2 + react-element-to-jsx-string: ^14.3.4 + react-refresh: ^0.11.0 + read-pkg-up: ^7.0.1 + regenerator-runtime: ^0.13.7 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + webpack: ">=4.43.0 <6.0.0" + peerDependencies: + "@babel/core": ^7.11.5 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + require-from-string: ^2.0.2 + peerDependenciesMeta: + "@babel/core": + optional: true + "@storybook/builder-webpack4": + optional: true + "@storybook/builder-webpack5": + optional: true + "@storybook/manager-webpack4": + optional: true + "@storybook/manager-webpack5": + optional: true + typescript: + optional: true + bin: + build-storybook: bin/build.js + start-storybook: bin/index.js + storybook-server: bin/index.js + checksum: 43ecfa0b875cbc278343e35f63b79bfde1ea3e2c8e04cf573fc8de050d73d7cd2a2736a0ea04a7557c1431ff216bc04b0413172a1209ec58288b695201af50ad + languageName: node + linkType: hard + +"@storybook/router@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/router@npm:6.5.15" + dependencies: + "@storybook/client-logger": 6.5.15 + core-js: ^3.8.2 + memoizerific: ^1.11.3 + qs: ^6.10.0 + regenerator-runtime: ^0.13.7 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: a5b0bd7c7bf8fb180ee45f2734d76082cf7d17dde0f8f6e90c2e64c60d06cca163536fa15489c4e5234b5d50be7cd459cf2dde5ee74a5c1e3c934ab236b53998 + languageName: node + linkType: hard + +"@storybook/semver@npm:^7.3.2": + version: 7.3.2 + resolution: "@storybook/semver@npm:7.3.2" + dependencies: + core-js: ^3.6.5 + find-up: ^4.1.0 + bin: + semver: bin/semver.js + checksum: f90e0c714d694330e9664af96ff7c3806c10981d6754e839caf59cd6791bf38c050caf98b19e97f7b059fd8521217f5f70b941a79b68a40b485e054d46343791 + languageName: node + linkType: hard + +"@storybook/source-loader@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/source-loader@npm:6.5.15" + dependencies: + "@storybook/addons": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + core-js: ^3.8.2 + estraverse: ^5.2.0 + global: ^4.4.0 + loader-utils: ^2.0.4 + lodash: ^4.17.21 + prettier: ">=2.2.1 <=2.3.0" + regenerator-runtime: ^0.13.7 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 98633dcc29379fa8d95d8e109b9a4b208f615c969cb730daf9ac8b17e127660fcb48abf6d7b2dd66743dccad62a2d82e8bfb886226b6f730fd64f2ca5c5b0083 + languageName: node + linkType: hard + +"@storybook/store@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/store@npm:6.5.15" + dependencies: + "@storybook/addons": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + core-js: ^3.8.2 + fast-deep-equal: ^3.1.3 + global: ^4.4.0 + lodash: ^4.17.21 + memoizerific: ^1.11.3 + regenerator-runtime: ^0.13.7 + slash: ^3.0.0 + stable: ^0.1.8 + synchronous-promise: ^2.0.15 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 33c0ec425066e6ae092cf9f8abe70e5f470df405f1d48b2e244de000f97ae93581659b22db4b8d2614753cb562b181ab68c7598268182a9f01c8943de27aa30c + languageName: node + linkType: hard + +"@storybook/telemetry@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/telemetry@npm:6.5.15" + dependencies: + "@storybook/client-logger": 6.5.15 + "@storybook/core-common": 6.5.15 + chalk: ^4.1.0 + core-js: ^3.8.2 + detect-package-manager: ^2.0.1 + fetch-retry: ^5.0.2 + fs-extra: ^9.0.1 + global: ^4.4.0 + isomorphic-unfetch: ^3.1.0 + nanoid: ^3.3.1 + read-pkg-up: ^7.0.1 + regenerator-runtime: ^0.13.7 + checksum: 7c3b5a0f3cf8ccec4cad1392fc793fcc3b5562b42a34016fb6dc17cfc190c49a2f9d39c0d776e6fbc166dfd63e9a63dc9d50b30a27a08f35f1d87773e4f4e75d + languageName: node + linkType: hard + +"@storybook/testing-library@npm:^0.0.13": + version: 0.0.13 + resolution: "@storybook/testing-library@npm:0.0.13" + dependencies: + "@storybook/client-logger": ^6.4.0 + "@storybook/instrumenter": ^6.4.0 + "@testing-library/dom": ^8.3.0 + "@testing-library/user-event": ^13.2.1 + ts-dedent: ^2.2.0 + checksum: d5d5953e825aed706491b44089b1b500dead328b3bf7c364ac911f33346f2890a9c205e701391733114f53a948de0d8490eea1ec13c70611643b1bf3d7636dff + languageName: node + linkType: hard + +"@storybook/theming@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/theming@npm:6.5.15" + dependencies: + "@storybook/client-logger": 6.5.15 + core-js: ^3.8.2 + memoizerific: ^1.11.3 + regenerator-runtime: ^0.13.7 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: d342bedbdc0f2254ea68cb2ea201d25e2d40f4e0a549e8d0a7722ba78af11a8e86fd82f178a852df3065c25c62c10516e0c00d52d9aabb625d0b334c61a3699e + languageName: node + linkType: hard + +"@storybook/ui@npm:6.5.15": + version: 6.5.15 + resolution: "@storybook/ui@npm:6.5.15" + dependencies: + "@storybook/addons": 6.5.15 + "@storybook/api": 6.5.15 + "@storybook/channels": 6.5.15 + "@storybook/client-logger": 6.5.15 + "@storybook/components": 6.5.15 + "@storybook/core-events": 6.5.15 + "@storybook/router": 6.5.15 + "@storybook/semver": ^7.3.2 + "@storybook/theming": 6.5.15 + core-js: ^3.8.2 + memoizerific: ^1.11.3 + qs: ^6.10.0 + regenerator-runtime: ^0.13.7 + resolve-from: ^5.0.0 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 3814895e71b6c98e9410d4e5e47fbb47e2a59ebbd22466ce32d63d97914b14713a4264335b32b8589b45207f2512b237a36cf31395f0568801dd0fa4a8752a46 + languageName: node + linkType: hard + +"@surma/rollup-plugin-off-main-thread@npm:^2.2.3": + version: 2.2.3 + resolution: "@surma/rollup-plugin-off-main-thread@npm:2.2.3" + dependencies: + ejs: ^3.1.6 + json5: ^2.2.0 + magic-string: ^0.25.0 + string.prototype.matchall: ^4.0.6 + checksum: 4f36a7488cdae2907053a48231430e8e9aa8f1903a96131bf8325786afba3224011f9120164cae75043558bd051881050b071958388fe477927d340b1cc1a066 + languageName: node + linkType: hard + +"@svg-maps/world@npm:^1.0.1": + version: 1.0.1 + resolution: "@svg-maps/world@npm:1.0.1" + checksum: ac62778db161779e422e21de4fad01019202f17fd2ae1ec417bbd6a9f52f83633dc338b6ea0a7a0c192782760192cc0575a1545980960e12168fdcded8b19505 + languageName: node + linkType: hard + +"@svgr/babel-plugin-add-jsx-attribute@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:6.5.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: a13ed0797189d5497890530449029bec388310e260a96459e304e2729e7a2cf4d20d34f882d9a77ccce73dd3d36065afbb6987258fdff618d7d57955065a8ad4 + languageName: node + linkType: hard + +"@svgr/babel-plugin-remove-jsx-attribute@npm:*": + version: 6.5.0 + resolution: "@svgr/babel-plugin-remove-jsx-attribute@npm:6.5.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 00870fc9add7ccbabfc839462f80cafa003819240b2c3144907a2d1a9d5c5cb4fd5a8a70377bc35a3a48a435e8f015ecc6bd08cc130ebbbb4fc00e162210e2cc + languageName: node + linkType: hard + +"@svgr/babel-plugin-remove-jsx-empty-expression@npm:*": + version: 6.5.0 + resolution: "@svgr/babel-plugin-remove-jsx-empty-expression@npm:6.5.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 9cfe0f4a1568958027d06e97876350b950f251392f6c6450a1922ea27c681523f1c0d543093f08292a1227a75be8715b0ef3efe7b6df1208937de7ad453a5cf9 + languageName: node + linkType: hard + +"@svgr/babel-plugin-replace-jsx-attribute-value@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:6.5.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 318786787c9a217c33a7340c8856436858e1fffa5a6df635fedc6b9a371f3afea080ea074b9e3cfbbd9dd962ead924fde8bc9855a394c38dd60e391883a58c81 + languageName: node + linkType: hard + +"@svgr/babel-plugin-svg-dynamic-title@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:6.5.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 16ef228c793b909fec47dd7dc05c1c3c2d77a824f42055df37e141e0534081b1bc4aec6dcc51be50c221df9f262f59270fc1c379923bfd4f5db302abafabfd8d + languageName: node + linkType: hard + +"@svgr/babel-plugin-svg-em-dimensions@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:6.5.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: dfdd5cbe6ae543505eaa0da69df0735b7407294c4b0504b3e74c0e7e371f1acb914eb99fd21ff39ef5bd626b3474f064a4cccc50f41b7c556ee834f9a6d6610a + languageName: node + linkType: hard + +"@svgr/babel-plugin-transform-react-native-svg@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:6.5.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 332fbf3bbc19d938b744440dbab9c8acd8f7a2ed6bf9c4e23f40e3f2c25615a60b3bf00902a4f1f6c20b5f382a1547b3acc6f2b2d70d80e532b5d45945f1b979 + languageName: node + linkType: hard + +"@svgr/babel-plugin-transform-svg-component@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/babel-plugin-transform-svg-component@npm:6.5.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 8d9e1c7c62abce23837e53cdacc6d09bc1f1f2b0ad7322105001c097995e9aa8dca4fa41acf39148af69f342e40081c438106949fb083e997ca497cb0448f27d + languageName: node + linkType: hard + +"@svgr/babel-preset@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/babel-preset@npm:6.5.1" + dependencies: + "@svgr/babel-plugin-add-jsx-attribute": ^6.5.1 + "@svgr/babel-plugin-remove-jsx-attribute": "*" + "@svgr/babel-plugin-remove-jsx-empty-expression": "*" + "@svgr/babel-plugin-replace-jsx-attribute-value": ^6.5.1 + "@svgr/babel-plugin-svg-dynamic-title": ^6.5.1 + "@svgr/babel-plugin-svg-em-dimensions": ^6.5.1 + "@svgr/babel-plugin-transform-react-native-svg": ^6.5.1 + "@svgr/babel-plugin-transform-svg-component": ^6.5.1 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 8e8d7a0049279152f9ac308fbfd4ce74063d8a376154718cba6309bae4316318804a32201c75c5839c629f8e1e5d641a87822764000998161d0fc1de24b0374a + languageName: node + linkType: hard + +"@svgr/core@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/core@npm:6.5.1" + dependencies: + "@babel/core": ^7.19.6 + "@svgr/babel-preset": ^6.5.1 + "@svgr/plugin-jsx": ^6.5.1 + camelcase: ^6.2.0 + cosmiconfig: ^7.0.1 + checksum: 60cce11e13391171132115dcc8da592d23e51f155ebadf9b819bd1836b8c13d40aa5c30a03a7d429f65e70a71c50669b2e10c94e4922de4e58bc898275f46c05 + languageName: node + linkType: hard + +"@svgr/hast-util-to-babel-ast@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/hast-util-to-babel-ast@npm:6.5.1" + dependencies: + "@babel/types": ^7.20.0 + entities: ^4.4.0 + checksum: 18fa37b36581ba1678f5cc5a05ce0411e08df4db267f3cd900af7ffdf5bd90522f3a46465f315cd5d7345264949479133930aafdd27ce05c474e63756196256f + languageName: node + linkType: hard + +"@svgr/plugin-jsx@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/plugin-jsx@npm:6.5.1" + dependencies: + "@babel/core": ^7.19.6 + "@svgr/babel-preset": ^6.5.1 + "@svgr/hast-util-to-babel-ast": ^6.5.1 + svg-parser: ^2.0.4 + peerDependencies: + "@svgr/core": ^6.0.0 + checksum: 365da6e43ceeff6b49258fa2fbb3c880210300e4a85ba74831e92d2dc9c53e6ab8dda422dc33fb6a339803227cf8d9a0024ce769401c46fd87209abe36d5ae43 + languageName: node + linkType: hard + +"@svgr/plugin-svgo@npm:^6.5.1": + version: 6.5.1 + resolution: "@svgr/plugin-svgo@npm:6.5.1" + dependencies: + cosmiconfig: ^7.0.1 + deepmerge: ^4.2.2 + svgo: ^2.8.0 + peerDependencies: + "@svgr/core": "*" + checksum: da40e461145af1a92fd2ec50ea64626681fa73786f218497a4b4fb85393a58812999ca2744ee33bb7ab771aa5ce9ab1dbd08a189cb3d7a89fb58fd96913ddf91 + languageName: node + linkType: hard + +"@svgr/webpack@npm:^6.2.1": + version: 6.5.1 + resolution: "@svgr/webpack@npm:6.5.1" + dependencies: + "@babel/core": ^7.19.6 + "@babel/plugin-transform-react-constant-elements": ^7.18.12 + "@babel/preset-env": ^7.19.4 + "@babel/preset-react": ^7.18.6 + "@babel/preset-typescript": ^7.18.6 + "@svgr/core": ^6.5.1 + "@svgr/plugin-jsx": ^6.5.1 + "@svgr/plugin-svgo": ^6.5.1 + checksum: 3e9edfbc2ef3dc07b5f50c9c5ff5c951048511dff9dffb0407e6d15343849dfb36099fc7e1e3911429382cab81f7735a86ba1d6f77d21bb8f9ca33a5dec4824a + languageName: node + linkType: hard + +"@testing-library/dom@npm:^8.3.0": + version: 8.20.0 + resolution: "@testing-library/dom@npm:8.20.0" + dependencies: + "@babel/code-frame": ^7.10.4 + "@babel/runtime": ^7.12.5 + "@types/aria-query": ^5.0.1 + aria-query: ^5.0.0 + chalk: ^4.1.0 + dom-accessibility-api: ^0.5.9 + lz-string: ^1.4.4 + pretty-format: ^27.0.2 + checksum: 454c71f65708d1504256ae055f060adf8ed9eadcc7ddbf6d07a528045beceb842783c1253871759f5c137eca58e5e32700dc64b130cbb37e10e7a52ab08d7b44 + languageName: node + linkType: hard + +"@testing-library/user-event@npm:^13.2.1": + version: 13.5.0 + resolution: "@testing-library/user-event@npm:13.5.0" + dependencies: + "@babel/runtime": ^7.12.5 + peerDependencies: + "@testing-library/dom": ">=7.21.4" + checksum: ff57edaeab31322c80c3f01d55404b4cebb907b9ec7672b96a1a14d053f172046b01c5f27b45677927ebee8ed91bce695a7d09edec9a48875cfacabe39d0426a + languageName: node + linkType: hard + +"@tootallnate/once@npm:2": + version: 2.0.0 + resolution: "@tootallnate/once@npm:2.0.0" + checksum: 073bfa548026b1ebaf1659eb8961e526be22fa77139b10d60e712f46d2f0f05f4e6c8bec62a087d41088ee9e29faa7f54838568e475ab2f776171003c3920858 + languageName: node + linkType: hard + +"@trysound/sax@npm:0.2.0": + version: 0.2.0 + resolution: "@trysound/sax@npm:0.2.0" + checksum: 44907308549ce775a41c38a815f747009ac45929a45d642b836aa6b0a536e4978d30b8d7d680bbd116e9dd73b7dbe2ef0d1369dcfc2d09e83ba381e485ecbe12 + languageName: node + linkType: hard + +"@tsconfig/node10@npm:^1.0.7": + version: 1.0.9 + resolution: "@tsconfig/node10@npm:1.0.9" + checksum: c176a2c1e1b16be120c328300ea910df15fb9a5277010116d26818272341a11483c5a80059389d04edacf6fd2d03d4687ad3660870fdd1cc0b7109e160adb220 + languageName: node + linkType: hard + +"@tsconfig/node12@npm:^1.0.7": + version: 1.0.11 + resolution: "@tsconfig/node12@npm:1.0.11" + checksum: dddca2b553e2bee1308a056705103fc8304e42bb2d2cbd797b84403a223b25c78f2c683ec3e24a095e82cd435387c877239bffcb15a590ba817cd3f6b9a99fd9 + languageName: node + linkType: hard + +"@tsconfig/node14@npm:^1.0.0": + version: 1.0.3 + resolution: "@tsconfig/node14@npm:1.0.3" + checksum: 67c1316d065fdaa32525bc9449ff82c197c4c19092b9663b23213c8cbbf8d88b6ed6a17898e0cbc2711950fbfaf40388938c1c748a2ee89f7234fc9e7fe2bf44 + languageName: node + linkType: hard + +"@tsconfig/node16@npm:^1.0.2": + version: 1.0.3 + resolution: "@tsconfig/node16@npm:1.0.3" + checksum: 451a0d4b2bc35c2cdb30a49b6c699d797b8bbac99b883237659698678076d4193050d90e2ee36016ccbca57075cdb073cadab38cedc45119bac68ab331958cbc + languageName: node + linkType: hard + +"@types/aria-query@npm:^5.0.1": + version: 5.0.1 + resolution: "@types/aria-query@npm:5.0.1" + checksum: bc9e40ce37bd3a1654948778c7829bd55aea1bc5f2cd06fcf6cd650b07bb388995799e9aab6e2d93a6cf55dcba3b85c155f7ba93adefcc7c2e152fc6057061b5 + languageName: node + linkType: hard + +"@types/babel__core@npm:^7.1.14": + version: 7.20.0 + resolution: "@types/babel__core@npm:7.20.0" + dependencies: + "@babel/parser": ^7.20.7 + "@babel/types": ^7.20.7 + "@types/babel__generator": "*" + "@types/babel__template": "*" + "@types/babel__traverse": "*" + checksum: 75dcd39258bc008b6fd4db7de2c8bfeb29b5cd2c726f54407f70243ddea1d8ce9e7082281557614c4a5f9f30d478387ca6ab6cc576fc829cebeb159bfaa8799f + languageName: node + linkType: hard + +"@types/babel__generator@npm:*": + version: 7.6.4 + resolution: "@types/babel__generator@npm:7.6.4" + dependencies: + "@babel/types": ^7.0.0 + checksum: e0051b450e4ba2df0a7e386f08df902a4e920f6f8d6f185d69ddbe9b0e2e2d3ae434bb51e437bc0fca2a9a0f5dc4ca44d3a1941ef75e74371e8be5bf64416fe4 + languageName: node + linkType: hard + +"@types/babel__template@npm:*": + version: 7.4.1 + resolution: "@types/babel__template@npm:7.4.1" + dependencies: + "@babel/parser": ^7.1.0 + "@babel/types": ^7.0.0 + checksum: 6f180e96c39765487f27e861d43eebed341ec7a2fc06cdf5a52c22872fae67f474ca165d149c708f4fd9d5482beb66c0a92f77411b234bb30262ed2303e50b1a + languageName: node + linkType: hard + +"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6": + version: 7.18.3 + resolution: "@types/babel__traverse@npm:7.18.3" + dependencies: + "@babel/types": ^7.3.0 + checksum: 4214fd3e95925d9a7efa01142969a310263430d4f5de89be6c9c193110666677415161b474fa627d751dfd0f1eb7dc1c84c48f8b53098625c6bc78917683215a + languageName: node + linkType: hard + +"@types/body-parser@npm:*": + version: 1.19.2 + resolution: "@types/body-parser@npm:1.19.2" + dependencies: + "@types/connect": "*" + "@types/node": "*" + checksum: c2dd533e1d4af958d656bdba7f376df68437d8dfb7e4522c88b6f3e6f827549e4be5bf0be68a5f1878accf5752ea37fba7e8a4b6dda53d0d122d77e27b69c750 + languageName: node + linkType: hard + +"@types/bonjour@npm:^3.5.9": + version: 3.5.10 + resolution: "@types/bonjour@npm:3.5.10" + dependencies: + "@types/node": "*" + checksum: 5a3d70695a8dfe79c020579fcbf18d7dbb89b8f061dd388c76b68c4797c0fccd71f3e8a9e2bea00afffdb9b37a49dd0ac0a192829d5b655a5b49c66f313a7be8 + languageName: node + linkType: hard + +"@types/connect-history-api-fallback@npm:^1.3.5": + version: 1.3.5 + resolution: "@types/connect-history-api-fallback@npm:1.3.5" + dependencies: + "@types/express-serve-static-core": "*" + "@types/node": "*" + checksum: 06217360db2665fe31351f98d95c1efdbf3919403e748d3a6b4377a79704ef524765ba2ccf499daa9b30fcbe5ef9d08988aee773e89a4998cf47e3800c95b635 + languageName: node + linkType: hard + +"@types/connect@npm:*": + version: 3.4.35 + resolution: "@types/connect@npm:3.4.35" + dependencies: + "@types/node": "*" + checksum: f11a1ccfed540723dddd7cb496543ad40a2f663f22ff825e9b220f0bae86db8b1ced2184ee41d3fb358b019ad6519e39481b06386db91ebb859003ad1d54fe6a + languageName: node + linkType: hard + +"@types/d3-array@npm:^3.0.3": + version: 3.0.4 + resolution: "@types/d3-array@npm:3.0.4" + checksum: e669233f7f341f4947f2c695b7880e28bf413a1e2825d8f10f631308e3aa69e5e92b7b3edcea52b320f0edd2043bd670c323b5ade4b03ba4c8a3e5ca8e140a17 + languageName: node + linkType: hard + +"@types/d3-color@npm:*": + version: 3.1.0 + resolution: "@types/d3-color@npm:3.1.0" + checksum: 5b4be21b4b025da9ffd0cef876fb7d82f99116fa26e7ee3449771faf0a953d160246b1ceb2a9bbc7d131e32ab60d7d19013131d098616369a56f9880f25f20ef + languageName: node + linkType: hard + +"@types/d3-ease@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/d3-ease@npm:3.0.0" + checksum: 8fa64035f3b459cbf178e0bbb01cd188ec7337877e959fcf0a6ef08528b6caf93fd9f69635ec1c8fc11f6d2448d0e5d2a4e11988cb16bc6e001f0c0afe609204 + languageName: node + linkType: hard + +"@types/d3-interpolate@npm:^3.0.1": + version: 3.0.1 + resolution: "@types/d3-interpolate@npm:3.0.1" + dependencies: + "@types/d3-color": "*" + checksum: 1c7577045a4a30dc177bca10980c456a28c9b89b1a5406fe7303824dd9cc898f67f8dafd8e22a7702ca5df12a28a5f48f77d92a9b5d8f1fc0939f33831067114 + languageName: node + linkType: hard + +"@types/d3-path@npm:*": + version: 3.0.0 + resolution: "@types/d3-path@npm:3.0.0" + checksum: 64750aeb3e490112d2f1d812230201140de352743846150e022e44c6924f44d47deb1a50f3dc63b930fd6a8fd6482f8fcb7da2516a14b8e29a4749d2b86f90ca + languageName: node + linkType: hard + +"@types/d3-scale@npm:^4.0.2": + version: 4.0.3 + resolution: "@types/d3-scale@npm:4.0.3" + dependencies: + "@types/d3-time": "*" + checksum: 5eace4cf45f87b3eec9637ade77e97530e778a3bb7f8356e4712bde732fb9474f3e8ef3aa12bc97dd3e4f76e23343ed81c1f5a3a1dcfdb72868f876b418da117 + languageName: node + linkType: hard + +"@types/d3-shape@npm:^3.1.0": + version: 3.1.1 + resolution: "@types/d3-shape@npm:3.1.1" + dependencies: + "@types/d3-path": "*" + checksum: e06f0e6f5d74184dfb6a71861085ffad221bde8a11d2de632649118d75e9605fddf9af664601b0841d794e0c27afd6ea37d652350fb47c196905facc04c284d5 + languageName: node + linkType: hard + +"@types/d3-time@npm:*, @types/d3-time@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/d3-time@npm:3.0.0" + checksum: 4f900608d5c557b09b38e4b096723af5eb4508a1b32f9afae253fe77a4bcbbe821a14225bab1c2ea36ddbc5c4154ab3563452c6b6eba7a9f48cefad94276e6b5 + languageName: node + linkType: hard + +"@types/d3-timer@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/d3-timer@npm:3.0.0" + checksum: 7f6cd693f6c99a360dc01e1b5aa1185cfa8f65d603f537c52c810d475c8ef8aa07ac2f8be24cc489d2e69b843e384ab30dd079ac75011dbc91b21cd216a79502 + languageName: node + linkType: hard + +"@types/eslint-scope@npm:^3.7.3": + version: 3.7.4 + resolution: "@types/eslint-scope@npm:3.7.4" + dependencies: + "@types/eslint": "*" + "@types/estree": "*" + checksum: f8a19cddf9d402f079bcc261958fff5ff2616465e4fb4cd423aa966a6a32bf5d3c65ca3ca0fbe824776b48c5cd525efbaf927b98b8eeef093aa68a1a2ba19359 + languageName: node + linkType: hard + +"@types/eslint@npm:*": + version: 8.4.10 + resolution: "@types/eslint@npm:8.4.10" + dependencies: + "@types/estree": "*" + "@types/json-schema": "*" + checksum: ff245f08f2a687a78314f7f5054af833ea17fc392587196d11c9811efe396f3bdf4aaba20c4be763607315ebb81c68da64f58726d14ab1d2ca4a98aaa758e1c9 + languageName: node + linkType: hard + +"@types/estree@npm:*": + version: 1.0.0 + resolution: "@types/estree@npm:1.0.0" + checksum: 4e73ff606bf7c7ccdaa66092de650c410a4ad2ecc388fdbed8242cac9dbcad72407e1ceff041b7da691babb02ff74ab885d6231fb09368fdd1eabbf1b5253d49 + languageName: node + linkType: hard + +"@types/estree@npm:0.0.39": + version: 0.0.39 + resolution: "@types/estree@npm:0.0.39" + checksum: f0af6c95ac1988c4827964bd9d3b51d24da442e2188943f6dfcb1e1559103d5d024d564b2e9d3f84c53714a02a0a7435c7441138eb63d9af5de4dfc66cdc0d92 + languageName: node + linkType: hard + +"@types/estree@npm:^0.0.51": + version: 0.0.51 + resolution: "@types/estree@npm:0.0.51" + checksum: a70c60d5e634e752fcd45b58c9c046ef22ad59ede4bc93ad5193c7e3b736ebd6bcd788ade59d9c3b7da6eeb0939235f011d4c59bb4fc04d8c346b76035099dd1 + languageName: node + linkType: hard + +"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.31": + version: 4.17.32 + resolution: "@types/express-serve-static-core@npm:4.17.32" + dependencies: + "@types/node": "*" + "@types/qs": "*" + "@types/range-parser": "*" + checksum: 283024a1d8247e4d39ca0f9ff23bbd3b88799dbd508c52b7ea479b21d326a2d0a9c362ec4b91d621d2cb7c5e961eb5a094d79702aeae25b295c6f2a70271c13e + languageName: node + linkType: hard + +"@types/express@npm:*, @types/express@npm:^4.17.13": + version: 4.17.15 + resolution: "@types/express@npm:4.17.15" + dependencies: + "@types/body-parser": "*" + "@types/express-serve-static-core": ^4.17.31 + "@types/qs": "*" + "@types/serve-static": "*" + checksum: a230500a0bcf47d090a6a254873325145ad29fb7513c1688aa7376d3ac3cddf4e6478e77e7c0273349260f26b2767ff403368b8ec4e3e72a6dc17159ba1bddb2 + languageName: node + linkType: hard + +"@types/glob@npm:*, @types/glob@npm:^8.0.0": + version: 8.0.0 + resolution: "@types/glob@npm:8.0.0" + dependencies: + "@types/minimatch": "*" + "@types/node": "*" + checksum: 7a906724c49cbb7e9279a0ddb7051ba39e1944924d5a0cadce7b2656b138465351b5d658cb5658be1964b865464fa66eb3a4a2f3e19ceb4559a4f3d52e08e055 + languageName: node + linkType: hard + +"@types/glob@npm:^7.1.1": + version: 7.2.0 + resolution: "@types/glob@npm:7.2.0" + dependencies: + "@types/minimatch": "*" + "@types/node": "*" + checksum: a8eb5d5cb5c48fc58c7ca3ff1e1ddf771ee07ca5043da6e4871e6757b4472e2e73b4cfef2644c38983174a4bc728c73f8da02845c28a1212f98cabd293ecae98 + languageName: node + linkType: hard + +"@types/graceful-fs@npm:^4.1.2, @types/graceful-fs@npm:^4.1.3": + version: 4.1.6 + resolution: "@types/graceful-fs@npm:4.1.6" + dependencies: + "@types/node": "*" + checksum: b1d32c5ae7bd52cf60e29df20407904c4312a39612e7ec2ee23c1e3731c1cfe31d97c6941bf6cb52f5f929d50d86d92dd506436b63fafa833181d439b628885e + languageName: node + linkType: hard + +"@types/hast@npm:^2.0.0": + version: 2.3.4 + resolution: "@types/hast@npm:2.3.4" + dependencies: + "@types/unist": "*" + checksum: 635cfe9a8e91f6b3c15c9929455d0136ac4d75c5b7f596ce21b453cecdfda785e89b10eb2b2d9da9d43e548b1d65ba3e20c741bbaf83823575c9c45001ade4bb + languageName: node + linkType: hard + +"@types/history@npm:^4.7.11": + version: 4.7.11 + resolution: "@types/history@npm:4.7.11" + checksum: 3facf37c2493d1f92b2e93a22cac7ea70b06351c2ab9aaceaa3c56aa6099fb63516f6c4ec1616deb5c56b4093c026a043ea2d3373e6c0644d55710364d02c934 + languageName: node + linkType: hard + +"@types/hoist-non-react-statics@npm:^3.3.0": + version: 3.3.1 + resolution: "@types/hoist-non-react-statics@npm:3.3.1" + dependencies: + "@types/react": "*" + hoist-non-react-statics: ^3.3.0 + checksum: 5ed808e5fbf0979fe07acd631147420c30319383f4388a57e0fb811c6ff30abef286e937a84c7b00f4647ca7f1ab390cc42af0bfc7547a87d2e59e0e7072d92b + languageName: node + linkType: hard + +"@types/html-minifier-terser@npm:^5.0.0": + version: 5.1.2 + resolution: "@types/html-minifier-terser@npm:5.1.2" + checksum: 13276ac71fbcc89795627cc7fa0234e95402656285df2bda4df83e3ebaa9b30f29905f52aa7786d158a887cef72748cbd897e12628dc396ba1225ce0d18159b9 + languageName: node + linkType: hard + +"@types/html-minifier-terser@npm:^6.0.0": + version: 6.1.0 + resolution: "@types/html-minifier-terser@npm:6.1.0" + checksum: a62fb8588e2f3818d82a2d7b953ad60a4a52fd767ae04671de1c16f5788bd72f1ed3a6109ed63fd190c06a37d919e3c39d8adbc1793a005def76c15a3f5f5dab + languageName: node + linkType: hard + +"@types/http-proxy@npm:^1.17.8": + version: 1.17.9 + resolution: "@types/http-proxy@npm:1.17.9" + dependencies: + "@types/node": "*" + checksum: f9bf3702f34c6de68f981c65b43d58d37f259cd6555403331ca10ec918b3778c28bbecc3f3aab15dd4d6751522b01ddf51a86834db7691fbe8ce94f3d2b1ec58 + languageName: node + linkType: hard + +"@types/is-function@npm:^1.0.0": + version: 1.0.1 + resolution: "@types/is-function@npm:1.0.1" + checksum: 3bd8cf70ef103141bf6bb0404bca6887766615bb655f967a0e46cf301e277b59eaeab8b91ce117331ce5c7c8875050796d06c25bf3da4531e3a4514269373c3c + languageName: node + linkType: hard + +"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": + version: 2.0.4 + resolution: "@types/istanbul-lib-coverage@npm:2.0.4" + checksum: af5f6b64e788331ed3f7b2e2613cb6ca659c58b8500be94bbda8c995ad3da9216c006f1cfe6f66b321c39392b1bda18b16e63cef090a77d24a00b4bd5ba3b018 + languageName: node + linkType: hard + +"@types/istanbul-lib-report@npm:*": + version: 3.0.0 + resolution: "@types/istanbul-lib-report@npm:3.0.0" + dependencies: + "@types/istanbul-lib-coverage": "*" + checksum: 7ced458631276a28082ee40645224c3cdd8b861961039ff811d841069171c987ec7e50bc221845ec0d04df0022b2f457a21fb2f816dab2fbe64d59377b32031f + languageName: node + linkType: hard + +"@types/istanbul-reports@npm:^3.0.0": + version: 3.0.1 + resolution: "@types/istanbul-reports@npm:3.0.1" + dependencies: + "@types/istanbul-lib-report": "*" + checksum: e147f0db9346a0cae9a359220bc76f7c78509fb6979a2597feb24d64b6e8328d2d26f9d152abbd59c6bca721e4ea2530af20116d01df50815efafd1e151fd777 + languageName: node + linkType: hard + +"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.4, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": + version: 7.0.11 + resolution: "@types/json-schema@npm:7.0.11" + checksum: bd1f9a7b898ff15c4bb494eb19124f2d688b804c39f07cbf135ac73f35324970e9e8329b72aae1fb543d925ea295a1568b23056c26658cecec4741fa28c3b81a + languageName: node + linkType: hard + +"@types/keyv@npm:^3.1.1": + version: 3.1.4 + resolution: "@types/keyv@npm:3.1.4" + dependencies: + "@types/node": "*" + checksum: ff8f54fc49621210291f815fe5b15d809fd7d032941b3180743440bd507ecdf08b9e844625fa346af568c84bf34114eb378dcdc3e921a08ba1e2a08d7e3c809c + languageName: node + linkType: hard + +"@types/lodash@npm:^4.14.167": + version: 4.14.191 + resolution: "@types/lodash@npm:4.14.191" + checksum: cca0bc3b5f1193a50a39a9c6e14892e7cf57ab81ca05985e1ccee0d732dd3b22a8d669fa87560757051c6d73f4f63a2e2248ce9a7d9c159d0d5e3f331baf6a62 + languageName: node + linkType: hard + +"@types/luxon@npm:^3.0.0": + version: 3.2.0 + resolution: "@types/luxon@npm:3.2.0" + checksum: d3574082d70cee8bad6ba683e5b81d4532b5ddcaefb986f0f4bd770fb71072456241d81c780cadf4857163119148d1ca6f8df36389446827eae17a609f4ed5c6 + languageName: node + linkType: hard + +"@types/mdast@npm:^3.0.0": + version: 3.0.10 + resolution: "@types/mdast@npm:3.0.10" + dependencies: + "@types/unist": "*" + checksum: 375f08b3910505291b2815d9edf55dca63c6c4ec58dd33c866521e68905fd4e8fe83b397e167af2cdd3799b851a7e02817d58610cfb814aee20bf3c52d87be9b + languageName: node + linkType: hard + +"@types/mime@npm:*": + version: 3.0.1 + resolution: "@types/mime@npm:3.0.1" + checksum: c4c0fc89042822a3b5ffd6ef0da7006513454ee8376ffa492372d17d2925a4e4b1b194c977b718c711df38b33eb9d06deb5dbf9f851bcfb7e5e65f06b2a87f97 + languageName: node + linkType: hard + +"@types/minimatch@npm:*": + version: 5.1.2 + resolution: "@types/minimatch@npm:5.1.2" + checksum: 83cf1c11748891b714e129de0585af4c55dd4c2cafb1f1d5233d79246e5e1e19d1b5ad9e8db449667b3ffa2b6c80125c429dbee1054e9efb45758dbc4e118562 + languageName: node + linkType: hard + +"@types/node-fetch@npm:^2.5.7": + version: 2.6.2 + resolution: "@types/node-fetch@npm:2.6.2" + dependencies: + "@types/node": "*" + form-data: ^3.0.0 + checksum: bd2ce7621905f9d80cd2fbe003d32a8d304f4aa53c12eb01a498255a1fc570d82216cff9a7ed38ff32570c78e46c924a8e23187a011ecfcfec4c530c7bdecdbb + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 18.11.18 + resolution: "@types/node@npm:18.11.18" + checksum: 1a308a03a7c1cfced411fc3187e49a507ab2b3ce7cdf0794b7511c8843aae5a8dbe52b16ba4217d2da1c8e3c50920c272b45fecff4cccc26606be7f42c33f4f5 + languageName: node + linkType: hard + +"@types/node@npm:^10.14.33": + version: 10.17.60 + resolution: "@types/node@npm:10.17.60" + checksum: 0742294912a6e79786cdee9ed77cff6ee8ff007b55d8e21170fc3e5994ad3a8101fea741898091876f8dc32b0a5ae3d64537b7176799e92da56346028d2cbcd2 + languageName: node + linkType: hard + +"@types/node@npm:^14.0.10 || ^16.0.0, @types/node@npm:^14.14.20 || ^16.0.0": + version: 16.18.11 + resolution: "@types/node@npm:16.18.11" + checksum: 7bdf5e865a7959a72881ede19a882219f9d0baadf9ef8fdf24523291d401a7fc43bf91aa3223b1961ca54e1363f542cc4d60c8b00a70b457b2e9439b82adac70 + languageName: node + linkType: hard + +"@types/node@npm:^14.14.31": + version: 14.18.36 + resolution: "@types/node@npm:14.18.36" + checksum: 9742b62fee705cbb177b681e849640834ea3651ea90c7bfea07462dc7511eb73116a12b32c01157b591ebaaed9ece8042016f2a78b50396883f1358a18903fe2 + languageName: node + linkType: hard + +"@types/normalize-package-data@npm:^2.4.0": + version: 2.4.1 + resolution: "@types/normalize-package-data@npm:2.4.1" + checksum: c90b163741f27a1a4c3b1869d7d5c272adbd355eb50d5f060f9ce122ce4342cf35f5b0005f55ef780596cacfeb69b7eee54cd3c2e02d37f75e664945b6e75fc6 + languageName: node + linkType: hard + +"@types/npmlog@npm:^4.1.2": + version: 4.1.4 + resolution: "@types/npmlog@npm:4.1.4" + checksum: 5670c38db51f2c4a5da05657920d7597bf9da00615920b42a001fde9011f05ab1e8ec8cbc671abb47915028e3e0ace5c3d981c23684c202a00bf518f16ece01d + languageName: node + linkType: hard + +"@types/parse-json@npm:^4.0.0": + version: 4.0.0 + resolution: "@types/parse-json@npm:4.0.0" + checksum: 1d3012ab2fcdad1ba313e1d065b737578f6506c8958e2a7a5bdbdef517c7e930796cb1599ee067d5dee942fb3a764df64b5eef7e9ae98548d776e86dcffba985 + languageName: node + linkType: hard + +"@types/parse5@npm:^5.0.0": + version: 5.0.3 + resolution: "@types/parse5@npm:5.0.3" + checksum: 7d7ebbcb704a0ef438aa0de43ea1fd9723dfa802b8fa459628ceaf063f092bd19791b2a2580265244898dcc9d40f7345588a76cf752847d29540539f802711ed + languageName: node + linkType: hard + +"@types/prettier@npm:^2.1.5": + version: 2.7.2 + resolution: "@types/prettier@npm:2.7.2" + checksum: 16ffbd1135c10027f118517d3b12aaaf3936be1f3c6e4c6c9c03d26d82077c2d86bf0dcad545417896f29e7d90faf058aae5c9db2e868be64298c644492ea29e + languageName: node + linkType: hard + +"@types/pretty-hrtime@npm:^1.0.0": + version: 1.0.1 + resolution: "@types/pretty-hrtime@npm:1.0.1" + checksum: e990110a3626e987319092c5149d5ea244785b83fbbd8e62605714ec1fa4317a3524ae0b6381cdc2ca92619d9a451b3fe9ff4085c42826f5398e3380d3031bff + languageName: node + linkType: hard + +"@types/prop-types@npm:*": + version: 15.7.5 + resolution: "@types/prop-types@npm:15.7.5" + checksum: 648aae41423821c61c83823ae36116c8d0f68258f8b609bdbc257752dcd616438d6343d554262aa9a7edaee5a19aca2e028a74fa2d0f40fffaf2816bc7056857 + languageName: node + linkType: hard + +"@types/qs@npm:*, @types/qs@npm:^6.9.5": + version: 6.9.7 + resolution: "@types/qs@npm:6.9.7" + checksum: 157eb05f4c75790b0ebdcf7b0547ff117feabc8cda03c3cac3d3ea82bb19a1912e76a411df3eb0bdd01026a9770f07bc0e7e3fbe39ebb31c1be4564c16be35f1 + languageName: node + linkType: hard + +"@types/raf@npm:^3.4.0": + version: 3.4.0 + resolution: "@types/raf@npm:3.4.0" + checksum: 05dd8550b33e8f3a74b5f041f4b1e3403d6a5cb7e8782e921238490680c4a63ddff65571aa56998fb34f43589d44d772fc60fd7754030375eacbc7c52bf26f4d + languageName: node + linkType: hard + +"@types/range-parser@npm:*": + version: 1.2.4 + resolution: "@types/range-parser@npm:1.2.4" + checksum: 8e3c3cda88675efd9145241bcb454449715b7d015a7fb80d018dcb3d441fa1938b302242cc0dfa6b02c5d014dd8bc082ae90091e62b1e816cae3ec36c2a7dbcb + languageName: node + linkType: hard + +"@types/react-dom@npm:^18.0.4": + version: 18.0.10 + resolution: "@types/react-dom@npm:18.0.10" + dependencies: + "@types/react": "*" + checksum: a07b900a2d5559830f88b3e525cf279f9f04b4893f4d17e64f5adb08d8abe0e3151e0d3c0ea17d836104ae47594be577529a004265600e4304a43a93b0d5d61e + languageName: node + linkType: hard + +"@types/react-redux@npm:^7.1.24": + version: 7.1.25 + resolution: "@types/react-redux@npm:7.1.25" + dependencies: + "@types/hoist-non-react-statics": ^3.3.0 + "@types/react": "*" + hoist-non-react-statics: ^3.3.0 + redux: ^4.0.0 + checksum: a9fe2da54df720339fb24c27fd3f9e8b61a04e7296e139f1f23dc6444b20dfe2ba74237d431fae6ef9f62bf0f6ef1b0c5155575c2d82c1e9586da023cdc151b6 + languageName: node + linkType: hard + +"@types/react-router-dom@npm:^5.3.3": + version: 5.3.3 + resolution: "@types/react-router-dom@npm:5.3.3" + dependencies: + "@types/history": ^4.7.11 + "@types/react": "*" + "@types/react-router": "*" + checksum: a9231a16afb9ed5142678147eafec9d48582809295754fb60946e29fcd3757a4c7a3180fa94b45763e4c7f6e3f02379e2fcb8dd986db479dcab40eff5fc62a91 + languageName: node + linkType: hard + +"@types/react-router@npm:*": + version: 5.1.20 + resolution: "@types/react-router@npm:5.1.20" + dependencies: + "@types/history": ^4.7.11 + "@types/react": "*" + checksum: 1f7eee61981d2f807fa01a34a0ef98ebc0774023832b6611a69c7f28fdff01de5a38cabf399f32e376bf8099dcb7afaf724775bea9d38870224492bea4cb5737 + languageName: node + linkType: hard + +"@types/react-transition-group@npm:^4.4.0": + version: 4.4.5 + resolution: "@types/react-transition-group@npm:4.4.5" + dependencies: + "@types/react": "*" + checksum: c0d81634ca5e1efac3ca6f6f006245976d584833ab9e933edf08b66551c1c7b9f0bc7878897f57ba44b137d3754583d623c932fe4b7721840ae5218ec2414942 + languageName: node + linkType: hard + +"@types/react-virtualized@npm:^9.21.21": + version: 9.21.21 + resolution: "@types/react-virtualized@npm:9.21.21" + dependencies: + "@types/prop-types": "*" + "@types/react": ^17 + checksum: eee986fede0e932a539cdc2eadaf03279861fc2107f5d0283c69b348c2b21ed59edf496c9864d665095a65cdde0f3bebc7b2bbbe2277e85c7f8a71e2e4f9b1d0 + languageName: node + linkType: hard + +"@types/react@npm:*, @types/react@npm:^18.0.9": + version: 18.0.27 + resolution: "@types/react@npm:18.0.27" + dependencies: + "@types/prop-types": "*" + "@types/scheduler": "*" + csstype: ^3.0.2 + checksum: 87711931b14cdca07c4149531b4c96e7a56609ed2d010c52bf6bde14b417ac128c326924b14b25667fdaeddacfe98c31fa9625a95af52dae45061df8e30b46a3 + languageName: node + linkType: hard + +"@types/react@npm:^17": + version: 17.0.53 + resolution: "@types/react@npm:17.0.53" + dependencies: + "@types/prop-types": "*" + "@types/scheduler": "*" + csstype: ^3.0.2 + checksum: 4ff3fa58ab32deefacdbd0ee002e294548391238ce2dea2cad213e11b4cc61480be48cb2b7ed3c53220ee1a5b9cadf3417a20db817ab2ca4669c9a4a45042b92 + languageName: node + linkType: hard + +"@types/resolve@npm:1.17.1": + version: 1.17.1 + resolution: "@types/resolve@npm:1.17.1" + dependencies: + "@types/node": "*" + checksum: 6eeb9c27d99bf4b393bf168d43208f63e78cefca5644662a0bdb2bdbf8352386f4f3aca66add138fc41bce5f66fd48a0de430a1473f11b612fbed0375ae78031 + languageName: node + linkType: hard + +"@types/responselike@npm:^1.0.0": + version: 1.0.0 + resolution: "@types/responselike@npm:1.0.0" + dependencies: + "@types/node": "*" + checksum: 474ac2402e6d43c007eee25f50d01eb1f67255ca83dd8e036877292bbe8dd5d2d1e50b54b408e233b50a8c38e681ff3ebeaf22f18b478056eddb65536abb003a + languageName: node + linkType: hard + +"@types/retry@npm:0.12.0": + version: 0.12.0 + resolution: "@types/retry@npm:0.12.0" + checksum: 7c5c9086369826f569b83a4683661557cab1361bac0897a1cefa1a915ff739acd10ca0d62b01071046fe3f5a3f7f2aec80785fe283b75602dc6726781ea3e328 + languageName: node + linkType: hard + +"@types/scheduler@npm:*": + version: 0.16.2 + resolution: "@types/scheduler@npm:0.16.2" + checksum: 89a3a922f03609b61c270d534226791edeedcb1b06f0225d5543ac17830254624ef9d8a97ad05418e4ce549dd545bddf1ff28cb90658ff10721ad14556ca68a5 + languageName: node + linkType: hard + +"@types/semver@npm:^7.3.12": + version: 7.3.13 + resolution: "@types/semver@npm:7.3.13" + checksum: 73295bb1fee46f8c76c7a759feeae5a3022f5bedfdc17d16982092e4b33af17560234fb94861560c20992a702a1e1b9a173bb623a96f95f80892105f5e7d25e3 + languageName: node + linkType: hard + +"@types/serve-index@npm:^1.9.1": + version: 1.9.1 + resolution: "@types/serve-index@npm:1.9.1" + dependencies: + "@types/express": "*" + checksum: ed1ac8407101a787ebf09164a81bc24248ccf9d9789cd4eaa360a9a06163e5d2168c46ab0ddf2007e47b455182ecaa7632a886639919d9d409a27f7aef4e847a + languageName: node + linkType: hard + +"@types/serve-static@npm:*, @types/serve-static@npm:^1.13.10": + version: 1.15.0 + resolution: "@types/serve-static@npm:1.15.0" + dependencies: + "@types/mime": "*" + "@types/node": "*" + checksum: 2bdf7561c74175cc57c912d360fe763af0fc77a078f67d22cb515fa5b23db937314ffe1b5f96ca77c5e9de55b9d94277b7a3d288ff07067d6b2f83d004027430 + languageName: node + linkType: hard + +"@types/sinonjs__fake-timers@npm:8.1.1": + version: 8.1.1 + resolution: "@types/sinonjs__fake-timers@npm:8.1.1" + checksum: e2e6c425a548177c0930c2f9b82d3951956c9701b9ebf59623d5ad2c3229c523d3c0d598e79fe7392a239657abd3dbe3676be0650ce438bcd1199ee3b617a4d7 + languageName: node + linkType: hard + +"@types/sizzle@npm:^2.3.2": + version: 2.3.3 + resolution: "@types/sizzle@npm:2.3.3" + checksum: a19de697d2d444c0a3e3cdbfb303b337aeef9dc54b8bdb4a2f15b1fbd7ab1f7b7bf85065b17b5d2da48ea80d38d659fa213ae706880787ff92323e9fce76d841 + languageName: node + linkType: hard + +"@types/sockjs@npm:^0.3.33": + version: 0.3.33 + resolution: "@types/sockjs@npm:0.3.33" + dependencies: + "@types/node": "*" + checksum: 75b9b2839970ebab3e557955b9e2b1091d87cefabee1023e566bccc093411acc4a1402f3da4fde18aca44f5b9c42fe0626afd073a2140002b9b53eb71a084e4d + languageName: node + linkType: hard + +"@types/source-list-map@npm:*": + version: 0.1.2 + resolution: "@types/source-list-map@npm:0.1.2" + checksum: 0538ce317294febf40ed3fc3a2e483fa4aee8ba85584a66e5ed9c0af9ea48a348960bc467076643cb56aeafdd7d2252e90c75e68ef664c0477ec87ea0554ffdc + languageName: node + linkType: hard + +"@types/stack-utils@npm:^2.0.0": + version: 2.0.1 + resolution: "@types/stack-utils@npm:2.0.1" + checksum: 3327ee919a840ffe907bbd5c1d07dfd79137dd9732d2d466cf717ceec5bb21f66296173c53bb56cff95fae4185b9cd6770df3e9745fe4ba528bbc4975f54d13f + languageName: node + linkType: hard + +"@types/tapable@npm:^1, @types/tapable@npm:^1.0.5": + version: 1.0.8 + resolution: "@types/tapable@npm:1.0.8" + checksum: 01f77d47bac8aaeee7ed298e8e74eb012a28f920106c3c359e1f2730512cd810f2c6165cd2cd769422ae1064e2bf1072778b27fb5ec1973e18c35e2cc1ed5c8d + languageName: node + linkType: hard + +"@types/trusted-types@npm:^2.0.2": + version: 2.0.2 + resolution: "@types/trusted-types@npm:2.0.2" + checksum: 8c5253d7a297ba375b1dff7704013fb8d31c08c681d257db9e7e0624309cbb4a1e0c916bdd5a8c378992391126af0adb731720ba7642244a2f2c1ff42aba5bcf + languageName: node + linkType: hard + +"@types/uglify-js@npm:*": + version: 3.17.1 + resolution: "@types/uglify-js@npm:3.17.1" + dependencies: + source-map: ^0.6.1 + checksum: c19a44017a901ab15f862e6645023bf96ffb7502b7305a15ee811667693ec66a997a42d5d0ba67814de537b562dafd26230142e26c9bb9f840ee8bb7f798cbcc + languageName: node + linkType: hard + +"@types/unist@npm:*, @types/unist@npm:^2.0.0, @types/unist@npm:^2.0.2, @types/unist@npm:^2.0.3": + version: 2.0.6 + resolution: "@types/unist@npm:2.0.6" + checksum: 8690789328e8e10c487334341fcf879fd49f8987c98ce49849f9871052f95d87477735171bb661e6f551bdb95235e015dfdad1867ca1d9b5b88a053f72ac40eb + languageName: node + linkType: hard + +"@types/webpack-env@npm:^1.16.0": + version: 1.18.0 + resolution: "@types/webpack-env@npm:1.18.0" + checksum: ac2e91b025ea31b98b0271ccb40da789c5b54693c1efb18265a54c03b303b750501f080a44e99d7ce0a5422d68d535e600e851df4b8f2240444eca7ac3159719 + languageName: node + linkType: hard + +"@types/webpack-sources@npm:*": + version: 3.2.0 + resolution: "@types/webpack-sources@npm:3.2.0" + dependencies: + "@types/node": "*" + "@types/source-list-map": "*" + source-map: ^0.7.3 + checksum: 17716e9f03fa63362f92d510bb9119313bac3a7985321e0fe9326dc30ebe598cb2c85b8c7cdc4f4d34d783c4c45e74e3ec08e209f9c9dab27bf188c3def32706 + languageName: node + linkType: hard + +"@types/webpack@npm:^4.41.26, @types/webpack@npm:^4.41.8": + version: 4.41.33 + resolution: "@types/webpack@npm:4.41.33" + dependencies: + "@types/node": "*" + "@types/tapable": ^1 + "@types/uglify-js": "*" + "@types/webpack-sources": "*" + anymatch: ^3.0.0 + source-map: ^0.6.0 + checksum: dc6db66fa84664d8fab7ea79bd2482ea1c4500b09ed6939258e205548501b8d29c06b0fe5e869c4b59f74acf884c61a391875dadb9f7a91c8cd10c3841143729 + languageName: node + linkType: hard + +"@types/ws@npm:^8.5.1": + version: 8.5.4 + resolution: "@types/ws@npm:8.5.4" + dependencies: + "@types/node": "*" + checksum: dd8bde7d69296037b5053d9c644ce3a86a988e6cb8a632e36f5040e9e274c8879a10c13ac7fe163e4eb11a85f5b8c46fe6ce5f257b80cc93118494336f4e26c6 + languageName: node + linkType: hard + +"@types/yargs-parser@npm:*": + version: 21.0.0 + resolution: "@types/yargs-parser@npm:21.0.0" + checksum: cb89f3bb2e8002f1479a65a934e825be4cc18c50b350bbc656405d41cf90b8a299b105e7da497d7eb1aa460472a07d1e5a389f3af0862f1d1252279cfcdd017c + languageName: node + linkType: hard + +"@types/yargs@npm:^15.0.0": + version: 15.0.15 + resolution: "@types/yargs@npm:15.0.15" + dependencies: + "@types/yargs-parser": "*" + checksum: b52519ba68a8d90996b54143ff74fcd8ac1722a1ef4a50ed8c3dbc1f7a76d14210f0262f8b91eabcdab202ff4babdd92ce7332ab1cdd6af4eae7c9fc81c83797 + languageName: node + linkType: hard + +"@types/yargs@npm:^16.0.0": + version: 16.0.5 + resolution: "@types/yargs@npm:16.0.5" + dependencies: + "@types/yargs-parser": "*" + checksum: 7b2824c749b6e28f5ee3248d13b244eaf7d3c5bb96089add774997572b5a10f1a0826d29a7bc797d64d29ca504b0b0d6ba2e74931b3fabae78ccbbcf07282f0c + languageName: node + linkType: hard + +"@types/yargs@npm:^17.0.8": + version: 17.0.20 + resolution: "@types/yargs@npm:17.0.20" + dependencies: + "@types/yargs-parser": "*" + checksum: 1f6b1378da6f52f8195bd25c15f5b22187e2dc419b8234f42accb6ed8787bfad2e4c1c3ee0217b574dcb01a4a903ef5f5dde1aa8e1fed4fb1df9ae4c36766e66 + languageName: node + linkType: hard + +"@types/yauzl@npm:^2.9.1": + version: 2.10.0 + resolution: "@types/yauzl@npm:2.10.0" + dependencies: + "@types/node": "*" + checksum: e917cf11c78e9ca7d037d0e6e0d6d5d99443d9d7201f8f1a556f02a2bc57ae457487e9bfec89dfa848d16979b35de6e5b34840d4d0bb9e5f33849d077ac15154 + languageName: node + linkType: hard + +"@typescript-eslint/eslint-plugin@npm:^5.24.0": + version: 5.48.2 + resolution: "@typescript-eslint/eslint-plugin@npm:5.48.2" + dependencies: + "@typescript-eslint/scope-manager": 5.48.2 + "@typescript-eslint/type-utils": 5.48.2 + "@typescript-eslint/utils": 5.48.2 + debug: ^4.3.4 + ignore: ^5.2.0 + natural-compare-lite: ^1.4.0 + regexpp: ^3.2.0 + semver: ^7.3.7 + tsutils: ^3.21.0 + peerDependencies: + "@typescript-eslint/parser": ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 03823453c2c10bf839cfc31e99a489856dc366b5d20231acb78d9499e8ff951452ed23ee350d2674d2af124dc7b1e19f3dae4ce05b8887dab72d74fbf2c47727 + languageName: node + linkType: hard + +"@typescript-eslint/parser@npm:^5.24.0": + version: 5.48.2 + resolution: "@typescript-eslint/parser@npm:5.48.2" + dependencies: + "@typescript-eslint/scope-manager": 5.48.2 + "@typescript-eslint/types": 5.48.2 + "@typescript-eslint/typescript-estree": 5.48.2 + debug: ^4.3.4 + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 5b7c2b82b5e726eb5c845717cea14549075b269e609bbbcd14648e066daad8315e9e6fbe33bcd28d5887e2e68137bf41f39f132fb555de569effe8477e4d7a18 + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:5.48.2": + version: 5.48.2 + resolution: "@typescript-eslint/scope-manager@npm:5.48.2" + dependencies: + "@typescript-eslint/types": 5.48.2 + "@typescript-eslint/visitor-keys": 5.48.2 + checksum: 926cfe3f3f75d88482e0af6937f93a1ce5dbb2ee26e5fee0cde03667c453a13fb6578bcd0f88153bba63f80df39fa855bc406d9af4b1764180f3b4cf45cac164 + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:5.48.2": + version: 5.48.2 + resolution: "@typescript-eslint/type-utils@npm:5.48.2" + dependencies: + "@typescript-eslint/typescript-estree": 5.48.2 + "@typescript-eslint/utils": 5.48.2 + debug: ^4.3.4 + tsutils: ^3.21.0 + peerDependencies: + eslint: "*" + peerDependenciesMeta: + typescript: + optional: true + checksum: b15b27616ed34a01d22f25af80b634f98230545f4458af2eaa1e3e539bf1705f8d7ea44a454d47c0bcac764acc0082b89931044a20e0a04dc9e8b035684efd6d + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:5.48.2": + version: 5.48.2 + resolution: "@typescript-eslint/types@npm:5.48.2" + checksum: 7e912a6148d8241073db5e7b6d371ef5f6cc4aecfe64f1ea2d7f2c634bf37ab25ae7928de83b2f224d5602ca618269c6c7cf24a7d16c446d618883527a173b42 + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:5.48.2": + version: 5.48.2 + resolution: "@typescript-eslint/typescript-estree@npm:5.48.2" + dependencies: + "@typescript-eslint/types": 5.48.2 + "@typescript-eslint/visitor-keys": 5.48.2 + debug: ^4.3.4 + globby: ^11.1.0 + is-glob: ^4.0.3 + semver: ^7.3.7 + tsutils: ^3.21.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: c9480726c3e36a6ebe887bb44c0e18275a1536f13b4a1d6a70bda673e236529b4eb5b445dcdb410c3126efd781eb7ce9f20b10470a4115da5764ff8b33d0179d + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:5.48.2": + version: 5.48.2 + resolution: "@typescript-eslint/utils@npm:5.48.2" + dependencies: + "@types/json-schema": ^7.0.9 + "@types/semver": ^7.3.12 + "@typescript-eslint/scope-manager": 5.48.2 + "@typescript-eslint/types": 5.48.2 + "@typescript-eslint/typescript-estree": 5.48.2 + eslint-scope: ^5.1.1 + eslint-utils: ^3.0.0 + semver: ^7.3.7 + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 447f8d8c8fb4fcfba12e87ffc6253f2bba1eaa50c0592fc6fcb82c0e53582ce317dbb1fa5485cb0b6801977000a992f7849c21b70db39d2cc27cae1eaacfd82e + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:5.48.2": + version: 5.48.2 + resolution: "@typescript-eslint/visitor-keys@npm:5.48.2" + dependencies: + "@typescript-eslint/types": 5.48.2 + eslint-visitor-keys: ^3.3.0 + checksum: 9659e19778bd7d0b463e32eb052f00c52b23d9a8d4570676dde9440a99d5c0f82482ef9dcf65bd64568ad3e7c0a23b3ec1d522c641ee4f714d82c9921868d58d + languageName: node + linkType: hard + +"@webassemblyjs/ast@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/ast@npm:1.11.1" + dependencies: + "@webassemblyjs/helper-numbers": 1.11.1 + "@webassemblyjs/helper-wasm-bytecode": 1.11.1 + checksum: 6f75b09f17a29e704d2343967c53128cda7c84af2d192a3146de1b53cafaedfe568eca0804bd6c1acc72e1269477ae22d772de1dcf605cdb0adf9768f31d88d7 + languageName: node + linkType: hard + +"@webassemblyjs/ast@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/ast@npm:1.9.0" + dependencies: + "@webassemblyjs/helper-module-context": 1.9.0 + "@webassemblyjs/helper-wasm-bytecode": 1.9.0 + "@webassemblyjs/wast-parser": 1.9.0 + checksum: 8246c714346cdcd3ab204a2b09904d9d36c4f7da8f30cc217b0b7272a3ef57a3c21e95d51b26601641133fb66fea5cc46c357cf897808512f13b3d1c2efe88e4 + languageName: node + linkType: hard + +"@webassemblyjs/floating-point-hex-parser@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.11.1" + checksum: 9644d9f7163d25aa301cf3be246e35cca9c472b70feda0593b1a43f30525c68d70bfb4b7f24624cd8e259579f1dee32ef28670adaeb3ab1314ffb52a25b831d5 + languageName: node + linkType: hard + +"@webassemblyjs/floating-point-hex-parser@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.9.0" + checksum: 17acfdfe6650691ae8d0279e6ff4fb8b5efce64e12f3fa18c6a7d279968cc72eb21c0db7ebb5be9d627d05fa7014cef087843d999de96c917079f57d7dac8f77 + languageName: node + linkType: hard + +"@webassemblyjs/helper-api-error@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/helper-api-error@npm:1.11.1" + checksum: 23e6f24100eb21779cd4dcc7c4231fd511622545a7638b195098bcfee79decb54a7e2b3295a12056c3042af7a5d8d62d4023a9194c9cba0311acb304ea20a292 + languageName: node + linkType: hard + +"@webassemblyjs/helper-api-error@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/helper-api-error@npm:1.9.0" + checksum: 892851b25cf4b4b307490328f45858414326dac667ca15244b5e959fa6e22478b29dabeb581d49ef8a2874e291d0417a3a959be70428c39cd40870e73b394dbc + languageName: node + linkType: hard + +"@webassemblyjs/helper-buffer@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/helper-buffer@npm:1.11.1" + checksum: ab662fc94a017538c538836387492567ed9f23fe4485a86de1834d61834e4327c24659830e1ecd2eea7690ce031a148b59c4724873dc5d3c0bdb71605c7d01af + languageName: node + linkType: hard + +"@webassemblyjs/helper-buffer@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/helper-buffer@npm:1.9.0" + checksum: b09a3e27d9127ccaab095bd171336e7675bb5b832e05b701ff174a853b763154a49f5382c4c3f2f1cc746b1cff3f2025452145cf807ddf788133bcccf5920ca8 + languageName: node + linkType: hard + +"@webassemblyjs/helper-code-frame@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/helper-code-frame@npm:1.9.0" + dependencies: + "@webassemblyjs/wast-printer": 1.9.0 + checksum: 010969a6c8b016680a9b1383ff4b8147c363608dd1e29602154e5460954af4fd48daed518a76b232ca43935d4b6bebf54fba38da56f809e2bd12f063d84013ec + languageName: node + linkType: hard + +"@webassemblyjs/helper-fsm@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/helper-fsm@npm:1.9.0" + checksum: ef0c99b58716d757a1a41f99fb46578d3f07d97b60cd51deaeffdf0aad09ec47f5093ee8d098d12324d57f8812609704c377fccfe9a32d02c0a658a4a33dce94 + languageName: node + linkType: hard + +"@webassemblyjs/helper-module-context@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/helper-module-context@npm:1.9.0" + dependencies: + "@webassemblyjs/ast": 1.9.0 + checksum: 130a9ac1141770b9f70ad568ec2dc769e92c756f91b06ece9cda2c2a5e80e21ec9c8c2a945a5839bf379e52fa921ae134245a7492e1b9ae0e8c557bb9b4953c3 + languageName: node + linkType: hard + +"@webassemblyjs/helper-numbers@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/helper-numbers@npm:1.11.1" + dependencies: + "@webassemblyjs/floating-point-hex-parser": 1.11.1 + "@webassemblyjs/helper-api-error": 1.11.1 + "@xtuc/long": 4.2.2 + checksum: 8cc7ced66dad8f968a68fbad551ba50562993cefa1add67b31ca6462bb986f7b21b5d7c6444c05dd39312126e10ac48def025dec6277ce0734665191e05acde7 + languageName: node + linkType: hard + +"@webassemblyjs/helper-wasm-bytecode@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.11.1" + checksum: f14e2bd836fed1420fe7507919767de16346a013bbac97b6b6794993594f37b5f0591d824866a7b32f47524cef8a4a300e5f914952ff2b0ff28659714400c793 + languageName: node + linkType: hard + +"@webassemblyjs/helper-wasm-bytecode@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.9.0" + checksum: 1741993e1c723f56b619a4981ec975f903886aa3f1f50c7bdb2eaa45ca4ad8d023d6ae7413ef643f060567b1f12a9dcfad6c43688879c46ee4f0b53aa71cd5c9 + languageName: node + linkType: hard + +"@webassemblyjs/helper-wasm-section@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/helper-wasm-section@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/helper-buffer": 1.11.1 + "@webassemblyjs/helper-wasm-bytecode": 1.11.1 + "@webassemblyjs/wasm-gen": 1.11.1 + checksum: e2da4192a843e96c8bf5156cea23193c9dbe12a1440c9c109d3393828f46753faab75fac78ecfe965aa7988723ad9b0b12f3ca0b9e4de75294980e67515460af + languageName: node + linkType: hard + +"@webassemblyjs/helper-wasm-section@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/helper-wasm-section@npm:1.9.0" + dependencies: + "@webassemblyjs/ast": 1.9.0 + "@webassemblyjs/helper-buffer": 1.9.0 + "@webassemblyjs/helper-wasm-bytecode": 1.9.0 + "@webassemblyjs/wasm-gen": 1.9.0 + checksum: 2a5baa7749c50a4a428f372ab88b7e52956b48798d44e7291b4aa8558b247337dba791112ce8a4f5b2281e1b9014e6d44d0141476a5fcde6016fac2e009671e8 + languageName: node + linkType: hard + +"@webassemblyjs/ieee754@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/ieee754@npm:1.11.1" + dependencies: + "@xtuc/ieee754": ^1.2.0 + checksum: 13d6a6ca2e9f35265f10b549cb8354f31a307a7480bbf76c0f4bc8b02e13d5556fb29456cef3815db490effc602c59f98cb0505090ca9e29d7dc61539762a065 + languageName: node + linkType: hard + +"@webassemblyjs/ieee754@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/ieee754@npm:1.9.0" + dependencies: + "@xtuc/ieee754": ^1.2.0 + checksum: 0eff34ec7048400b30282ab9af6ad19d2852dab2f5ffaec8bdc697b8380bc2c9dbe6cadf65f49e68242c82ee3caa8aa6e46c89dbfdab37615189b4da2eab3819 + languageName: node + linkType: hard + +"@webassemblyjs/leb128@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/leb128@npm:1.11.1" + dependencies: + "@xtuc/long": 4.2.2 + checksum: e505edb5de61f13c6c66c57380ae16e95db9d7c43a41ac132e298426bcead9c90622e3d3035fb63df09d0eeabafd471be35ba583fca72ac2e776ab537dda6883 + languageName: node + linkType: hard + +"@webassemblyjs/leb128@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/leb128@npm:1.9.0" + dependencies: + "@xtuc/long": 4.2.2 + checksum: 441be8634733b33b710f44d4394552d6290bb1a0a8311b384b1865b58c3549d0ddeaf1c3985bbee024a8df12c597be3580fc1cde2ae003dcbf26762b493a7a2f + languageName: node + linkType: hard + +"@webassemblyjs/utf8@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/utf8@npm:1.11.1" + checksum: a7c13c7c82d525fe774f51a4fc1da058b0e2c73345eed9e2d6fbeb96ba50c1942daf97e0ff394e7a4d0f26b705f9587cb14681870086d51f02abc78ff6ce3703 + languageName: node + linkType: hard + +"@webassemblyjs/utf8@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/utf8@npm:1.9.0" + checksum: 9566689a1bcf555d6b79d0da79e24ff2be23c0395e5a19ed3c2ceca7831e50b867e0b1c66b3ff1b1d7f297b2d2414314967a884a77634ad0acff8a78489e2b19 + languageName: node + linkType: hard + +"@webassemblyjs/wasm-edit@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/wasm-edit@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/helper-buffer": 1.11.1 + "@webassemblyjs/helper-wasm-bytecode": 1.11.1 + "@webassemblyjs/helper-wasm-section": 1.11.1 + "@webassemblyjs/wasm-gen": 1.11.1 + "@webassemblyjs/wasm-opt": 1.11.1 + "@webassemblyjs/wasm-parser": 1.11.1 + "@webassemblyjs/wast-printer": 1.11.1 + checksum: 10bef22579f96f8c0934aa9fbf6f0d9110563f9c1a510100a84fdfa3dbd9126fdc10bfc12e7ce3ace0ba081e6789eac533c81698faab75859b3a41e97b5ab3bc + languageName: node + linkType: hard + +"@webassemblyjs/wasm-edit@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/wasm-edit@npm:1.9.0" + dependencies: + "@webassemblyjs/ast": 1.9.0 + "@webassemblyjs/helper-buffer": 1.9.0 + "@webassemblyjs/helper-wasm-bytecode": 1.9.0 + "@webassemblyjs/helper-wasm-section": 1.9.0 + "@webassemblyjs/wasm-gen": 1.9.0 + "@webassemblyjs/wasm-opt": 1.9.0 + "@webassemblyjs/wasm-parser": 1.9.0 + "@webassemblyjs/wast-printer": 1.9.0 + checksum: 07f4cb4a73989622c524f9264b6afe664d33354f081499f04db675aed2b79498bd43600c3d7bebcb9f93ccce6a094b3c28f3f7b11ea62e9e82074c2ae68dc058 + languageName: node + linkType: hard + +"@webassemblyjs/wasm-gen@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/wasm-gen@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/helper-wasm-bytecode": 1.11.1 + "@webassemblyjs/ieee754": 1.11.1 + "@webassemblyjs/leb128": 1.11.1 + "@webassemblyjs/utf8": 1.11.1 + checksum: 4e49a19e302e19a2a2438e87ae85805acf39a7d93f9ac0ab65620ae395894937ceb762fa328acbe259d2e60d252cbb87a40ec2b4c088f3149be23fa69ddbf855 + languageName: node + linkType: hard + +"@webassemblyjs/wasm-gen@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/wasm-gen@npm:1.9.0" + dependencies: + "@webassemblyjs/ast": 1.9.0 + "@webassemblyjs/helper-wasm-bytecode": 1.9.0 + "@webassemblyjs/ieee754": 1.9.0 + "@webassemblyjs/leb128": 1.9.0 + "@webassemblyjs/utf8": 1.9.0 + checksum: 876826bef91f3af9e48118fb269c348871d5b6f019e071065556da56a3a5818630b00133e07c9dd2cc767e7f2c70934f3ed0060330ce3e37910e9c9df25f1600 + languageName: node + linkType: hard + +"@webassemblyjs/wasm-opt@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/wasm-opt@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/helper-buffer": 1.11.1 + "@webassemblyjs/wasm-gen": 1.11.1 + "@webassemblyjs/wasm-parser": 1.11.1 + checksum: af7fd6bcb942baafda3b8cc1e574062d01c582aaa12d4f0ea62ff8e83ce1317f06a79c16313a3bc98625e1226d0fc49ba90edac18c21a64c75e9cd114306f07a + languageName: node + linkType: hard + +"@webassemblyjs/wasm-opt@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/wasm-opt@npm:1.9.0" + dependencies: + "@webassemblyjs/ast": 1.9.0 + "@webassemblyjs/helper-buffer": 1.9.0 + "@webassemblyjs/wasm-gen": 1.9.0 + "@webassemblyjs/wasm-parser": 1.9.0 + checksum: 3d5558e078b660cd9777950f2df60f005f3cbdbcfa6c8c19dc0cf012f44f5bfa97c991d7ac26b3e78596bad0538e92dd00b5db4b51ebc373da8e329a03639190 + languageName: node + linkType: hard + +"@webassemblyjs/wasm-parser@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/wasm-parser@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/helper-api-error": 1.11.1 + "@webassemblyjs/helper-wasm-bytecode": 1.11.1 + "@webassemblyjs/ieee754": 1.11.1 + "@webassemblyjs/leb128": 1.11.1 + "@webassemblyjs/utf8": 1.11.1 + checksum: 5a7e8ad36176347f3bc9aee15860a7002b608c181012128ea3e5a1199649d6722e05e029fdf2a73485f2ab3e2f7386b3e0dce46ff9cfd1918417a4ee1151f21e + languageName: node + linkType: hard + +"@webassemblyjs/wasm-parser@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/wasm-parser@npm:1.9.0" + dependencies: + "@webassemblyjs/ast": 1.9.0 + "@webassemblyjs/helper-api-error": 1.9.0 + "@webassemblyjs/helper-wasm-bytecode": 1.9.0 + "@webassemblyjs/ieee754": 1.9.0 + "@webassemblyjs/leb128": 1.9.0 + "@webassemblyjs/utf8": 1.9.0 + checksum: 1e8615b9f9c3c431c9635c9a9884bca89eff1ab2383ad849341c23e09899454482a8f8813d33bf86ee1b0acc97c7c83926961a9b34d4804fa5d559610ab0a4a2 + languageName: node + linkType: hard + +"@webassemblyjs/wast-parser@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/wast-parser@npm:1.9.0" + dependencies: + "@webassemblyjs/ast": 1.9.0 + "@webassemblyjs/floating-point-hex-parser": 1.9.0 + "@webassemblyjs/helper-api-error": 1.9.0 + "@webassemblyjs/helper-code-frame": 1.9.0 + "@webassemblyjs/helper-fsm": 1.9.0 + "@xtuc/long": 4.2.2 + checksum: c79952466fdf7816be527b1db102952b777b12318eabb5c40df074cd8361e3a7b0179a985534fa8b5a7b93668b07ba46875ffeb5da03ca5177c80ba960ebdffc + languageName: node + linkType: hard + +"@webassemblyjs/wast-printer@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/wast-printer@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@xtuc/long": 4.2.2 + checksum: cede13c53a176198f949e7f0edf921047c524472b2e4c99edfe829d20e168b4037395479325635b4a3662ea7b4b59be4555ea3bb6050c61b823c68abdb435c74 + languageName: node + linkType: hard + +"@webassemblyjs/wast-printer@npm:1.9.0": + version: 1.9.0 + resolution: "@webassemblyjs/wast-printer@npm:1.9.0" + dependencies: + "@webassemblyjs/ast": 1.9.0 + "@webassemblyjs/wast-parser": 1.9.0 + "@xtuc/long": 4.2.2 + checksum: f3d106aa884cbb7687307db7adeb3b98abff9de81b9ba8c1065267340b5e9de64ffc533044ab916b1f4ce8a67fb03efa54b29b61c8e908abe4c07edf82f614cd + languageName: node + linkType: hard + +"@webpack-cli/configtest@npm:^1.2.0": + version: 1.2.0 + resolution: "@webpack-cli/configtest@npm:1.2.0" + peerDependencies: + webpack: 4.x.x || 5.x.x + webpack-cli: 4.x.x + checksum: 560e4dbd92fc6e4f574654fb1325b90d02c634bcdf8564c22b0e44c1ecf8db828fbea9f20d0546fa809002bd27b1b6f544f74b13bd5ccdee64e8e9368df46cc2 + languageName: node + linkType: hard + +"@webpack-cli/info@npm:^1.5.0": + version: 1.5.0 + resolution: "@webpack-cli/info@npm:1.5.0" + dependencies: + envinfo: ^7.7.3 + peerDependencies: + webpack-cli: 4.x.x + checksum: 3e7c7ceb30b15fecdf5b5492494fbc76accee27748445c04f2bf66d0c036793b59ae7c27f5f4f6013a500aeae82762244c51f49c1de3d046e0b2dcfe163b642b + languageName: node + linkType: hard + +"@webpack-cli/serve@npm:^1.7.0": + version: 1.7.0 + resolution: "@webpack-cli/serve@npm:1.7.0" + peerDependencies: + webpack-cli: 4.x.x + peerDependenciesMeta: + webpack-dev-server: + optional: true + checksum: a2045c6ada073c517820424f97264a99c809e8bfdef866f5af7ceaefff44580351e9713b06d68e326469bd170111e370942825adcdac7eb242b2ee4343458a81 + languageName: node + linkType: hard + +"@xtuc/ieee754@npm:^1.2.0": + version: 1.2.0 + resolution: "@xtuc/ieee754@npm:1.2.0" + checksum: a8565d29d135039bd99ae4b2220d3e167d22cf53f867e491ed479b3f84f895742d0097f935b19aab90265a23d5d46711e4204f14c479ae3637fbf06c4666882f + languageName: node + linkType: hard + +"@xtuc/long@npm:4.2.2": + version: 4.2.2 + resolution: "@xtuc/long@npm:4.2.2" + checksum: 8582cbc69c79ad2d31568c412129bf23d2b1210a1dfb60c82d5a1df93334da4ee51f3057051658569e2c196d8dc33bc05ae6b974a711d0d16e801e1d0647ccd1 + languageName: node + linkType: hard + +"@zxing/text-encoding@npm:0.9.0": + version: 0.9.0 + resolution: "@zxing/text-encoding@npm:0.9.0" + checksum: d15bff181d46c2ab709e7242801a8d40408aa8c19b44462e5f60e766bf59105b44957914ab6baab60d10d466a5e965f21fe890c67dfdb7d5c7f940df457b4d0d + languageName: node + linkType: hard + +"JSONStream@npm:^1.3.4, JSONStream@npm:^1.3.5": + version: 1.3.5 + resolution: "JSONStream@npm:1.3.5" + dependencies: + jsonparse: ^1.2.0 + through: ">=2.2.7 <3" + bin: + JSONStream: ./bin.js + checksum: 0f54694da32224d57b715385d4a6b668d2117379d1f3223dc758459246cca58fdc4c628b83e8a8883334e454a0a30aa198ede77c788b55537c1844f686a751f2 + languageName: node + linkType: hard + +"abbrev@npm:1, abbrev@npm:^1.0.0, abbrev@npm:~1.1.1": + version: 1.1.1 + resolution: "abbrev@npm:1.1.1" + checksum: 3f762677702acb24f65e813070e306c61fafe25d4b2583f9dfc935131f774863f3addd5741572ed576bd69cabe473c5af18e1e108b829cb7b6b4747884f726e6 + languageName: node + linkType: hard + +"accepts@npm:~1.3.4, accepts@npm:~1.3.5, accepts@npm:~1.3.8": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" + dependencies: + mime-types: ~2.1.34 + negotiator: 0.6.3 + checksum: 3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 + languageName: node + linkType: hard + +"acorn-import-assertions@npm:^1.7.6": + version: 1.8.0 + resolution: "acorn-import-assertions@npm:1.8.0" + peerDependencies: + acorn: ^8 + checksum: ad8e177a177dcda35a91cca2dc54a7cf6958211c14af2b48e4685a5e752d4782779d367e1d5e275700ad5767834d0063edf2ba85aeafb98d7398f8ebf957e7f5 + languageName: node + linkType: hard + +"acorn-jsx@npm:^5.3.1, acorn-jsx@npm:^5.3.2": + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 + languageName: node + linkType: hard + +"acorn-node@npm:^1.8.2": + version: 1.8.2 + resolution: "acorn-node@npm:1.8.2" + dependencies: + acorn: ^7.0.0 + acorn-walk: ^7.0.0 + xtend: ^4.0.2 + checksum: e9a20dae515701cd3d03812929a7f74c4363fdcb4c74d762f7c43566dc87175ad817aa281ba11c88dabf5e8d35aec590073393c02a04bbdcfda58c2f320d08ac + languageName: node + linkType: hard + +"acorn-walk@npm:^7.0.0, acorn-walk@npm:^7.2.0": + version: 7.2.0 + resolution: "acorn-walk@npm:7.2.0" + checksum: ff99f3406ed8826f7d6ef6ac76b7608f099d45a1ff53229fa267125da1924188dbacf02e7903dfcfd2ae4af46f7be8847dc7d564c73c4e230dfb69c8ea8e6b4c + languageName: node + linkType: hard + +"acorn-walk@npm:^8.0.0, acorn-walk@npm:^8.1.1": + version: 8.2.0 + resolution: "acorn-walk@npm:8.2.0" + checksum: dbe92f5b2452c93e960c5594e666dd1fae141b965ff2cb4a1e1d0381e3e4db4274c5ce4ffa3d681a86ca2a8d4e29d5efc0670a08e23fd2800051ea387df56ca2 + languageName: node + linkType: hard + +"acorn@npm:^6.4.1": + version: 6.4.2 + resolution: "acorn@npm:6.4.2" + bin: + acorn: bin/acorn + checksum: 52a72d5d785fa64a95880f2951021a38954f8f69a4944dfeab6fb1449b0f02293eae109a56d55b58ff31a90a00d16a804658a12db8ef834c20b3d1201fe5ba5b + languageName: node + linkType: hard + +"acorn@npm:^7.0.0, acorn@npm:^7.4.1": + version: 7.4.1 + resolution: "acorn@npm:7.4.1" + bin: + acorn: bin/acorn + checksum: bd0b2c2b0f334bbee48828ff897c12bd2eb5898d03bf556dcc8942022cec795ac5bb5b6b585e2de687db6231faf07e096b59a361231dd8c9344d5df5f7f0e526 + languageName: node + linkType: hard + +"acorn@npm:^8.0.4, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.7.1, acorn@npm:^8.8.0": + version: 8.8.1 + resolution: "acorn@npm:8.8.1" + bin: + acorn: bin/acorn + checksum: 9fd00e3373ecd6c7e8f6adfb3216f5bc9ac050e6fc4ef932f03dbd1d45ccc08289ae16fc9eec10c5de8f1ca65b5f70c02635e1e9015d109dae96fdede340abf5 + languageName: node + linkType: hard + +"add-dom-event-listener@npm:^1.1.0": + version: 1.1.0 + resolution: "add-dom-event-listener@npm:1.1.0" + dependencies: + object-assign: 4.x + checksum: 79e490bebebbc1dbded6d86240d1532cd319a4cdd2b7682e46411bd6224bb2d3ea41661eeccebbc53a004005dac8edaaf5c56c7981d3697ec8c5c83008f2b6e7 + languageName: node + linkType: hard + +"address@npm:^1.0.1": + version: 1.2.2 + resolution: "address@npm:1.2.2" + checksum: 1c8056b77fb124456997b78ed682ecc19d2fd7ea8bd5850a2aa8c3e3134c913847c57bcae418622efd32ba858fa1e242a40a251ac31da0515664fc0ac03a047d + languageName: node + linkType: hard + +"agent-base@npm:4, agent-base@npm:^4.3.0": + version: 4.3.0 + resolution: "agent-base@npm:4.3.0" + dependencies: + es6-promisify: ^5.0.0 + checksum: a618d4e4ca7c0c2023b2664346570773455c501a930718764f65016a8a9eea6d2ab5ba54255589e46de529bab4026a088523dce17f94e34ba385af1f644febe1 + languageName: node + linkType: hard + +"agent-base@npm:6, agent-base@npm:^6.0.2": + version: 6.0.2 + resolution: "agent-base@npm:6.0.2" + dependencies: + debug: 4 + checksum: dc4f757e40b5f3e3d674bc9beb4f1048f4ee83af189bae39be99f57bf1f48dde166a8b0a5342a84b5944ee8e6ed1e5a9d801858f4ad44764e84957122fe46261 + languageName: node + linkType: hard + +"agent-base@npm:~4.2.1": + version: 4.2.1 + resolution: "agent-base@npm:4.2.1" + dependencies: + es6-promisify: ^5.0.0 + checksum: f60aaf82e40bc3668baac64c9063615452bfd24f56a2c14161ac1b725c9662a6567841f9afa7d1fd9a055411a8110cd165f2eedae8fbb708abefe3d6645acd3d + languageName: node + linkType: hard + +"agentkeepalive@npm:^3.4.1": + version: 3.5.2 + resolution: "agentkeepalive@npm:3.5.2" + dependencies: + humanize-ms: ^1.2.1 + checksum: b062f6e4b129522e3dd42fc5e771babdab9ff3e754cf46f851ca6fe4161b0f0c236d88e9910ec7fdb50ac7c2c91cad29029cd9f08c71219bd9e13b3e52fcba06 + languageName: node + linkType: hard + +"agentkeepalive@npm:^4.2.1": + version: 4.2.1 + resolution: "agentkeepalive@npm:4.2.1" + dependencies: + debug: ^4.1.0 + depd: ^1.1.2 + humanize-ms: ^1.2.1 + checksum: 259dafa84a9e1f9e277ac8b31995a7a4f4db36a1df1710e9d413d98c6c013ab81370ad585d92038045cc8657662e578b07fd60b312b212f59ad426b10e1d6dce + languageName: node + linkType: hard + +"aggregate-error@npm:^3.0.0": + version: 3.1.0 + resolution: "aggregate-error@npm:3.1.0" + dependencies: + clean-stack: ^2.0.0 + indent-string: ^4.0.0 + checksum: a42f67faa79e3e6687a4923050e7c9807db3848a037076f791d10e092677d65c1d2d863b7848560699f40fc0502c19f40963fb1cd1fb3d338a7423df8e45e039 + languageName: node + linkType: hard + +"airbnb-js-shims@npm:^2.2.1": + version: 2.2.1 + resolution: "airbnb-js-shims@npm:2.2.1" + dependencies: + array-includes: ^3.0.3 + array.prototype.flat: ^1.2.1 + array.prototype.flatmap: ^1.2.1 + es5-shim: ^4.5.13 + es6-shim: ^0.35.5 + function.prototype.name: ^1.1.0 + globalthis: ^1.0.0 + object.entries: ^1.1.0 + object.fromentries: ^2.0.0 || ^1.0.0 + object.getownpropertydescriptors: ^2.0.3 + object.values: ^1.1.0 + promise.allsettled: ^1.0.0 + promise.prototype.finally: ^3.1.0 + string.prototype.matchall: ^4.0.0 || ^3.0.1 + string.prototype.padend: ^3.0.0 + string.prototype.padstart: ^3.0.0 + symbol.prototype.description: ^1.0.0 + checksum: 55fdeb2673da440772d278816664b8e8da946b57adfd95b6635bc980ad235e388266c1488fdff3a055e95dc1c789e389821598b2711b59ce76ab4500f528216c + languageName: node + linkType: hard + +"ajv-errors@npm:^1.0.0": + version: 1.0.1 + resolution: "ajv-errors@npm:1.0.1" + peerDependencies: + ajv: ">=5.0.0" + checksum: de2d6e8100c8707ea063ee4785d53adf599b457c0d4f72c3592244d67ad16448a6d35f7ce45f12bdd2819939447c876e8ef2f1c0800896d7f2aa25c3838acdf1 + languageName: node + linkType: hard + +"ajv-formats@npm:^2.1.1": + version: 2.1.1 + resolution: "ajv-formats@npm:2.1.1" + dependencies: + ajv: ^8.0.0 + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + checksum: e43ba22e91b6a48d96224b83d260d3a3a561b42d391f8d3c6d2c1559f9aa5b253bfb306bc94bbeca1d967c014e15a6efe9a207309e95b3eaae07fcbcdc2af662 + languageName: node + linkType: hard + +"ajv-keywords@npm:^3.1.0, ajv-keywords@npm:^3.4.1, ajv-keywords@npm:^3.5.2": + version: 3.5.2 + resolution: "ajv-keywords@npm:3.5.2" + peerDependencies: + ajv: ^6.9.1 + checksum: 0c57a47cbd656e8cdfd99d7c2264de5868918ffa207c8d7a72a7f63379d4333254b2ba03d69e3c035e996a3fd3eb6d5725d7a1597cca10694296e32510546360 + languageName: node + linkType: hard + +"ajv-keywords@npm:^5.0.0": + version: 5.1.0 + resolution: "ajv-keywords@npm:5.1.0" + dependencies: + fast-deep-equal: ^3.1.3 + peerDependencies: + ajv: ^8.8.2 + checksum: 18bec51f0171b83123ba1d8883c126e60c6f420cef885250898bf77a8d3e65e3bfb9e8564f497e30bdbe762a83e0d144a36931328616a973ee669dc74d4a9590 + languageName: node + linkType: hard + +"ajv@npm:^6.1.0, ajv@npm:^6.10.0, ajv@npm:^6.10.2, ajv@npm:^6.12.2, ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.12.5": + version: 6.12.6 + resolution: "ajv@npm:6.12.6" + 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 + checksum: 41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71 + languageName: node + linkType: hard + +"ajv@npm:^8.0.0, ajv@npm:^8.6.0, ajv@npm:^8.8.0": + version: 8.12.0 + resolution: "ajv@npm:8.12.0" + dependencies: + fast-deep-equal: ^3.1.1 + json-schema-traverse: ^1.0.0 + require-from-string: ^2.0.2 + uri-js: ^4.2.2 + checksum: ac4f72adf727ee425e049bc9d8b31d4a57e1c90da8d28bcd23d60781b12fcd6fc3d68db5df16994c57b78b94eed7988f5a6b482fd376dc5b084125e20a0a622e + languageName: node + linkType: hard + +"ansi-align@npm:^2.0.0": + version: 2.0.0 + resolution: "ansi-align@npm:2.0.0" + dependencies: + string-width: ^2.0.0 + checksum: a3e3e5e58146ca7da775b919a480b8588ea76d0c91aa03c055535de25f327a09b72f7c10be2de6c84a6e1d8387b89bd9b4c98321b1759fdf4c2767c6f10ecd29 + languageName: node + linkType: hard + +"ansi-align@npm:^3.0.0": + version: 3.0.1 + resolution: "ansi-align@npm:3.0.1" + dependencies: + string-width: ^4.1.0 + checksum: ad8b755a253a1bc8234eb341e0cec68a857ab18bf97ba2bda529e86f6e30460416523e0ec58c32e5c21f0ca470d779503244892873a5895dbd0c39c788e82467 + languageName: node + linkType: hard + +"ansi-colors@npm:^3.0.0": + version: 3.2.4 + resolution: "ansi-colors@npm:3.2.4" + checksum: 1785466547bac3b1cb8055325a415c8c946a818669da4fd3d1247cab7617b845b221c2ae04756277074d278b52d90efd67f73d2dd927c7a0d1a10395c1b7665b + languageName: node + linkType: hard + +"ansi-colors@npm:^4.1.1": + version: 4.1.3 + resolution: "ansi-colors@npm:4.1.3" + checksum: ec87a2f59902f74e61eada7f6e6fe20094a628dab765cfdbd03c3477599368768cffccdb5d3bb19a1b6c99126783a143b1fee31aab729b31ffe5836c7e5e28b9 + languageName: node + linkType: hard + +"ansi-escapes@npm:^4.1.0, ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.0": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: ^0.21.3 + checksum: da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 + languageName: node + linkType: hard + +"ansi-html-community@npm:0.0.8, ansi-html-community@npm:^0.0.8": + version: 0.0.8 + resolution: "ansi-html-community@npm:0.0.8" + bin: + ansi-html: bin/ansi-html + checksum: 45d3a6f0b4f10b04fdd44bef62972e2470bfd917bf00439471fa7473d92d7cbe31369c73db863cc45dda115cb42527f39e232e9256115534b8ee5806b0caeed4 + languageName: node + linkType: hard + +"ansi-regex@npm:^2.0.0": + version: 2.1.1 + resolution: "ansi-regex@npm:2.1.1" + checksum: 78cebaf50bce2cb96341a7230adf28d804611da3ce6bf338efa7b72f06cc6ff648e29f80cd95e582617ba58d5fdbec38abfeed3500a98bce8381a9daec7c548b + languageName: node + linkType: hard + +"ansi-regex@npm:^3.0.0": + version: 3.0.1 + resolution: "ansi-regex@npm:3.0.1" + checksum: d108a7498b8568caf4a46eea4f1784ab4e0dfb2e3f3938c697dee21443d622d765c958f2b7e2b9f6b9e55e2e2af0584eaa9915d51782b89a841c28e744e7a167 + languageName: node + linkType: hard + +"ansi-regex@npm:^4.1.0": + version: 4.1.1 + resolution: "ansi-regex@npm:4.1.1" + checksum: d36d34234d077e8770169d980fed7b2f3724bfa2a01da150ccd75ef9707c80e883d27cdf7a0eac2f145ac1d10a785a8a855cffd05b85f778629a0db62e7033da + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"ansi-styles@npm:^2.2.1": + version: 2.2.1 + resolution: "ansi-styles@npm:2.2.1" + checksum: 7c68aed4f1857389e7a12f85537ea5b40d832656babbf511cc7ecd9efc52889b9c3e5653a71a6aade783c3c5e0aa223ad4ff8e83c27ac8a666514e6c79068cab + languageName: node + linkType: hard + +"ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1": + version: 3.2.1 + resolution: "ansi-styles@npm:3.2.1" + dependencies: + color-convert: ^1.9.0 + checksum: ece5a8ef069fcc5298f67e3f4771a663129abd174ea2dfa87923a2be2abf6cd367ef72ac87942da00ce85bd1d651d4cd8595aebdb1b385889b89b205860e977b + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: ^2.0.1 + checksum: 895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"ansi-styles@npm:^5.0.0": + version: 5.2.0 + resolution: "ansi-styles@npm:5.2.0" + checksum: 9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df + languageName: node + linkType: hard + +"ansi-to-html@npm:^0.6.11": + version: 0.6.15 + resolution: "ansi-to-html@npm:0.6.15" + dependencies: + entities: ^2.0.0 + bin: + ansi-to-html: bin/ansi-to-html + checksum: 50fa836c3bec74b5f3d8ea630a86cad972e6463203be30171ed65073afa5f3e70946de2d0e129eb5cab391b489e99972aab3aa4fded3da45c4bd7c265bfae6f5 + languageName: node + linkType: hard + +"ansicolors@npm:~0.3.2": + version: 0.3.2 + resolution: "ansicolors@npm:0.3.2" + checksum: e202182895e959c5357db6c60791b2abaade99fcc02221da11a581b26a7f83dc084392bc74e4d3875c22f37b3c9ef48842e896e3bfed394ec278194b8003e0ac + languageName: node + linkType: hard + +"ansistyles@npm:~0.1.3": + version: 0.1.3 + resolution: "ansistyles@npm:0.1.3" + checksum: dae21dfb76c217ed37b31c9d202b8bdee77b5ca88e9b74f7a88f0208815148d857b8443f17a761c08157f39efa1b6e5f45bb4114a79d82acf31b29ce0dd91328 + languageName: node + linkType: hard + +"anymatch@npm:^2.0.0": + version: 2.0.0 + resolution: "anymatch@npm:2.0.0" + dependencies: + micromatch: ^3.1.4 + normalize-path: ^2.1.1 + checksum: a0d745e52f0233048724b9c9d7b1d8a650f7a50151a0f1d2cce1857b09fd096052d334f8c570cc88596edef8249ae778f767db94025cd00f81e154a37bb7e34e + languageName: node + linkType: hard + +"anymatch@npm:^3.0.0, anymatch@npm:^3.0.3, anymatch@npm:~3.1.2": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: ^3.0.0 + picomatch: ^2.0.4 + checksum: 57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac + languageName: node + linkType: hard + +"app-path@npm:^3.2.0": + version: 3.3.0 + resolution: "app-path@npm:3.3.0" + dependencies: + execa: ^1.0.0 + checksum: 404bd84e73c02087574d8b923915eb7f3d1e9c7723b8b8a50789933e01101800281fd8da0b0dd7b1beed5c2370e0e0c8dab574fe5f93b13355c6f70cf26844f9 + languageName: node + linkType: hard + +"app-root-dir@npm:^1.0.2": + version: 1.0.2 + resolution: "app-root-dir@npm:1.0.2" + checksum: 0225e4be7788968a82bb76df9b14b0d7f212a5c12e8c625cdc34f80548780bcbfc5f3287d0806dddd83bf9dbf9ce302e76b2887cd3a6f4be52b79df7f3aa9e7c + languageName: node + linkType: hard + +"aproba@npm:^1.0.3 || ^2.0.0, aproba@npm:^1.1.2 || 2, aproba@npm:^2.0.0": + version: 2.0.0 + resolution: "aproba@npm:2.0.0" + checksum: d06e26384a8f6245d8c8896e138c0388824e259a329e0c9f196b4fa533c82502a6fd449586e3604950a0c42921832a458bb3aa0aa9f0ba449cfd4f50fd0d09b5 + languageName: node + linkType: hard + +"aproba@npm:^1.0.3, aproba@npm:^1.1.1, aproba@npm:^1.1.2": + version: 1.2.0 + resolution: "aproba@npm:1.2.0" + checksum: 2d34f008c9edfa991f42fe4b667d541d38a474a39ae0e24805350486d76744cd91ee45313283c1d39a055b14026dd0fc4d0cbfc13f210855d59d7e8b5a61dc51 + languageName: node + linkType: hard + +"arch@npm:^2.2.0": + version: 2.2.0 + resolution: "arch@npm:2.2.0" + checksum: 4ceaf8d8207817c216ebc4469742052cb0a097bc45d9b7fcd60b7507220da545a28562ab5bdd4dfe87921bb56371a0805da4e10d704e01f93a15f83240f1284c + languageName: node + linkType: hard + +"archy@npm:~1.0.0": + version: 1.0.0 + resolution: "archy@npm:1.0.0" + checksum: 200c849dd1c304ea9914827b0555e7e1e90982302d574153e28637db1a663c53de62bad96df42d50e8ce7fc18d05e3437d9aa8c4b383803763755f0956c7d308 + languageName: node + linkType: hard + +"are-we-there-yet@npm:^2.0.0": + version: 2.0.0 + resolution: "are-we-there-yet@npm:2.0.0" + dependencies: + delegates: ^1.0.0 + readable-stream: ^3.6.0 + checksum: 375f753c10329153c8d66dc95e8f8b6c7cc2aa66e05cb0960bd69092b10dae22900cacc7d653ad11d26b3ecbdbfe1e8bfb6ccf0265ba8077a7d979970f16b99c + languageName: node + linkType: hard + +"are-we-there-yet@npm:^3.0.0": + version: 3.0.1 + resolution: "are-we-there-yet@npm:3.0.1" + dependencies: + delegates: ^1.0.0 + readable-stream: ^3.6.0 + checksum: 8373f289ba42e4b5ec713bb585acdac14b5702c75f2a458dc985b9e4fa5762bc5b46b40a21b72418a3ed0cfb5e35bdc317ef1ae132f3035f633d581dd03168c3 + languageName: node + linkType: hard + +"are-we-there-yet@npm:~1.1.2": + version: 1.1.7 + resolution: "are-we-there-yet@npm:1.1.7" + dependencies: + delegates: ^1.0.0 + readable-stream: ^2.0.6 + checksum: 03cb45f2892767773c86a616205fc67feb8dfdd56685d1b34999cfa6c0d2aebe73ec0e6ba88a406422b998dea24138337fdb9a3f9b172d7c2a7f75d02f3df088 + languageName: node + linkType: hard + +"arg@npm:^4.1.0": + version: 4.1.3 + resolution: "arg@npm:4.1.3" + checksum: 070ff801a9d236a6caa647507bdcc7034530604844d64408149a26b9e87c2f97650055c0f049abd1efc024b334635c01f29e0b632b371ac3f26130f4cf65997a + languageName: node + linkType: hard + +"arg@npm:^5.0.2": + version: 5.0.2 + resolution: "arg@npm:5.0.2" + checksum: ccaf86f4e05d342af6666c569f844bec426595c567d32a8289715087825c2ca7edd8a3d204e4d2fb2aa4602e09a57d0c13ea8c9eea75aac3dbb4af5514e6800e + languageName: node + linkType: hard + +"argparse@npm:^1.0.7": + version: 1.0.10 + resolution: "argparse@npm:1.0.10" + dependencies: + sprintf-js: ~1.0.2 + checksum: b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de + languageName: node + linkType: hard + +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e + languageName: node + linkType: hard + +"aria-hidden@npm:^1.1.3": + version: 1.2.2 + resolution: "aria-hidden@npm:1.2.2" + dependencies: + tslib: ^2.0.0 + peerDependencies: + "@types/react": ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.9.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: a7183b35975c4e70d23c5e484e2d83fd1bfcfcb4985414c402732032391ea57dac0f628abad3ccd4d8565fb8e631b043593ba8dd023252acb90c13b6827a488e + languageName: node + linkType: hard + +"aria-query@npm:^5.0.0": + version: 5.1.3 + resolution: "aria-query@npm:5.1.3" + dependencies: + deep-equal: ^2.0.5 + checksum: edcbc8044c4663d6f88f785e983e6784f98cb62b4ba1e9dd8d61b725d0203e4cfca38d676aee984c31f354103461102a3d583aa4fbe4fd0a89b679744f4e5faf + languageName: node + linkType: hard + +"arr-diff@npm:^4.0.0": + version: 4.0.0 + resolution: "arr-diff@npm:4.0.0" + checksum: 67b80067137f70c89953b95f5c6279ad379c3ee39f7143578e13bd51580a40066ee2a55da066e22d498dce10f68c2d70056d7823f972fab99dfbf4c78d0bc0f7 + languageName: node + linkType: hard + +"arr-flatten@npm:^1.1.0": + version: 1.1.0 + resolution: "arr-flatten@npm:1.1.0" + checksum: bef53be02ed3bc58f202b3861a5b1eb6e1ae4fecf39c3ad4d15b1e0433f941077d16e019a33312d820844b0661777322acbb7d0c447b04d9bdf7d6f9c532548a + languageName: node + linkType: hard + +"arr-union@npm:^3.1.0": + version: 3.1.0 + resolution: "arr-union@npm:3.1.0" + checksum: 7d5aa05894e54aa93c77c5726c1dd5d8e8d3afe4f77983c0aa8a14a8a5cbe8b18f0cf4ecaa4ac8c908ef5f744d2cbbdaa83fd6e96724d15fea56cfa7f5efdd51 + languageName: node + linkType: hard + +"array-find-index@npm:^1.0.1": + version: 1.0.2 + resolution: "array-find-index@npm:1.0.2" + checksum: 86b9485c74ddd324feab807e10a6de3f9c1683856267236fac4bb4d4667ada6463e106db3f6c540ae6b720e0442b590ec701d13676df4c6af30ebf4da09b4f57 + languageName: node + linkType: hard + +"array-flatten@npm:1.1.1": + version: 1.1.1 + resolution: "array-flatten@npm:1.1.1" + checksum: 806966c8abb2f858b08f5324d9d18d7737480610f3bd5d3498aaae6eb5efdc501a884ba019c9b4a8f02ff67002058749d05548fd42fa8643f02c9c7f22198b91 + languageName: node + linkType: hard + +"array-flatten@npm:^2.1.2": + version: 2.1.2 + resolution: "array-flatten@npm:2.1.2" + checksum: bdc1cee68e41bec9cfc1161408734e2269428ef371445606bce4e6241001e138a94b9a617cc9a5b4b7fe6a3a51e3d5a942646975ce82a2e202ccf3e9b478c82f + languageName: node + linkType: hard + +"array-includes@npm:^3.0.3, array-includes@npm:^3.1.5, array-includes@npm:^3.1.6": + version: 3.1.6 + resolution: "array-includes@npm:3.1.6" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + get-intrinsic: ^1.1.3 + is-string: ^1.0.7 + checksum: d0caeaa57bea7d14b8480daee30cf8611899321006b15a6cd872b831bd7aaed7649f8764e060d01c5d33b8d9e998e5de5c87f4901874e1c1f467f429b7db2929 + languageName: node + linkType: hard + +"array-union@npm:^1.0.2": + version: 1.0.2 + resolution: "array-union@npm:1.0.2" + dependencies: + array-uniq: ^1.0.1 + checksum: 18686767c0cfdae8dc4acf5ac119b0f0eacad82b7fcc0aa62cc41f93c5ad406d494b6a6e53d85e52e8f0349b67a4fec815feeb537e95c02510d747bc9a4157c7 + languageName: node + linkType: hard + +"array-union@npm:^2.1.0": + version: 2.1.0 + resolution: "array-union@npm:2.1.0" + checksum: 429897e68110374f39b771ec47a7161fc6a8fc33e196857c0a396dc75df0b5f65e4d046674db764330b6bb66b39ef48dd7c53b6a2ee75cfb0681e0c1a7033962 + languageName: node + linkType: hard + +"array-uniq@npm:^1.0.1": + version: 1.0.3 + resolution: "array-uniq@npm:1.0.3" + checksum: 3acbaf9e6d5faeb1010e2db04ab171b8d265889e46c61762e502979bdc5e55656013726e9a61507de3c82d329a0dc1e8072630a3454b4f2b881cb19ba7fd8aa6 + languageName: node + linkType: hard + +"array-unique@npm:^0.3.2": + version: 0.3.2 + resolution: "array-unique@npm:0.3.2" + checksum: dbf4462cdba8a4b85577be07705210b3d35be4b765822a3f52962d907186617638ce15e0603a4fefdcf82f4cbbc9d433f8cbbd6855148a68872fa041b6474121 + languageName: node + linkType: hard + +"array.prototype.flat@npm:^1.2.1": + version: 1.3.1 + resolution: "array.prototype.flat@npm:1.3.1" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + es-shim-unscopables: ^1.0.0 + checksum: 8eda91d6925cc84b73ebf5a3d406ff28745d93a22ef6a0afb967755107081a937cf6c4555d3c18354870b2c5366c0ff51b3f597c11079e689869810a418b1b4f + languageName: node + linkType: hard + +"array.prototype.flatmap@npm:^1.2.1, array.prototype.flatmap@npm:^1.3.1": + version: 1.3.1 + resolution: "array.prototype.flatmap@npm:1.3.1" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + es-shim-unscopables: ^1.0.0 + checksum: 2bd58a0e79d5d90cb4f5ef0e287edf8b28e87c65428f54025ac6b7b4c204224b92811c266f296c53a2dbc93872117c0fcea2e51d3c9e8cecfd5024d4a4a57db4 + languageName: node + linkType: hard + +"array.prototype.map@npm:^1.0.5": + version: 1.0.5 + resolution: "array.prototype.map@npm:1.0.5" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + es-array-method-boxes-properly: ^1.0.0 + is-string: ^1.0.7 + checksum: cf44c0c958e94059d98132a3d5b7aa20d29aea34d20c515fdb236b69a95b1d3f1408f634f26fca51a9dbe06c85e93f7b351c85ea94300774af5ad2f1e8df3ae8 + languageName: node + linkType: hard + +"array.prototype.reduce@npm:^1.0.5": + version: 1.0.5 + resolution: "array.prototype.reduce@npm:1.0.5" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + es-array-method-boxes-properly: ^1.0.0 + is-string: ^1.0.7 + checksum: 0c6c589d22d6cda4a32458c6fd57a41f420a4fa6cd184a3f6fe7b507f457bc4a073aff6accd595bcd6ac29cad856e7ac306549f127acdb098f401eea13c54901 + languageName: node + linkType: hard + +"array.prototype.tosorted@npm:^1.1.1": + version: 1.1.1 + resolution: "array.prototype.tosorted@npm:1.1.1" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + es-shim-unscopables: ^1.0.0 + get-intrinsic: ^1.1.3 + checksum: fd5f57aca3c7ddcd1bb83965457b625f3a67d8f334f5cbdb8ac8ef33d5b0d38281524114db2936f8c08048115d5158af216c94e6ae1eb966241b9b6f4ab8a7e8 + languageName: node + linkType: hard + +"arrify@npm:^2.0.1": + version: 2.0.1 + resolution: "arrify@npm:2.0.1" + checksum: 3fb30b5e7c37abea1907a60b28a554d2f0fc088757ca9bf5b684786e583fdf14360721eb12575c1ce6f995282eab936712d3c4389122682eafab0e0b57f78dbb + languageName: node + linkType: hard + +"asap@npm:^2.0.0, asap@npm:~2.0.3": + version: 2.0.6 + resolution: "asap@npm:2.0.6" + checksum: c6d5e39fe1f15e4b87677460bd66b66050cd14c772269cee6688824c1410a08ab20254bb6784f9afb75af9144a9f9a7692d49547f4d19d715aeb7c0318f3136d + languageName: node + linkType: hard + +"asn1.js@npm:^5.2.0": + version: 5.4.1 + resolution: "asn1.js@npm:5.4.1" + dependencies: + bn.js: ^4.0.0 + inherits: ^2.0.1 + minimalistic-assert: ^1.0.0 + safer-buffer: ^2.1.0 + checksum: b577232fa6069cc52bb128e564002c62b2b1fe47f7137bdcd709c0b8495aa79cee0f8cc458a831b2d8675900eea0d05781b006be5e1aa4f0ae3577a73ec20324 + languageName: node + linkType: hard + +"asn1@npm:~0.2.3": + version: 0.2.6 + resolution: "asn1@npm:0.2.6" + dependencies: + safer-buffer: ~2.1.0 + checksum: 00c8a06c37e548762306bcb1488388d2f76c74c36f70c803f0c081a01d3bdf26090fc088cd812afc5e56a6d49e33765d451a5f8a68ab9c2b087eba65d2e980e0 + languageName: node + linkType: hard + +"assert-plus@npm:1.0.0, assert-plus@npm:^1.0.0": + version: 1.0.0 + resolution: "assert-plus@npm:1.0.0" + checksum: b194b9d50c3a8f872ee85ab110784911e696a4d49f7ee6fc5fb63216dedbefd2c55999c70cb2eaeb4cf4a0e0338b44e9ace3627117b5bf0d42460e9132f21b91 + languageName: node + linkType: hard + +"assert@npm:^1.1.1": + version: 1.5.0 + resolution: "assert@npm:1.5.0" + dependencies: + object-assign: ^4.1.1 + util: 0.10.3 + checksum: 188da37d63be479a3b14657c01080db90cdf7fa004e346af916cf8beebcaffb11359c596d0c9c3cd8174c9125a6225796ef1ce533487edc97f8ce3b18c1ab590 + languageName: node + linkType: hard + +"assign-symbols@npm:^1.0.0": + version: 1.0.0 + resolution: "assign-symbols@npm:1.0.0" + checksum: 29a654b8a6da6889a190d0d0efef4b1bfb5948fa06cbc245054aef05139f889f2f7c75b989917e3fde853fc4093b88048e4de8578a73a76f113d41bfd66e5775 + languageName: node + linkType: hard + +"ast-types@npm:^0.14.2": + version: 0.14.2 + resolution: "ast-types@npm:0.14.2" + dependencies: + tslib: ^2.0.1 + checksum: 5d66d89b6c07fe092087454b6042dbaf81f2882b176db93861e2b986aafe0bce49e1f1ff59aac775d451c1426ad1e967d250e9e3548f5166ea8a3475e66c169d + languageName: node + linkType: hard + +"astral-regex@npm:^2.0.0": + version: 2.0.0 + resolution: "astral-regex@npm:2.0.0" + checksum: f63d439cc383db1b9c5c6080d1e240bd14dae745f15d11ec5da863e182bbeca70df6c8191cffef5deba0b566ef98834610a68be79ac6379c95eeb26e1b310e25 + languageName: node + linkType: hard + +"async-each@npm:^1.0.1": + version: 1.0.3 + resolution: "async-each@npm:1.0.3" + checksum: d5f0ed24792d04b747f667fdcc92c7e6972da1252525a942119f468e629adba1e235df8b8a8e75776e6c7b18ef04d68db7295350bfa1a958457b34faa9a3bd65 + languageName: node + linkType: hard + +"async@npm:^3.1.0, async@npm:^3.2.0, async@npm:^3.2.3": + version: 3.2.4 + resolution: "async@npm:3.2.4" + checksum: b5d02fed64717edf49e35b2b156debd9cf524934ea670108fa5528e7615ed66a5e0bf6c65f832c9483b63aa7f0bffe3e588ebe8d58a539b833798d324516e1c9 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + +"at-least-node@npm:^1.0.0": + version: 1.0.0 + resolution: "at-least-node@npm:1.0.0" + checksum: 4c058baf6df1bc5a1697cf182e2029c58cd99975288a13f9e70068ef5d6f4e1f1fd7c4d2c3c4912eae44797d1725be9700995736deca441b39f3e66d8dee97ef + languageName: node + linkType: hard + +"atob@npm:^2.1.2": + version: 2.1.2 + resolution: "atob@npm:2.1.2" + bin: + atob: bin/atob.js + checksum: ada635b519dc0c576bb0b3ca63a73b50eefacf390abb3f062558342a8d68f2db91d0c8db54ce81b0d89de3b0f000de71f3ae7d761fd7d8cc624278fe443d6c7e + languageName: node + linkType: hard + +"autoprefixer@npm:^10.4.7": + version: 10.4.13 + resolution: "autoprefixer@npm:10.4.13" + dependencies: + browserslist: ^4.21.4 + caniuse-lite: ^1.0.30001426 + fraction.js: ^4.2.0 + normalize-range: ^0.1.2 + picocolors: ^1.0.0 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.1.0 + bin: + autoprefixer: bin/autoprefixer + checksum: 55ef1feb555516f68c740b3a0050d89b663c4a806a52ff23b184869ddf511b561fa56d66b2adb533bfef3798aee87b31132474582968d84fa59da133f837a230 + languageName: node + linkType: hard + +"autoprefixer@npm:^9.8.6": + version: 9.8.8 + resolution: "autoprefixer@npm:9.8.8" + dependencies: + browserslist: ^4.12.0 + caniuse-lite: ^1.0.30001109 + normalize-range: ^0.1.2 + num2fraction: ^1.2.2 + picocolors: ^0.2.1 + postcss: ^7.0.32 + postcss-value-parser: ^4.1.0 + bin: + autoprefixer: bin/autoprefixer + checksum: 9b2688cd0ef7252ae1a565ca935a83ddd5c38b9b4c7bf895f36d88e91dbc36d2e7ccb2d34270e436498d8f372d7320a83af6ceb5d1c3bff8f8cbeb6ff33ac837 + languageName: node + linkType: hard + +"available-typed-arrays@npm:^1.0.5": + version: 1.0.5 + resolution: "available-typed-arrays@npm:1.0.5" + checksum: c4df567ca72d2754a6cbad20088f5f98b1065b3360178169fa9b44ea101af62c0f423fc3854fa820fd6895b6b9171b8386e71558203103ff8fc2ad503fdcc660 + languageName: node + linkType: hard + +"aws-sdk@npm:2": + version: 2.1297.0 + resolution: "aws-sdk@npm:2.1297.0" + dependencies: + buffer: 4.9.2 + events: 1.1.1 + ieee754: 1.1.13 + jmespath: 0.16.0 + querystring: 0.2.0 + sax: 1.2.1 + url: 0.10.3 + util: ^0.12.4 + uuid: 8.0.0 + xml2js: 0.4.19 + checksum: 00228be50c2208859787da2a86c5ebda345ac6f6c26cc581f6fbd759a2909b0bf807ccecf43125ad396d47b6f31260d7e17ea72f765a0c1dc5c2c1edbbc8817e + languageName: node + linkType: hard + +"aws-sign2@npm:~0.7.0": + version: 0.7.0 + resolution: "aws-sign2@npm:0.7.0" + checksum: 021d2cc5547d4d9ef1633e0332e746a6f447997758b8b68d6fb33f290986872d2bff5f0c37d5832f41a7229361f093cd81c40898d96ed153493c0fb5cd8575d2 + languageName: node + linkType: hard + +"aws4@npm:^1.8.0": + version: 1.12.0 + resolution: "aws4@npm:1.12.0" + checksum: 1e39c266f53b04daf88e112de93a6006375b386a1b7ab6197260886e39abd012aa90bdd87949c3bf9c30754846031f6d5d8ac4f8676628097c11065b5d39847a + languageName: node + linkType: hard + +"babel-code-frame@npm:^6.26.0": + version: 6.26.0 + resolution: "babel-code-frame@npm:6.26.0" + dependencies: + chalk: ^1.1.3 + esutils: ^2.0.2 + js-tokens: ^3.0.2 + checksum: 7fecc128e87578cf1b96e78d2b25e0b260e202bdbbfcefa2eac23b7f8b7b2f7bc9276a14599cde14403cc798cc2a38e428e2cab50b77658ab49228b09ae92473 + languageName: node + linkType: hard + +"babel-jest@npm:^29.3.1": + version: 29.3.1 + resolution: "babel-jest@npm:29.3.1" + dependencies: + "@jest/transform": ^29.3.1 + "@types/babel__core": ^7.1.14 + babel-plugin-istanbul: ^6.1.1 + babel-preset-jest: ^29.2.0 + chalk: ^4.0.0 + graceful-fs: ^4.2.9 + slash: ^3.0.0 + peerDependencies: + "@babel/core": ^7.8.0 + checksum: 0ac669113d54a331e43cf3a5f39895bb8feadaad76c741027197c9c63dedff1835f1414877931dcb2daca614d8b50bc3c9c671fd44b46dca365fbec1c42e661a + languageName: node + linkType: hard + +"babel-loader@npm:^8.0.0, babel-loader@npm:^8.2.4": + version: 8.3.0 + resolution: "babel-loader@npm:8.3.0" + dependencies: + find-cache-dir: ^3.3.1 + loader-utils: ^2.0.0 + make-dir: ^3.1.0 + schema-utils: ^2.6.5 + peerDependencies: + "@babel/core": ^7.0.0 + webpack: ">=2" + checksum: 7b83bae35a12fbc5cdf250e2d36a288305fe5b6d20ab044ab7c09bbf456c8895b80af7a4f1e8b64b5c07a4fd48d4b5144dab40b4bc72a4fed532dc000362f38f + languageName: node + linkType: hard + +"babel-messages@npm:^6.23.0": + version: 6.23.0 + resolution: "babel-messages@npm:6.23.0" + dependencies: + babel-runtime: ^6.22.0 + checksum: d4fd6414ee5bb1aa0dad6d8d2c4ffaa66331ec5a507959e11f56b19a683566e2c1e7a4d0b16cfef58ea4cc07db8acf5ff3dc8b25c585407cff2e09ac60553401 + languageName: node + linkType: hard + +"babel-plugin-add-react-displayname@npm:^0.0.5": + version: 0.0.5 + resolution: "babel-plugin-add-react-displayname@npm:0.0.5" + checksum: 96b363d613e3d25e55606546874f3ab34b45088ac5143a64e417976f1eb29ed3e4df90400daa5edb2026d6088ed172f7af469d89838aac4bc810ede377b63c63 + languageName: node + linkType: hard + +"babel-plugin-apply-mdx-type-prop@npm:1.6.22": + version: 1.6.22 + resolution: "babel-plugin-apply-mdx-type-prop@npm:1.6.22" + dependencies: + "@babel/helper-plugin-utils": 7.10.4 + "@mdx-js/util": 1.6.22 + peerDependencies: + "@babel/core": ^7.11.6 + checksum: d1fd88f2eee87f3d709373cfac5165f8407793b123e1c7061308311f7e6b0778e093a4a93e7130b47c5a742f2515d0c1d4f3da5097ff195ef91011688ec17ddc + languageName: node + linkType: hard + +"babel-plugin-extract-import-names@npm:1.6.22": + version: 1.6.22 + resolution: "babel-plugin-extract-import-names@npm:1.6.22" + dependencies: + "@babel/helper-plugin-utils": 7.10.4 + checksum: c7b7206222f7b70f2c9852caa621cc3742b5d9f7dd4229a6e3c560d7683b82f835a8ea46db632df5dab5ad91b1439ead3771a8576a7a14e418248c16fd1f0cc4 + languageName: node + linkType: hard + +"babel-plugin-istanbul@npm:^6.0.0, babel-plugin-istanbul@npm:^6.1.1": + version: 6.1.1 + resolution: "babel-plugin-istanbul@npm:6.1.1" + 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 + checksum: 1075657feb705e00fd9463b329921856d3775d9867c5054b449317d39153f8fbcebd3e02ebf00432824e647faff3683a9ca0a941325ef1afe9b3c4dd51b24beb + languageName: node + linkType: hard + +"babel-plugin-jest-hoist@npm:^29.2.0": + version: 29.2.0 + resolution: "babel-plugin-jest-hoist@npm:29.2.0" + dependencies: + "@babel/template": ^7.3.3 + "@babel/types": ^7.3.3 + "@types/babel__core": ^7.1.14 + "@types/babel__traverse": ^7.0.6 + checksum: 37888f5161cfefefebe7f81c6fb3cc3a38ff793f1b1d6196a5b5b3a72f778476cdfb78eb4a4e1bc09903f952bfc28c4854a88433e2dd31366512c85e493e32f9 + languageName: node + linkType: hard + +"babel-plugin-macros@npm:^3.0.1, babel-plugin-macros@npm:^3.1.0": + version: 3.1.0 + resolution: "babel-plugin-macros@npm:3.1.0" + dependencies: + "@babel/runtime": ^7.12.5 + cosmiconfig: ^7.0.0 + resolve: ^1.19.0 + checksum: c6dfb15de96f67871d95bd2e8c58b0c81edc08b9b087dc16755e7157f357dc1090a8dc60ebab955e92587a9101f02eba07e730adc253a1e4cf593ca3ebd3839c + languageName: node + linkType: hard + +"babel-plugin-named-exports-order@npm:^0.0.2": + version: 0.0.2 + resolution: "babel-plugin-named-exports-order@npm:0.0.2" + checksum: e1d001722bddabc296b74f7cd020418a3cce9ca7052d5dd5dbd2870745d9566e286d14707c0bbfc9d4b4b643031052b358124ec735069f214d22b0b6768daf9d + languageName: node + linkType: hard + +"babel-plugin-polyfill-corejs2@npm:^0.3.3": + version: 0.3.3 + resolution: "babel-plugin-polyfill-corejs2@npm:0.3.3" + dependencies: + "@babel/compat-data": ^7.17.7 + "@babel/helper-define-polyfill-provider": ^0.3.3 + semver: ^6.1.1 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 21e34d4ba961de66d3fe31f3fecca5612d5db99638949766a445d37de72c1f736552fe436f3bd3792e5cc307f48e8f78a498a01e858c84946627ddb662415cc4 + languageName: node + linkType: hard + +"babel-plugin-polyfill-corejs3@npm:^0.1.0": + version: 0.1.7 + resolution: "babel-plugin-polyfill-corejs3@npm:0.1.7" + dependencies: + "@babel/helper-define-polyfill-provider": ^0.1.5 + core-js-compat: ^3.8.1 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: d31c7f0c210994593e2cc57d202ada8539cbbff1a112f52aa3607c8c9ba23b64e03fa52fbdc243dccbce8b8052f29f8d541bc4151e3055738cb03647708c0f42 + languageName: node + linkType: hard + +"babel-plugin-polyfill-corejs3@npm:^0.6.0": + version: 0.6.0 + resolution: "babel-plugin-polyfill-corejs3@npm:0.6.0" + dependencies: + "@babel/helper-define-polyfill-provider": ^0.3.3 + core-js-compat: ^3.25.1 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 58f7d16c1fbc5e4a68cc58126039cb997edc9b9d29adf1bc4124eb6a12ec31eb9e1da8df769b7219714748af7916cfbb194b2f15bd55571b3b43cdcd7839fe8f + languageName: node + linkType: hard + +"babel-plugin-polyfill-regenerator@npm:^0.4.1": + version: 0.4.1 + resolution: "babel-plugin-polyfill-regenerator@npm:0.4.1" + dependencies: + "@babel/helper-define-polyfill-provider": ^0.3.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: bd915d51e30259201b289a58dfa46c8c1bc8827a38c275ff3134c8194d27e634d5c32ec62137d489d81c7dd5f6ea46b04057eb44b7180d06c19388e3a5f4f8c6 + languageName: node + linkType: hard + +"babel-plugin-react-docgen@npm:^4.2.1": + version: 4.2.1 + resolution: "babel-plugin-react-docgen@npm:4.2.1" + dependencies: + ast-types: ^0.14.2 + lodash: ^4.17.15 + react-docgen: ^5.0.0 + checksum: 9f7af20e6ebd794beae14aa1ffe4f1c1c5855821a5a9f205099602c89b557e33b9cb3dc3fe0b3a2f2ca35007c6ab45f52da9695a681d8495ad0f5494ef78ec34 + languageName: node + linkType: hard + +"babel-plugin-react-require@npm:^3.1.3": + version: 3.1.3 + resolution: "babel-plugin-react-require@npm:3.1.3" + checksum: c22c35d4df55ee5ec593fbb255b75fdd1c0e9d7af653fa1be43ed1c737840a9b6e16480177a5b6efbdf1462e36f9a845fe202252f434cc4e1ba9bdad0d13ce33 + languageName: node + linkType: hard + +"babel-plugin-recharts@npm:^1.2.1": + version: 1.2.1 + resolution: "babel-plugin-recharts@npm:1.2.1" + dependencies: + "@babel/traverse": ^7.1.0 + "@babel/types": ^7.1.0 + babylon: ^6.18.0 + checksum: c0e43b719f8438cf8b88bb88c25adc57edcd77ec8ebac7a231b8d45d2a7f4efd26790abd02c90470f170f84782526ea4cd1126722ce545b658dbcff0a2b92cc1 + languageName: node + linkType: hard + +"babel-plugin-syntax-decorators@npm:^6.1.18": + version: 6.13.0 + resolution: "babel-plugin-syntax-decorators@npm:6.13.0" + checksum: b289da052203f498405864e769f52930d248b5d9868341e770495b37dc2430f724e049d99c482898f78e8a32cdb52877b2e6ba06a455e9698ad97a03ac31b957 + languageName: node + linkType: hard + +"babel-plugin-transform-decorators-legacy@npm:^1.3.5": + version: 1.3.5 + resolution: "babel-plugin-transform-decorators-legacy@npm:1.3.5" + dependencies: + babel-plugin-syntax-decorators: ^6.1.18 + babel-runtime: ^6.2.0 + babel-template: ^6.3.0 + checksum: 22357eea4e0794790001d968a554838e19b374a43a4d50e012698e2ad07ae44bc1fbc9e2414221b7956c801f4ce2c86cb18d2a7f27948eb8c75a4c00ef985d47 + languageName: node + linkType: hard + +"babel-preset-current-node-syntax@npm:^1.0.0": + version: 1.0.1 + resolution: "babel-preset-current-node-syntax@npm:1.0.1" + dependencies: + "@babel/plugin-syntax-async-generators": ^7.8.4 + "@babel/plugin-syntax-bigint": ^7.8.3 + "@babel/plugin-syntax-class-properties": ^7.8.3 + "@babel/plugin-syntax-import-meta": ^7.8.3 + "@babel/plugin-syntax-json-strings": ^7.8.3 + "@babel/plugin-syntax-logical-assignment-operators": ^7.8.3 + "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3 + "@babel/plugin-syntax-numeric-separator": ^7.8.3 + "@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-top-level-await": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 5ba39a3a0e6c37d25e56a4fb843be632dac98d54706d8a0933f9bcb1a07987a96d55c2b5a6c11788a74063fb2534fe68c1f1dbb6c93626850c785e0938495627 + languageName: node + linkType: hard + +"babel-preset-jest@npm:^29.2.0": + version: 29.2.0 + resolution: "babel-preset-jest@npm:29.2.0" + dependencies: + babel-plugin-jest-hoist: ^29.2.0 + babel-preset-current-node-syntax: ^1.0.0 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: bc72a6a64dd77b1075cbeaa53ee925b33e78d32e44fe3676c57941baa3ae8f59f6e2f399cef5b2d3ce2eecefb41e401ed4e276f4310f36519f4821c57227fb16 + languageName: node + linkType: hard + +"babel-runtime@npm:6.x, babel-runtime@npm:^6.2.0, babel-runtime@npm:^6.22.0, babel-runtime@npm:^6.26.0": + version: 6.26.0 + resolution: "babel-runtime@npm:6.26.0" + dependencies: + core-js: ^2.4.0 + regenerator-runtime: ^0.11.0 + checksum: caa752004936b1463765ed3199c52f6a55d0613b9bed108743d6f13ca532b821d4ea9decc4be1b583193164462b1e3e7eefdfa36b15c72e7daac58dd72c1772f + languageName: node + linkType: hard + +"babel-template@npm:^6.3.0": + version: 6.26.0 + resolution: "babel-template@npm:6.26.0" + dependencies: + babel-runtime: ^6.26.0 + babel-traverse: ^6.26.0 + babel-types: ^6.26.0 + babylon: ^6.18.0 + lodash: ^4.17.4 + checksum: 67bc875f19d289dabb1830a1cde93d7f1e187e4599dac9b1d16392fd47f1d12b53fea902dacf7be360acd09807d440faafe0f7907758c13275b1a14d100b68e4 + languageName: node + linkType: hard + +"babel-traverse@npm:^6.26.0": + version: 6.26.0 + resolution: "babel-traverse@npm:6.26.0" + dependencies: + babel-code-frame: ^6.26.0 + babel-messages: ^6.23.0 + babel-runtime: ^6.26.0 + babel-types: ^6.26.0 + babylon: ^6.18.0 + debug: ^2.6.8 + globals: ^9.18.0 + invariant: ^2.2.2 + lodash: ^4.17.4 + checksum: dca71b23d07e3c00833c3222d7998202e687105f461048107afeb2b4a7aa2507efab1bd5a6e3e724724ebb9b1e0b14f0113621e1d8c25b4ffdb829392b54b8de + languageName: node + linkType: hard + +"babel-types@npm:^6.26.0": + version: 6.26.0 + resolution: "babel-types@npm:6.26.0" + dependencies: + babel-runtime: ^6.26.0 + esutils: ^2.0.2 + lodash: ^4.17.4 + to-fast-properties: ^1.0.3 + checksum: cabe371de1b32c4bbb1fd4ed0fe8a8726d42e5ad7d5cefb83cdae6de0f0a152dce591e4026719743fdf3aa45f84fea2c8851fb822fbe29b0c78a1f0094b67418 + languageName: node + linkType: hard + +"babylon@npm:^6.18.0": + version: 6.18.0 + resolution: "babylon@npm:6.18.0" + bin: + babylon: ./bin/babylon.js + checksum: 9b1bf946e16782deadb1f5414c1269efa6044eb1e97a3de2051f09a3f2a54e97be3542d4242b28d23de0ef67816f519d38ce1ec3ddb7be306131c39a60e5a667 + languageName: node + linkType: hard + +"bail@npm:^1.0.0": + version: 1.0.5 + resolution: "bail@npm:1.0.5" + checksum: 4cf7d0b5c82fdc69590b3fe85c17c4ec37647681b20875551fd6187a85c122b20178dc118001d3ebd5d0ab3dc0e95637c71f889f481882ee761db43c6b16fa05 + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"base16@npm:^1.0.0": + version: 1.0.0 + resolution: "base16@npm:1.0.0" + checksum: af1aee7b297d968528ef47c8de2c5274029743e8a4a5f61ec823e36b673781691d124168cb22936c7997f53d89b344c58bf7ecf93eeb148cffa7e3fb4e4b8b18 + languageName: node + linkType: hard + +"base64-arraybuffer@npm:^1.0.2": + version: 1.0.2 + resolution: "base64-arraybuffer@npm:1.0.2" + checksum: 3acac95c70f9406e87a41073558ba85b6be9dbffb013a3d2a710e3f2d534d506c911847d5d9be4de458af6362c676de0a5c4c2d7bdf4def502d00b313368e72f + languageName: node + linkType: hard + +"base64-js@npm:^1.0.2, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf + languageName: node + linkType: hard + +"base@npm:^0.11.1": + version: 0.11.2 + resolution: "base@npm:0.11.2" + dependencies: + cache-base: ^1.0.1 + class-utils: ^0.3.5 + component-emitter: ^1.2.1 + define-property: ^1.0.0 + isobject: ^3.0.1 + mixin-deep: ^1.2.0 + pascalcase: ^0.1.1 + checksum: 30a2c0675eb52136b05ef496feb41574d9f0bb2d6d677761da579c00a841523fccf07f1dbabec2337b5f5750f428683b8ca60d89e56a1052c4ae1c0cd05de64d + languageName: node + linkType: hard + +"batch@npm:0.6.1": + version: 0.6.1 + resolution: "batch@npm:0.6.1" + checksum: 925a13897b4db80d4211082fe287bcf96d297af38e26448c857cee3e095c9792e3b8f26b37d268812e7f38a589f694609de8534a018b1937d7dc9f84e6b387c5 + languageName: node + linkType: hard + +"bcrypt-pbkdf@npm:^1.0.0": + version: 1.0.2 + resolution: "bcrypt-pbkdf@npm:1.0.2" + dependencies: + tweetnacl: ^0.14.3 + checksum: ddfe85230b32df25aeebfdccfbc61d3bc493ace49c884c9c68575de1f5dcf733a5d7de9def3b0f318b786616b8d85bad50a28b1da1750c43e0012c93badcc148 + languageName: node + linkType: hard + +"better-opn@npm:^2.1.1": + version: 2.1.1 + resolution: "better-opn@npm:2.1.1" + dependencies: + open: ^7.0.3 + checksum: c483f52a1c71555926df37a89ae7e521cddff6509e2a02c6af83c2c500e20cb3307417579ce1d5ec4d09855cc7d30f608b20934e7cedc54218888722d5bfc1d3 + languageName: node + linkType: hard + +"big-integer@npm:^1.6.7": + version: 1.6.51 + resolution: "big-integer@npm:1.6.51" + checksum: c8139662d57f8833a44802f4b65be911679c569535ea73c5cfd3c1c8994eaead1b84b6f63e1db63833e4d4cacb6b6a9e5522178113dfdc8e4c81ed8436f1e8cc + languageName: node + linkType: hard + +"big.js@npm:^5.2.2": + version: 5.2.2 + resolution: "big.js@npm:5.2.2" + checksum: 230520f1ff920b2d2ce3e372d77a33faa4fa60d802fe01ca4ffbc321ee06023fe9a741ac02793ee778040a16b7e497f7d60c504d1c402b8fdab6f03bb785a25f + languageName: node + linkType: hard + +"bin-links@npm:^1.1.2, bin-links@npm:^1.1.8": + version: 1.1.8 + resolution: "bin-links@npm:1.1.8" + dependencies: + bluebird: ^3.5.3 + cmd-shim: ^3.0.0 + gentle-fs: ^2.3.0 + graceful-fs: ^4.1.15 + npm-normalize-package-bin: ^1.0.0 + write-file-atomic: ^2.3.0 + checksum: 2a377b3ef1835f7ff832af5623e41c25656963bc6611ff04660cb7982b99078cae6a9a3c78c97ae996564e4a9e8ca7c13579689592c17774a3859a0ecaf7123f + languageName: node + linkType: hard + +"binary-extensions@npm:^1.0.0": + version: 1.13.1 + resolution: "binary-extensions@npm:1.13.1" + checksum: 2d616938ac23d828ec3fbe0dea429b566fd2c137ddc38f166f16561ccd58029deac3fa9fddb489ab13d679c8fb5f1bd0e82824041299e5e39d8dd3cc68fbb9f9 + languageName: node + linkType: hard + +"binary-extensions@npm:^2.0.0": + version: 2.2.0 + resolution: "binary-extensions@npm:2.2.0" + checksum: d73d8b897238a2d3ffa5f59c0241870043aa7471335e89ea5e1ff48edb7c2d0bb471517a3e4c5c3f4c043615caa2717b5f80a5e61e07503d51dc85cb848e665d + languageName: node + linkType: hard + +"bindings@npm:^1.5.0": + version: 1.5.0 + resolution: "bindings@npm:1.5.0" + dependencies: + file-uri-to-path: 1.0.0 + checksum: 3dab2491b4bb24124252a91e656803eac24292473e56554e35bbfe3cc1875332cfa77600c3bac7564049dc95075bf6fcc63a4609920ff2d64d0fe405fcf0d4ba + languageName: node + linkType: hard + +"blob-util@npm:^2.0.2": + version: 2.0.2 + resolution: "blob-util@npm:2.0.2" + checksum: ed82d587827e5c86be122301a7c250f8364963e9582f72a826255bfbd32f8d69cc10169413d666667bb1c4fc8061329ae89d176ffe46fee8f32080af944ccddc + languageName: node + linkType: hard + +"block-stream2@npm:^2.0.0": + version: 2.1.0 + resolution: "block-stream2@npm:2.1.0" + dependencies: + readable-stream: ^3.4.0 + checksum: 653661502a04164e57e570a38d293ad97d2184ae8963715644f7425bbcf4e52de2258858450378992211f222a328f56f5540a1e2a7cd9ed2ec9a363222f40bde + languageName: node + linkType: hard + +"bluebird@npm:^3.5.1, bluebird@npm:^3.5.3, bluebird@npm:^3.5.5, bluebird@npm:^3.7.2": + version: 3.7.2 + resolution: "bluebird@npm:3.7.2" + checksum: 680de03adc54ff925eaa6c7bb9a47a0690e8b5de60f4792604aae8ed618c65e6b63a7893b57ca924beaf53eee69c5af4f8314148c08124c550fe1df1add897d2 + languageName: node + linkType: hard + +"bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.11.9": + version: 4.12.0 + resolution: "bn.js@npm:4.12.0" + checksum: 9736aaa317421b6b3ed038ff3d4491935a01419ac2d83ddcfebc5717385295fcfcf0c57311d90fe49926d0abbd7a9dbefdd8861e6129939177f7e67ebc645b21 + languageName: node + linkType: hard + +"bn.js@npm:^5.0.0, bn.js@npm:^5.1.1": + version: 5.2.1 + resolution: "bn.js@npm:5.2.1" + checksum: bed3d8bd34ec89dbcf9f20f88bd7d4a49c160fda3b561c7bb227501f974d3e435a48fb9b61bc3de304acab9215a3bda0803f7017ffb4d0016a0c3a740a283caa + languageName: node + linkType: hard + +"body-parser@npm:1.20.1": + version: 1.20.1 + resolution: "body-parser@npm:1.20.1" + 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 + checksum: a202d493e2c10a33fb7413dac7d2f713be579c4b88343cd814b6df7a38e5af1901fc31044e04de176db56b16d9772aa25a7723f64478c20f4d91b1ac223bf3b8 + languageName: node + linkType: hard + +"bonjour-service@npm:^1.0.11": + version: 1.1.0 + resolution: "bonjour-service@npm:1.1.0" + dependencies: + array-flatten: ^2.1.2 + dns-equal: ^1.0.0 + fast-deep-equal: ^3.1.3 + multicast-dns: ^7.2.5 + checksum: 29e862ab140efd01e5b0b25c1faa4e71377037502e1036b619e6fcee68784c0ae136557a3285ed2a2018d979c01c253c05125a1adbed8937c8255fae1166f104 + languageName: node + linkType: hard + +"boolbase@npm:^1.0.0": + version: 1.0.0 + resolution: "boolbase@npm:1.0.0" + checksum: e4b53deb4f2b85c52be0e21a273f2045c7b6a6ea002b0e139c744cb6f95e9ec044439a52883b0d74dedd1ff3da55ed140cfdddfed7fb0cccbed373de5dce1bcf + languageName: node + linkType: hard + +"boxen@npm:^1.2.1": + version: 1.3.0 + resolution: "boxen@npm:1.3.0" + dependencies: + ansi-align: ^2.0.0 + camelcase: ^4.0.0 + chalk: ^2.0.1 + cli-boxes: ^1.0.0 + string-width: ^2.0.0 + term-size: ^1.2.0 + widest-line: ^2.0.0 + checksum: 9df2e59dd6622cee184aaffc017b3b3d506f1f70cddb0a7c19d22c865db4222faa9940512a7a6e2065f48500f9310a04d27af2037b1afeb2143e79430c9af476 + languageName: node + linkType: hard + +"boxen@npm:^5.1.2": + version: 5.1.2 + resolution: "boxen@npm:5.1.2" + dependencies: + ansi-align: ^3.0.0 + camelcase: ^6.2.0 + chalk: ^4.1.0 + cli-boxes: ^2.2.1 + string-width: ^4.2.2 + type-fest: ^0.20.2 + widest-line: ^3.1.0 + wrap-ansi: ^7.0.0 + checksum: 71f31c2eb3dcacd5fce524ae509e0cc90421752e0bfbd0281fd3352871d106c462a0f810c85f2fdb02f3a9fab2d7a84e9718b4999384d651b76104ebe5d2c024 + languageName: node + linkType: hard + +"bplist-parser@npm:^0.1.0": + version: 0.1.1 + resolution: "bplist-parser@npm:0.1.1" + dependencies: + big-integer: ^1.6.7 + checksum: cd50206f956e74f6e46cb5ed14be5eb00b2e14676ea3dd36703470715177a2770fc22032eca63a36adb3b56a1e51138a95bb0fc6849a78c21e92caeedf219ea7 + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.11 + resolution: "brace-expansion@npm:1.1.11" + dependencies: + balanced-match: ^1.0.0 + concat-map: 0.0.1 + checksum: 695a56cd058096a7cb71fb09d9d6a7070113c7be516699ed361317aca2ec169f618e28b8af352e02ab4233fb54eb0168460a40dc320bab0034b36ab59aaad668 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.1 + resolution: "brace-expansion@npm:2.0.1" + dependencies: + balanced-match: ^1.0.0 + checksum: b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f + languageName: node + linkType: hard + +"braces@npm:^2.3.1, braces@npm:^2.3.2": + version: 2.3.2 + resolution: "braces@npm:2.3.2" + dependencies: + arr-flatten: ^1.1.0 + array-unique: ^0.3.2 + extend-shallow: ^2.0.1 + fill-range: ^4.0.0 + isobject: ^3.0.1 + repeat-element: ^1.1.2 + snapdragon: ^0.8.1 + snapdragon-node: ^2.0.1 + split-string: ^3.0.2 + to-regex: ^3.0.1 + checksum: 72b27ea3ea2718f061c29e70fd6e17606e37c65f5801abddcf0b0052db1de7d60f3bf92cfc220ab57b44bd0083a5f69f9d03b3461d2816cfe9f9398207acc728 + languageName: node + linkType: hard + +"braces@npm:^3.0.2, braces@npm:~3.0.2": + version: 3.0.2 + resolution: "braces@npm:3.0.2" + dependencies: + fill-range: ^7.0.1 + checksum: 321b4d675791479293264019156ca322163f02dc06e3c4cab33bb15cd43d80b51efef69b0930cfde3acd63d126ebca24cd0544fa6f261e093a0fb41ab9dda381 + languageName: node + linkType: hard + +"brorand@npm:^1.0.1, brorand@npm:^1.1.0": + version: 1.1.0 + resolution: "brorand@npm:1.1.0" + checksum: 6f366d7c4990f82c366e3878492ba9a372a73163c09871e80d82fb4ae0d23f9f8924cb8a662330308206e6b3b76ba1d528b4601c9ef73c2166b440b2ea3b7571 + languageName: node + linkType: hard + +"browser-assert@npm:^1.2.1": + version: 1.2.1 + resolution: "browser-assert@npm:1.2.1" + checksum: 902abf999f92c9c951fdb6d7352c09eea9a84706258699655f7e7906e42daa06a1ae286398a755872740e05a6a71c43c5d1a0c0431d67a8cdb66e5d859a3fc0c + languageName: node + linkType: hard + +"browser-or-node@npm:^1.3.0": + version: 1.3.0 + resolution: "browser-or-node@npm:1.3.0" + checksum: a914790dd6a857149b1bb128a39872a95172f6f1db7824dd014b88cc2f88ce6433013ec83388fff83512fb815e7dc2ea60ddbfe8ff150d4f233092ff0d60ec60 + languageName: node + linkType: hard + +"browserify-aes@npm:^1.0.0, browserify-aes@npm:^1.0.4": + version: 1.2.0 + resolution: "browserify-aes@npm:1.2.0" + dependencies: + buffer-xor: ^1.0.3 + cipher-base: ^1.0.0 + create-hash: ^1.1.0 + evp_bytestokey: ^1.0.3 + inherits: ^2.0.1 + safe-buffer: ^5.0.1 + checksum: 967f2ae60d610b7b252a4cbb55a7a3331c78293c94b4dd9c264d384ca93354c089b3af9c0dd023534efdc74ffbc82510f7ad4399cf82bc37bc07052eea485f18 + languageName: node + linkType: hard + +"browserify-cipher@npm:^1.0.0": + version: 1.0.1 + resolution: "browserify-cipher@npm:1.0.1" + dependencies: + browserify-aes: ^1.0.4 + browserify-des: ^1.0.0 + evp_bytestokey: ^1.0.0 + checksum: aa256dcb42bc53a67168bbc94ab85d243b0a3b56109dee3b51230b7d010d9b78985ffc1fb36e145c6e4db151f888076c1cfc207baf1525d3e375cbe8187fe27d + languageName: node + linkType: hard + +"browserify-des@npm:^1.0.0": + version: 1.0.2 + resolution: "browserify-des@npm:1.0.2" + dependencies: + cipher-base: ^1.0.1 + des.js: ^1.0.0 + inherits: ^2.0.1 + safe-buffer: ^5.1.2 + checksum: 943eb5d4045eff80a6cde5be4e5fbb1f2d5002126b5a4789c3c1aae3cdddb1eb92b00fb92277f512288e5c6af330730b1dbabcf7ce0923e749e151fcee5a074d + languageName: node + linkType: hard + +"browserify-rsa@npm:^4.0.0, browserify-rsa@npm:^4.0.1": + version: 4.1.0 + resolution: "browserify-rsa@npm:4.1.0" + dependencies: + bn.js: ^5.0.0 + randombytes: ^2.0.1 + checksum: fb2b5a8279d8a567a28d8ee03fb62e448428a906bab5c3dc9e9c3253ace551b5ea271db15e566ac78f1b1d71b243559031446604168b9235c351a32cae99d02a + languageName: node + linkType: hard + +"browserify-sign@npm:^4.0.0": + version: 4.2.1 + resolution: "browserify-sign@npm:4.2.1" + dependencies: + bn.js: ^5.1.1 + browserify-rsa: ^4.0.1 + create-hash: ^1.2.0 + create-hmac: ^1.1.7 + elliptic: ^6.5.3 + inherits: ^2.0.4 + parse-asn1: ^5.1.5 + readable-stream: ^3.6.0 + safe-buffer: ^5.2.0 + checksum: 8f00a370e3e97060977dc58e51251d3ca398ee73523994a44430321e8de2c7d85395362d59014b2b07efe4190f369baee2ff28eb8f405ff4660b776651cf052d + languageName: node + linkType: hard + +"browserify-zlib@npm:^0.2.0": + version: 0.2.0 + resolution: "browserify-zlib@npm:0.2.0" + dependencies: + pako: ~1.0.5 + checksum: 9ab10b6dc732c6c5ec8ebcbe5cb7fe1467f97402c9b2140113f47b5f187b9438f93a8e065d8baf8b929323c18324fbf1105af479ee86d9d36cab7d7ef3424ad9 + languageName: node + linkType: hard + +"browserslist@npm:^4.0.0, browserslist@npm:^4.12.0, browserslist@npm:^4.14.5, browserslist@npm:^4.16.6, browserslist@npm:^4.21.3, browserslist@npm:^4.21.4": + version: 4.21.4 + resolution: "browserslist@npm:4.21.4" + dependencies: + caniuse-lite: ^1.0.30001400 + electron-to-chromium: ^1.4.251 + node-releases: ^2.0.6 + update-browserslist-db: ^1.0.9 + bin: + browserslist: cli.js + checksum: bbc5fe2b4280a590cb40b110cd282f18f4542d75ddb559dfe0a174fda0263d2a7dd5b1634d0f795d617d69cb5f9716479c4a90d9a954a7ef16bc0a2878965af8 + languageName: node + linkType: hard + +"bs-logger@npm:0.x": + version: 0.2.6 + resolution: "bs-logger@npm:0.2.6" + dependencies: + fast-json-stable-stringify: 2.x + checksum: 80e89aaaed4b68e3374ce936f2eb097456a0dddbf11f75238dbd53140b1e39259f0d248a5089ed456f1158984f22191c3658d54a713982f676709fbe1a6fa5a0 + languageName: node + linkType: hard + +"bser@npm:2.1.1": + version: 2.1.1 + resolution: "bser@npm:2.1.1" + dependencies: + node-int64: ^0.4.0 + checksum: 24d8dfb7b6d457d73f32744e678a60cc553e4ec0e9e1a01cf614b44d85c3c87e188d3cc78ef0442ce5032ee6818de20a0162ba1074725c0d08908f62ea979227 + languageName: node + linkType: hard + +"btoa@npm:^1.2.1": + version: 1.2.1 + resolution: "btoa@npm:1.2.1" + bin: + btoa: bin/btoa.js + checksum: 557b9682e40a68ae057af1b377e28884e6ff756ba0f499fe0f8c7b725a5bfb5c0d891604ac09944dbe330c9d43fb3976fef734f9372608d0d8e78a30eda292ae + languageName: node + linkType: hard + +"buffer-crc32@npm:^0.2.13, buffer-crc32@npm:~0.2.3": + version: 0.2.13 + resolution: "buffer-crc32@npm:0.2.13" + checksum: cb0a8ddf5cf4f766466db63279e47761eb825693eeba6a5a95ee4ec8cb8f81ede70aa7f9d8aeec083e781d47154290eb5d4d26b3f7a465ec57fb9e7d59c47150 + languageName: node + linkType: hard + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34 + languageName: node + linkType: hard + +"buffer-xor@npm:^1.0.3": + version: 1.0.3 + resolution: "buffer-xor@npm:1.0.3" + checksum: fd269d0e0bf71ecac3146187cfc79edc9dbb054e2ee69b4d97dfb857c6d997c33de391696d04bdd669272751fa48e7872a22f3a6c7b07d6c0bc31dbe02a4075c + languageName: node + linkType: hard + +"buffer@npm:4.9.2, buffer@npm:^4.3.0": + version: 4.9.2 + resolution: "buffer@npm:4.9.2" + dependencies: + base64-js: ^1.0.2 + ieee754: ^1.1.4 + isarray: ^1.0.0 + checksum: dc443d7e7caab23816b58aacdde710b72f525ad6eecd7d738fcaa29f6d6c12e8d9c13fed7219fd502be51ecf0615f5c077d4bdc6f9308dde2e53f8e5393c5b21 + languageName: node + linkType: hard + +"buffer@npm:^5.6.0": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" + dependencies: + base64-js: ^1.3.1 + ieee754: ^1.1.13 + checksum: 27cac81cff434ed2876058d72e7c4789d11ff1120ef32c9de48f59eab58179b66710c488987d295ae89a228f835fc66d088652dffeb8e3ba8659f80eb091d55e + languageName: node + linkType: hard + +"builtin-modules@npm:^3.1.0": + version: 3.3.0 + resolution: "builtin-modules@npm:3.3.0" + checksum: 2cb3448b4f7306dc853632a4fcddc95e8d4e4b9868c139400027b71938fc6806d4ff44007deffb362ac85724bd40c2c6452fb6a0aa4531650eeddb98d8e5ee8a + languageName: node + linkType: hard + +"builtin-status-codes@npm:^3.0.0": + version: 3.0.0 + resolution: "builtin-status-codes@npm:3.0.0" + checksum: c37bbba11a34c4431e56bd681b175512e99147defbe2358318d8152b3a01df7bf25e0305873947e5b350073d5ef41a364a22b37e48f1fb6d2fe6d5286a0f348c + languageName: node + linkType: hard + +"builtins@npm:^1.0.3": + version: 1.0.3 + resolution: "builtins@npm:1.0.3" + checksum: 493afcc1db0a56d174cc85bebe5ca69144f6fdd0007d6cbe6b2434185314c79d83cb867e492b56aa5cf421b4b8a8135bf96ba4c3ce71994cf3da154d1ea59747 + languageName: node + linkType: hard + +"byline@npm:^5.0.0": + version: 5.0.0 + resolution: "byline@npm:5.0.0" + checksum: 33fb64cd84440b3652a99a68d732c56ef18a748ded495ba38e7756a242fab0d4654b9b8ce269fd0ac14c5f97aa4e3c369613672b280a1f60b559b34223105c85 + languageName: node + linkType: hard + +"byte-size@npm:^5.0.1": + version: 5.0.1 + resolution: "byte-size@npm:5.0.1" + checksum: 4ebef854ae67bd2f05cee948f518b8232eb9f93155dcc3765de1d0fed1dfe88a8d974f73a78997faa43fb51a83777a61056452cfe250a4720776ddbd1c42db5c + languageName: node + linkType: hard + +"bytes@npm:3.0.0": + version: 3.0.0 + resolution: "bytes@npm:3.0.0" + checksum: 91d42c38601c76460519ffef88371caacaea483a354c8e4b8808e7b027574436a5713337c003ea3de63ee4991c2a9a637884fdfe7f761760d746929d9e8fec60 + languageName: node + linkType: hard + +"bytes@npm:3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: 76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e + languageName: node + linkType: hard + +"c8@npm:^7.6.0": + version: 7.12.0 + resolution: "c8@npm:7.12.0" + dependencies: + "@bcoe/v8-coverage": ^0.2.3 + "@istanbuljs/schema": ^0.1.3 + find-up: ^5.0.0 + foreground-child: ^2.0.0 + istanbul-lib-coverage: ^3.2.0 + istanbul-lib-report: ^3.0.0 + istanbul-reports: ^3.1.4 + rimraf: ^3.0.2 + test-exclude: ^6.0.0 + v8-to-istanbul: ^9.0.0 + yargs: ^16.2.0 + yargs-parser: ^20.2.9 + bin: + c8: bin/c8.js + checksum: 4939095767be901170efec3e160221997711f2e62e1745a2ae42eef68933ec55f9ad7cdec074a0fd8948bdd24c860d0872741101fba16741ab6e239454243355 + languageName: node + linkType: hard + +"cacache@npm:^12.0.0, cacache@npm:^12.0.2, cacache@npm:^12.0.4": + version: 12.0.4 + resolution: "cacache@npm:12.0.4" + dependencies: + bluebird: ^3.5.5 + chownr: ^1.1.1 + figgy-pudding: ^3.5.1 + glob: ^7.1.4 + graceful-fs: ^4.1.15 + infer-owner: ^1.0.3 + lru-cache: ^5.1.1 + mississippi: ^3.0.0 + mkdirp: ^0.5.1 + move-concurrently: ^1.0.1 + promise-inflight: ^1.0.1 + rimraf: ^2.6.3 + ssri: ^6.0.1 + unique-filename: ^1.1.1 + y18n: ^4.0.0 + checksum: b4b0aa49e3fbd3ca92f71bc62923e4afce31fd687b31d5ba524b2a54b36e96a8b027165599307dda5e4a6f7268cc951b77ca170efa00c1b72761f9daae51fdfb + languageName: node + linkType: hard + +"cacache@npm:^15.0.5": + version: 15.3.0 + resolution: "cacache@npm:15.3.0" + dependencies: + "@npmcli/fs": ^1.0.0 + "@npmcli/move-file": ^1.0.1 + chownr: ^2.0.0 + fs-minipass: ^2.0.0 + glob: ^7.1.4 + infer-owner: ^1.0.4 + lru-cache: ^6.0.0 + minipass: ^3.1.1 + minipass-collect: ^1.0.2 + minipass-flush: ^1.0.5 + minipass-pipeline: ^1.2.2 + mkdirp: ^1.0.3 + p-map: ^4.0.0 + promise-inflight: ^1.0.1 + rimraf: ^3.0.2 + ssri: ^8.0.1 + tar: ^6.0.2 + unique-filename: ^1.1.1 + checksum: 886fcc0acc4f6fd5cd142d373d8276267bc6d655d7c4ce60726fbbec10854de3395ee19bbf9e7e73308cdca9fdad0ad55060ff3bd16c6d4165c5b8d21515e1d8 + languageName: node + linkType: hard + +"cacache@npm:^16.1.0": + version: 16.1.3 + resolution: "cacache@npm:16.1.3" + dependencies: + "@npmcli/fs": ^2.1.0 + "@npmcli/move-file": ^2.0.0 + chownr: ^2.0.0 + fs-minipass: ^2.1.0 + glob: ^8.0.1 + infer-owner: ^1.0.4 + lru-cache: ^7.7.1 + minipass: ^3.1.6 + minipass-collect: ^1.0.2 + minipass-flush: ^1.0.5 + minipass-pipeline: ^1.2.4 + mkdirp: ^1.0.4 + p-map: ^4.0.0 + promise-inflight: ^1.0.1 + rimraf: ^3.0.2 + ssri: ^9.0.0 + tar: ^6.1.11 + unique-filename: ^2.0.0 + checksum: cdf6836e1c457d2a5616abcaf5d8240c0346b1f5bd6fdb8866b9d84b6dff0b54e973226dc11e0d099f35394213d24860d1989c8358d2a41b39eb912b3000e749 + languageName: node + linkType: hard + +"cache-base@npm:^1.0.1": + version: 1.0.1 + resolution: "cache-base@npm:1.0.1" + dependencies: + collection-visit: ^1.0.0 + component-emitter: ^1.2.1 + get-value: ^2.0.6 + has-value: ^1.0.0 + isobject: ^3.0.1 + set-value: ^2.0.0 + to-object-path: ^0.3.0 + union-value: ^1.0.0 + unset-value: ^1.0.0 + checksum: a7142e25c73f767fa520957dcd179b900b86eac63b8cfeaa3b2a35e18c9ca5968aa4e2d2bed7a3e7efd10f13be404344cfab3a4156217e71f9bdb95940bb9c8c + languageName: node + linkType: hard + +"cachedir@npm:^2.3.0": + version: 2.3.0 + resolution: "cachedir@npm:2.3.0" + checksum: 8380a4a4aa824b20cbc246c38ae2b3379a865f52ea1f31f7b057d07545ea1ab27f93c4323d4bd1bd398991489f18a226880c3166b19ecbf49a77b18c519d075a + languageName: node + linkType: hard + +"calendar@npm:^0.1.0": + version: 0.1.1 + resolution: "calendar@npm:0.1.1" + checksum: 315e4ce334538f3d570fc293abee7e3942664ec524687682e69bf219a658d11ec545cbc4e49406ca3bef4e23204e17ddf264a20d0015126d9b61956f6fd28284 + languageName: node + linkType: hard + +"call-bind@npm:^1.0.0, call-bind@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind@npm:1.0.2" + dependencies: + function-bind: ^1.1.1 + get-intrinsic: ^1.0.2 + checksum: 74ba3f31e715456e22e451d8d098779b861eba3c7cac0d9b510049aced70d75c231ba05071f97e1812c98e34e2bee734c0c6126653e0088c2d9819ca047f4073 + languageName: node + linkType: hard + +"call-limit@npm:^1.1.1": + version: 1.1.1 + resolution: "call-limit@npm:1.1.1" + checksum: 4d3ec413d3cb4474cf4d5f8aac52363dc0933f80c5f7cda2eb27e4443811ea2c98ab82c2bce7a464de7418438db5ff480c7a8bc7ae22aebe4d0bba8006fd8986 + languageName: node + linkType: hard + +"call-me-maybe@npm:^1.0.1": + version: 1.0.2 + resolution: "call-me-maybe@npm:1.0.2" + checksum: 8eff5dbb61141ebb236ed71b4e9549e488bcb5451c48c11e5667d5c75b0532303788a1101e6978cafa2d0c8c1a727805599c2741e3e0982855c9f1d78cd06c9f + languageName: node + linkType: hard + +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 + languageName: node + linkType: hard + +"camel-case@npm:^4.1.1, camel-case@npm:^4.1.2": + version: 4.1.2 + resolution: "camel-case@npm:4.1.2" + dependencies: + pascal-case: ^3.1.2 + tslib: ^2.0.3 + checksum: bf9eefaee1f20edbed2e9a442a226793bc72336e2b99e5e48c6b7252b6f70b080fc46d8246ab91939e2af91c36cdd422e0af35161e58dd089590f302f8f64c8a + languageName: node + linkType: hard + +"camelcase-css@npm:2.0.1, camelcase-css@npm:^2.0.1": + version: 2.0.1 + resolution: "camelcase-css@npm:2.0.1" + checksum: 1a1a3137e8a781e6cbeaeab75634c60ffd8e27850de410c162cce222ea331cd1ba5364e8fb21c95e5ca76f52ac34b81a090925ca00a87221355746d049c6e273 + languageName: node + linkType: hard + +"camelcase-keys@npm:^2.0.0": + version: 2.1.0 + resolution: "camelcase-keys@npm:2.1.0" + dependencies: + camelcase: ^2.0.0 + map-obj: ^1.0.0 + checksum: d9431f8b5ac52644cfc45377c0d3897f045137d645c8890bd2bfb48c282d22e76644974198dbba3a2d96b33f9bf3af07aacb712b0dd6d2671330a7e2531b72f9 + languageName: node + linkType: hard + +"camelcase@npm:^2.0.0": + version: 2.1.1 + resolution: "camelcase@npm:2.1.1" + checksum: 610db65fa7dd50a400525ec2188fd65a1939dda4afe5de7d08608670013269c3743c3737fb0f138d1df8aa74e257cc83e3b756e776b604af16dac297b4a0d054 + languageName: node + linkType: hard + +"camelcase@npm:^4.0.0": + version: 4.1.0 + resolution: "camelcase@npm:4.1.0" + checksum: 54c0b6a85b54fb4e96a9d834a9d0d8f760fd608ab6752a6789042b5e1c38d3dd60f878d2c590d005046445d32d77f6e05e568a91fe8db3e282da0a1560d83058 + languageName: node + linkType: hard + +"camelcase@npm:^5.0.0, camelcase@npm:^5.3.1": + version: 5.3.1 + resolution: "camelcase@npm:5.3.1" + checksum: 92ff9b443bfe8abb15f2b1513ca182d16126359ad4f955ebc83dc4ddcc4ef3fdd2c078bc223f2673dc223488e75c99b16cc4d056624374b799e6a1555cf61b23 + languageName: node + linkType: hard + +"camelcase@npm:^6.2.0": + version: 6.3.0 + resolution: "camelcase@npm:6.3.0" + checksum: 0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 + languageName: node + linkType: hard + +"caniuse-api@npm:^3.0.0": + version: 3.0.0 + resolution: "caniuse-api@npm:3.0.0" + dependencies: + browserslist: ^4.0.0 + caniuse-lite: ^1.0.0 + lodash.memoize: ^4.1.2 + lodash.uniq: ^4.5.0 + checksum: 60f9e85a3331e6d761b1b03eec71ca38ef7d74146bece34694853033292156b815696573ed734b65583acf493e88163618eda915c6c826d46a024c71a9572b4c + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001400, caniuse-lite@npm:^1.0.30001426": + version: 1.0.30001446 + resolution: "caniuse-lite@npm:1.0.30001446" + checksum: 07af2c24ecf86bda64c8483ccc5b1f73b570ea262af22e795092d8fb4c0968214a3ba6897666be944b8380c8c83d992a58d7613d2f91d9e17e3e8d3eb51807b8 + languageName: node + linkType: hard + +"canvg@npm:^3.0.6": + version: 3.0.10 + resolution: "canvg@npm:3.0.10" + dependencies: + "@babel/runtime": ^7.12.5 + "@types/raf": ^3.4.0 + core-js: ^3.8.3 + raf: ^3.4.1 + regenerator-runtime: ^0.13.7 + rgbcolor: ^1.0.1 + stackblur-canvas: ^2.0.0 + svg-pathdata: ^6.0.3 + checksum: b6bcd95d60c923c6a4e2be49e1fc1d395790577913a5a68439a2bb5a784ee75533ed7720bef69f2d9d0404203b4d61e89fdf1346f829e5da71e54cc57614153f + languageName: node + linkType: hard + +"capture-exit@npm:^2.0.0": + version: 2.0.0 + resolution: "capture-exit@npm:2.0.0" + dependencies: + rsvp: ^4.8.4 + checksum: d68df1e15937809501644a49c0267ef323b5b6a0cae5c08bbdceafd718aa08241844798bfdd762cf6756bc2becd83122aabc25b5222192f18093113bec670617 + languageName: node + linkType: hard + +"capture-stack-trace@npm:^1.0.0": + version: 1.0.2 + resolution: "capture-stack-trace@npm:1.0.2" + checksum: 9f910506dcbe82dbfadf81a9e8c7cff478dd64ea2de319d01762de32940cdb082217686215a8ed389a540e683779fe56ac4b9a2957d1bfdd8c730d08e5f12ca5 + languageName: node + linkType: hard + +"case-sensitive-paths-webpack-plugin@npm:^2.3.0": + version: 2.4.0 + resolution: "case-sensitive-paths-webpack-plugin@npm:2.4.0" + checksum: 310dab619b661a7fa44ed773870be6d6d7373faff6953ad92720f9553e2579e46dda5b9a79eae6d25ff3733cc15aa466b96e5811af16213f23c115aa220b4ab4 + languageName: node + linkType: hard + +"caseless@npm:~0.12.0": + version: 0.12.0 + resolution: "caseless@npm:0.12.0" + checksum: ccf64bcb6c0232cdc5b7bd91ddd06e23a4b541f138336d4725233ac538041fb2f29c2e86c3c4a7a61ef990b665348db23a047060b9414c3a6603e9fa61ad4626 + languageName: node + linkType: hard + +"ccount@npm:^1.0.0": + version: 1.1.0 + resolution: "ccount@npm:1.1.0" + checksum: 9ccfddfa45c8d6d01411b8e30d2ce03c55c33f32a69bdb84ee44d743427cdb01b03159954917023d0dac960c34973ba42626bb9fa883491ebb663a53a6713d43 + languageName: node + linkType: hard + +"chalk@npm:^1.1.3": + version: 1.1.3 + resolution: "chalk@npm:1.1.3" + 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 + checksum: 28c3e399ec286bb3a7111fd4225ebedb0d7b813aef38a37bca7c498d032459c265ef43404201d5fbb8d888d29090899c95335b4c0cda13e8b126ff15c541cef8 + languageName: node + linkType: hard + +"chalk@npm:^2.0.0, chalk@npm:^2.0.1, chalk@npm:^2.4.1": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: ^3.2.1 + escape-string-regexp: ^1.0.5 + supports-color: ^5.3.0 + checksum: e6543f02ec877732e3a2d1c3c3323ddb4d39fbab687c23f526e25bd4c6a9bf3b83a696e8c769d078e04e5754921648f7821b2a2acfd16c550435fd630026e073 + languageName: node + linkType: hard + +"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: ^4.1.0 + supports-color: ^7.1.0 + checksum: 4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 + languageName: node + linkType: hard + +"char-regex@npm:^1.0.2": + version: 1.0.2 + resolution: "char-regex@npm:1.0.2" + checksum: 57a09a86371331e0be35d9083ba429e86c4f4648ecbe27455dbfb343037c16ee6fdc7f6b61f433a57cc5ded5561d71c56a150e018f40c2ffb7bc93a26dae341e + languageName: node + linkType: hard + +"character-entities-legacy@npm:^1.0.0": + version: 1.1.4 + resolution: "character-entities-legacy@npm:1.1.4" + checksum: ea4ca9c29887335eed86d78fc67a640168342b1274da84c097abb0575a253d1265281a5052f9a863979e952bcc267b4ecaaf4fe233a7e1e0d8a47806c65b96c7 + languageName: node + linkType: hard + +"character-entities@npm:^1.0.0": + version: 1.2.4 + resolution: "character-entities@npm:1.2.4" + checksum: ad015c3d7163563b8a0ee1f587fb0ef305ef344e9fd937f79ca51cccc233786a01d591d989d5bf7b2e66b528ac9efba47f3b1897358324e69932f6d4b25adfe1 + languageName: node + linkType: hard + +"character-reference-invalid@npm:^1.0.0": + version: 1.1.4 + resolution: "character-reference-invalid@npm:1.1.4" + checksum: 29f05081c5817bd1e975b0bf61e77b60a40f62ad371d0f0ce0fdb48ab922278bc744d1fbe33771dced751887a8403f265ff634542675c8d7375f6ff4811efd0e + languageName: node + linkType: hard + +"check-more-types@npm:^2.24.0": + version: 2.24.0 + resolution: "check-more-types@npm:2.24.0" + checksum: 93fda2c32eb5f6cd1161a84a2f4107c0e00b40a851748516791dd9a0992b91bdf504e3bf6bf7673ce603ae620042e11ed4084d16d6d92b36818abc9c2e725520 + languageName: node + linkType: hard + +"chokidar@npm:>=3.0.0 <4.0.0, chokidar@npm:^3.4.0, chokidar@npm:^3.4.1, chokidar@npm:^3.4.2, chokidar@npm:^3.5.3": + version: 3.5.3 + resolution: "chokidar@npm:3.5.3" + dependencies: + anymatch: ~3.1.2 + braces: ~3.0.2 + fsevents: ~2.3.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 + dependenciesMeta: + fsevents: + optional: true + checksum: 1076953093e0707c882a92c66c0f56ba6187831aa51bb4de878c1fec59ae611a3bf02898f190efec8e77a086b8df61c2b2a3ea324642a0558bdf8ee6c5dc9ca1 + languageName: node + linkType: hard + +"chokidar@npm:^2.1.8": + version: 2.1.8 + resolution: "chokidar@npm:2.1.8" + dependencies: + anymatch: ^2.0.0 + async-each: ^1.0.1 + braces: ^2.3.2 + fsevents: ^1.2.7 + glob-parent: ^3.1.0 + inherits: ^2.0.3 + is-binary-path: ^1.0.0 + is-glob: ^4.0.0 + normalize-path: ^3.0.0 + path-is-absolute: ^1.0.0 + readdirp: ^2.2.1 + upath: ^1.1.1 + dependenciesMeta: + fsevents: + optional: true + checksum: 5631cc00080224f9482cf5418dcbea111aec02fa8d81a8cfe37e47b9cf36089e071de52d503647e3a821a01426a40adc926ba899f657af86a51b8f8d4eef12a7 + languageName: node + linkType: hard + +"chownr@npm:^1.1.1, chownr@npm:^1.1.2, chownr@npm:^1.1.3, chownr@npm:^1.1.4": + version: 1.1.4 + resolution: "chownr@npm:1.1.4" + checksum: ed57952a84cc0c802af900cf7136de643d3aba2eecb59d29344bc2f3f9bf703a301b9d84cdc71f82c3ffc9ccde831b0d92f5b45f91727d6c9da62f23aef9d9db + languageName: node + linkType: hard + +"chownr@npm:^2.0.0": + version: 2.0.0 + resolution: "chownr@npm:2.0.0" + checksum: 594754e1303672171cc04e50f6c398ae16128eb134a88f801bf5354fd96f205320f23536a045d9abd8b51024a149696e51231565891d4efdab8846021ecf88e6 + languageName: node + linkType: hard + +"chroma-js@npm:^2.4.2": + version: 2.4.2 + resolution: "chroma-js@npm:2.4.2" + checksum: 5657cd10892538c4a41e8bd95524d018c3a43318b26dfb20d572b2084bc6d5af742457a6d5701ddecb4d4eceb99995873b22293c1b396ab0b35ef55a264550c8 + languageName: node + linkType: hard + +"chrome-trace-event@npm:^1.0.2": + version: 1.0.3 + resolution: "chrome-trace-event@npm:1.0.3" + checksum: 080ce2d20c2b9e0f8461a380e9585686caa768b1c834a464470c9dc74cda07f27611c7b727a2cd768a9cecd033297fdec4ce01f1e58b62227882c1059dec321c + languageName: node + linkType: hard + +"ci-info@npm:^1.5.0": + version: 1.6.0 + resolution: "ci-info@npm:1.6.0" + checksum: 5a74921e50e0be504ef811f00cb8dd6a547e1f4f2e709b7364b2de72a6fbc0215d86c88cd62c485a129090365d2a6367ca00344ced04e714860b22fbe10180c8 + languageName: node + linkType: hard + +"ci-info@npm:^2.0.0": + version: 2.0.0 + resolution: "ci-info@npm:2.0.0" + checksum: 8c5fa3830a2bcee2b53c2e5018226f0141db9ec9f7b1e27a5c57db5512332cde8a0beb769bcbaf0d8775a78afbf2bb841928feca4ea6219638a5b088f9884b46 + languageName: node + linkType: hard + +"ci-info@npm:^3.2.0": + version: 3.7.1 + resolution: "ci-info@npm:3.7.1" + checksum: bae9bbcb0c2cdaf9ecceb4680079486e6bd3634f767e7c27eba761bea01fa133ea79432245e6fe6a8f202e4661f43bb5ec42014c6b547f24393457aa53ce56e1 + languageName: node + linkType: hard + +"cidr-regex@npm:^2.0.10": + version: 2.0.10 + resolution: "cidr-regex@npm:2.0.10" + dependencies: + ip-regex: ^2.1.0 + checksum: b5abec8c61e080eb9853d9a0d3198d54cece96ea64dc562f97632f7360d09e842b888adbcd7c0a7e3bc75491596dbfb936f01527ca4f532e92975fe28dbc1c33 + languageName: node + linkType: hard + +"cipher-base@npm:^1.0.0, cipher-base@npm:^1.0.1, cipher-base@npm:^1.0.3": + version: 1.0.4 + resolution: "cipher-base@npm:1.0.4" + dependencies: + inherits: ^2.0.1 + safe-buffer: ^5.0.1 + checksum: d8d005f8b64d8a77b3d3ce531301ae7b45902c9cab4ec8b66bdbd2bf2a1d9fceb9a2133c293eb3c060b2d964da0f14c47fb740366081338aa3795dd1faa8984b + languageName: node + linkType: hard + +"cjs-module-lexer@npm:^1.0.0": + version: 1.2.2 + resolution: "cjs-module-lexer@npm:1.2.2" + checksum: 83330e1feda2e3699b8c305bfa8f841b41822049393f5eefeb574e60bde556e2a251ee9b7971cde0cb47ac4f7823bf4ab4a6005b8471f86ad9f5509eefb66cbd + languageName: node + linkType: hard + +"class-utils@npm:^0.3.5": + version: 0.3.6 + resolution: "class-utils@npm:0.3.6" + dependencies: + arr-union: ^3.1.0 + define-property: ^0.2.5 + isobject: ^3.0.0 + static-extend: ^0.1.1 + checksum: d44f4afc7a3e48dba4c2d3fada5f781a1adeeff371b875c3b578bc33815c6c29d5d06483c2abfd43a32d35b104b27b67bfa39c2e8a422fa858068bd756cfbd42 + languageName: node + linkType: hard + +"classnames@npm:2.x, classnames@npm:^2.1.1, classnames@npm:^2.2.5, classnames@npm:^2.2.6, classnames@npm:^2.3.1": + version: 2.3.2 + resolution: "classnames@npm:2.3.2" + checksum: cd50ead57b4f97436aaa9f9885c6926323efc7c2bea8e3d4eb10e4e972aa6a1cfca1c7a0e06f8a199ca7498d4339e30bb6002e589e61c9f21248cbf3e8b0b18d + languageName: node + linkType: hard + +"clean-css@npm:^4.2.3": + version: 4.2.4 + resolution: "clean-css@npm:4.2.4" + dependencies: + source-map: ~0.6.0 + checksum: 0e41795fdc9d65e5e17a3b0016d90bf2a653e3a680829b5bcebdbab48604cfe36d96d8af6346338d2c2aca8aa9af024ac4fb752ac3eb5b71bef68a34a129b58a + languageName: node + linkType: hard + +"clean-css@npm:^5.2.2": + version: 5.3.2 + resolution: "clean-css@npm:5.3.2" + dependencies: + source-map: ~0.6.0 + checksum: 315e0e81306524bd2c1905fa6823bf7658be40799b78f446e5e6922808718d2b80266fb3e96842a06176fa683bc2c1a0d2827b08d154e2f9cf136d7bda909d33 + languageName: node + linkType: hard + +"clean-stack@npm:^2.0.0": + version: 2.2.0 + resolution: "clean-stack@npm:2.2.0" + checksum: 1f90262d5f6230a17e27d0c190b09d47ebe7efdd76a03b5a1127863f7b3c9aec4c3e6c8bb3a7bbf81d553d56a1fd35728f5a8ef4c63f867ac8d690109742a8c1 + languageName: node + linkType: hard + +"cli-boxes@npm:^1.0.0": + version: 1.0.0 + resolution: "cli-boxes@npm:1.0.0" + checksum: 1f79cd17e39cc00710d85c2a2d33ead781d1215b72546b31cfb4da5b2edc1f12ef8f99af67afb8a79fa1f92ad6b54505cc245afb16eee5fa01772f87353ba6e4 + languageName: node + linkType: hard + +"cli-boxes@npm:^2.2.1": + version: 2.2.1 + resolution: "cli-boxes@npm:2.2.1" + checksum: 6111352edbb2f62dbc7bfd58f2d534de507afed7f189f13fa894ce5a48badd94b2aa502fda28f1d7dd5f1eb456e7d4033d09a76660013ef50c7f66e7a034f050 + languageName: node + linkType: hard + +"cli-columns@npm:^3.1.2": + version: 3.1.2 + resolution: "cli-columns@npm:3.1.2" + dependencies: + string-width: ^2.0.0 + strip-ansi: ^3.0.1 + checksum: aef5aa1180378ae2a202bdfcaac372666018ace3e792efbd33be15564db201764733dfcbe4769c68256fa22bb66d38a9f9b1383aa4faf97898fb0584fdaa54ca + languageName: node + linkType: hard + +"cli-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "cli-cursor@npm:3.1.0" + dependencies: + restore-cursor: ^3.1.0 + checksum: 92a2f98ff9037d09be3dfe1f0d749664797fb674bf388375a2207a1203b69d41847abf16434203e0089212479e47a358b13a0222ab9fccfe8e2644a7ccebd111 + languageName: node + linkType: hard + +"cli-table3@npm:^0.5.0, cli-table3@npm:^0.5.1": + version: 0.5.1 + resolution: "cli-table3@npm:0.5.1" + dependencies: + colors: ^1.1.2 + object-assign: ^4.1.0 + string-width: ^2.1.1 + dependenciesMeta: + colors: + optional: true + checksum: 659c40ead17539d0665aa9dea85a7650fc161939f9d8bd3842c6cf5da51dc867057d3066fe8c962dafa163da39ce2029357754aee2c8f9513ea7a0810511d1d6 + languageName: node + linkType: hard + +"cli-table3@npm:^0.6.1, cli-table3@npm:~0.6.1": + version: 0.6.3 + resolution: "cli-table3@npm:0.6.3" + dependencies: + "@colors/colors": 1.5.0 + string-width: ^4.2.0 + dependenciesMeta: + "@colors/colors": + optional: true + checksum: 39e580cb346c2eaf1bd8f4ff055ae644e902b8303c164a1b8894c0dc95941f92e001db51f49649011be987e708d9fa3183ccc2289a4d376a057769664048cc0c + languageName: node + linkType: hard + +"cli-truncate@npm:^2.1.0": + version: 2.1.0 + resolution: "cli-truncate@npm:2.1.0" + dependencies: + slice-ansi: ^3.0.0 + string-width: ^4.2.0 + checksum: dfaa3df675bcef7a3254773de768712b590250420345a4c7ac151f041a4bacb4c25864b1377bee54a39b5925a030c00eabf014e312e3a4ac130952ed3b3879e9 + languageName: node + linkType: hard + +"cli@npm:~1.0.0": + version: 1.0.1 + resolution: "cli@npm:1.0.1" + dependencies: + exit: 0.1.2 + glob: ^7.1.1 + checksum: 12e406248386ebcf5351c28b0c94ae0392245c3534ebd4bb67423e1999daf8d898705f654eb70738d9870997d981aef3929d2db3aba3ea95a24380092a94b786 + languageName: node + linkType: hard + +"cliui@npm:^5.0.0": + version: 5.0.0 + resolution: "cliui@npm:5.0.0" + dependencies: + string-width: ^3.1.0 + strip-ansi: ^5.2.0 + wrap-ansi: ^5.1.0 + checksum: 76142bf306965850a71efd10c9755bd7f447c7c20dd652e1c1ce27d987f862a3facb3cceb2909cef6f0cb363646ee7a1735e3dfdd49f29ed16d733d33e15e2f8 + languageName: node + linkType: hard + +"cliui@npm:^7.0.2": + version: 7.0.4 + resolution: "cliui@npm:7.0.4" + dependencies: + string-width: ^4.2.0 + strip-ansi: ^6.0.0 + wrap-ansi: ^7.0.0 + checksum: 6035f5daf7383470cef82b3d3db00bec70afb3423538c50394386ffbbab135e26c3689c41791f911fa71b62d13d3863c712fdd70f0fbdffd938a1e6fd09aac00 + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: ^4.2.0 + strip-ansi: ^6.0.1 + wrap-ansi: ^7.0.0 + checksum: 4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 + languageName: node + linkType: hard + +"clone-deep@npm:^4.0.1": + version: 4.0.1 + resolution: "clone-deep@npm:4.0.1" + dependencies: + is-plain-object: ^2.0.4 + kind-of: ^6.0.2 + shallow-clone: ^3.0.0 + checksum: 637753615aa24adf0f2d505947a1bb75e63964309034a1cf56ba4b1f30af155201edd38d26ffe26911adaae267a3c138b344a4947d39f5fc1b6d6108125aa758 + languageName: node + linkType: hard + +"clone@npm:^1.0.2": + version: 1.0.4 + resolution: "clone@npm:1.0.4" + checksum: 2176952b3649293473999a95d7bebfc9dc96410f6cbd3d2595cf12fd401f63a4bf41a7adbfd3ab2ff09ed60cb9870c58c6acdd18b87767366fabfc163700f13b + languageName: node + linkType: hard + +"clsx@npm:1.1.0": + version: 1.1.0 + resolution: "clsx@npm:1.1.0" + checksum: 7a3478c7757fd031ddc6a12c5e65eb29559df6ae7c89a52a89d177af130834eb914b1b80318a806cae43b3063d0b9cf70c8cbfedc2f60160e299d3b14cbeb400 + languageName: node + linkType: hard + +"clsx@npm:^1.0.4, clsx@npm:^1.1.1": + version: 1.2.1 + resolution: "clsx@npm:1.2.1" + checksum: 34dead8bee24f5e96f6e7937d711978380647e936a22e76380290e35486afd8634966ce300fc4b74a32f3762c7d4c0303f442c3e259f4ce02374eb0c82834f27 + languageName: node + linkType: hard + +"cmd-shim@npm:^3.0.0, cmd-shim@npm:^3.0.3": + version: 3.0.3 + resolution: "cmd-shim@npm:3.0.3" + dependencies: + graceful-fs: ^4.1.2 + mkdirp: ~0.5.0 + checksum: fce068469cb56a858bb165b162c0dbbafb4eb880505ca7f6d84f010e4165b5f1f2e916f7c56d837553a47668696c6e8e7c3e8494841da1e7d1e01304848e2192 + languageName: node + linkType: hard + +"co@npm:^4.6.0": + version: 4.6.0 + resolution: "co@npm:4.6.0" + checksum: c0e85ea0ca8bf0a50cbdca82efc5af0301240ca88ebe3644a6ffb8ffe911f34d40f8fbcf8f1d52c5ddd66706abd4d3bfcd64259f1e8e2371d4f47573b0dc8c28 + languageName: node + linkType: hard + +"code-point-at@npm:^1.0.0": + version: 1.1.0 + resolution: "code-point-at@npm:1.1.0" + checksum: 33f6b234084e46e6e369b6f0b07949392651b4dde70fc6a592a8d3dafa08d5bb32e3981a02f31f6fc323a26bc03a4c063a9d56834848695bda7611c2417ea2e6 + languageName: node + linkType: hard + +"collapse-white-space@npm:^1.0.2": + version: 1.0.6 + resolution: "collapse-white-space@npm:1.0.6" + checksum: 7fd27a883eee1ddd5e39c53fbcd4a42dfe2a65dfac70e2c442d20827f5258202b360a12e99b4f0128c3addd2d64796bb2eb1bb8a3b75d5a2e9c061adb549c36b + languageName: node + linkType: hard + +"collect-v8-coverage@npm:^1.0.0": + version: 1.0.1 + resolution: "collect-v8-coverage@npm:1.0.1" + checksum: df8192811a773d10978fd25060124e4228d9a86bab40de3f18df5ce1a3730832351a52ba1c0e3915d5bd638298fc7bc9723760d25f534462746e269a6f0ac91c + languageName: node + linkType: hard + +"collection-visit@npm:^1.0.0": + version: 1.0.0 + resolution: "collection-visit@npm:1.0.0" + dependencies: + map-visit: ^1.0.0 + object-visit: ^1.0.0 + checksum: add72a8d1c37cb90e53b1aaa2c31bf1989bfb733f0b02ce82c9fa6828c7a14358dba2e4f8e698c02f69e424aeccae1ffb39acdeaf872ade2f41369e84a2fcf8a + languageName: node + linkType: hard + +"color-convert@npm:^1.9.0, color-convert@npm:^1.9.3": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: 1.1.3 + checksum: 5ad3c534949a8c68fca8fbc6f09068f435f0ad290ab8b2f76841b9e6af7e0bb57b98cb05b0e19fe33f5d91e5a8611ad457e5f69e0a484caad1f7487fd0e8253c + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: ~1.1.4 + checksum: 37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: 566a3d42cca25b9b3cd5528cd7754b8e89c0eb646b7f214e8e2eaddb69994ac5f0557d9c175eb5d8f0ad73531140d9c47525085ee752a91a2ab15ab459caf6d6 + languageName: node + linkType: hard + +"color-name@npm:^1.0.0, color-name@npm:^1.1.4, color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"color-string@npm:^1.6.0": + version: 1.9.1 + resolution: "color-string@npm:1.9.1" + dependencies: + color-name: ^1.0.0 + simple-swizzle: ^0.2.2 + checksum: b0bfd74c03b1f837f543898b512f5ea353f71630ccdd0d66f83028d1f0924a7d4272deb278b9aef376cacf1289b522ac3fb175e99895283645a2dc3a33af2404 + languageName: node + linkType: hard + +"color-support@npm:^1.1.2, color-support@npm:^1.1.3": + version: 1.1.3 + resolution: "color-support@npm:1.1.3" + bin: + color-support: bin.js + checksum: 8ffeaa270a784dc382f62d9be0a98581db43e11eee301af14734a6d089bd456478b1a8b3e7db7ca7dc5b18a75f828f775c44074020b51c05fc00e6d0992b1cc6 + languageName: node + linkType: hard + +"color@npm:^3.1.3": + version: 3.2.1 + resolution: "color@npm:3.2.1" + dependencies: + color-convert: ^1.9.3 + color-string: ^1.6.0 + checksum: 39345d55825884c32a88b95127d417a2c24681d8b57069413596d9fcbb721459ef9d9ec24ce3e65527b5373ce171b73e38dbcd9c830a52a6487e7f37bf00e83c + languageName: node + linkType: hard + +"colord@npm:^2.9.1": + version: 2.9.3 + resolution: "colord@npm:2.9.3" + checksum: 9699e956894d8996b28c686afe8988720785f476f59335c80ce852ded76ab3ebe252703aec53d9bef54f6219aea6b960fb3d9a8300058a1d0c0d4026460cd110 + languageName: node + linkType: hard + +"colorette@npm:^1.2.2": + version: 1.4.0 + resolution: "colorette@npm:1.4.0" + checksum: 4955c8f7daafca8ae7081d672e4bd89d553bd5782b5846d5a7e05effe93c2f15f7e9c0cb46f341b59f579a39fcf436241ff79594899d75d5f3460c03d607fe9e + languageName: node + linkType: hard + +"colorette@npm:^2.0.10, colorette@npm:^2.0.14, colorette@npm:^2.0.16": + version: 2.0.19 + resolution: "colorette@npm:2.0.19" + checksum: 2bcc9134095750fece6e88167011499b964b78bf0ea953469130ddb1dba3c8fe6c03debb0ae181e710e2be10900d117460f980483a7df4ba4a1bac3b182ecb64 + languageName: node + linkType: hard + +"colors@npm:^1.1.2": + version: 1.4.0 + resolution: "colors@npm:1.4.0" + checksum: 9af357c019da3c5a098a301cf64e3799d27549d8f185d86f79af23069e4f4303110d115da98483519331f6fb71c8568d5688fa1c6523600044fd4a54e97c4efb + languageName: node + linkType: hard + +"colorspace@npm:1.1.x": + version: 1.1.4 + resolution: "colorspace@npm:1.1.4" + dependencies: + color: ^3.1.3 + text-hex: 1.0.x + checksum: af5f91ff7f8e146b96e439ac20ed79b197210193bde721b47380a75b21751d90fa56390c773bb67c0aedd34ff85091883a437ab56861c779bd507d639ba7e123 + languageName: node + linkType: hard + +"columnify@npm:~1.5.4": + version: 1.5.4 + resolution: "columnify@npm:1.5.4" + dependencies: + strip-ansi: ^3.0.0 + wcwidth: ^1.0.0 + checksum: bed7041413afab966f6c478730a1617764065c6cee598b6ba8d7400fb95974b857682a721b7edd7358e10ab3e47512930208903707f7f806fa887f9ee6ca5946 + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.6, combined-stream@npm:^1.0.8, combined-stream@npm:~1.0.6": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: ~1.0.0 + checksum: 0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + +"comma-separated-tokens@npm:^1.0.0": + version: 1.0.8 + resolution: "comma-separated-tokens@npm:1.0.8" + checksum: c3bcfeaa6d50313528a006a40bcc0f9576086665c9b48d4b3a76ddd63e7d6174734386c98be1881cbf6ecfc25e1db61cd775a7b896d2ea7a65de28f83a0f9b17 + languageName: node + linkType: hard + +"commander@npm:^2.19.0, commander@npm:^2.20.0": + version: 2.20.3 + resolution: "commander@npm:2.20.3" + checksum: 74c781a5248c2402a0a3e966a0a2bba3c054aad144f5c023364be83265e796b20565aa9feff624132ff629aa64e16999fa40a743c10c12f7c61e96a794b99288 + languageName: node + linkType: hard + +"commander@npm:^4.0.1, commander@npm:^4.1.1": + version: 4.1.1 + resolution: "commander@npm:4.1.1" + checksum: 84a76c08fe6cc08c9c93f62ac573d2907d8e79138999312c92d4155bc2325d487d64d13f669b2000c9f8caf70493c1be2dac74fec3c51d5a04f8bc3ae1830bab + languageName: node + linkType: hard + +"commander@npm:^5.1.0": + version: 5.1.0 + resolution: "commander@npm:5.1.0" + checksum: da9d71dbe4ce039faf1fe9eac3771dca8c11d66963341f62602f7b66e36d2a3f8883407af4f9a37b1db1a55c59c0c1325f186425764c2e963dc1d67aec2a4b6d + languageName: node + linkType: hard + +"commander@npm:^6.2.1": + version: 6.2.1 + resolution: "commander@npm:6.2.1" + checksum: 85748abd9d18c8bc88febed58b98f66b7c591d9b5017cad459565761d7b29ca13b7783ea2ee5ce84bf235897333706c4ce29adf1ce15c8252780e7000e2ce9ea + languageName: node + linkType: hard + +"commander@npm:^7.0.0, commander@npm:^7.2.0": + version: 7.2.0 + resolution: "commander@npm:7.2.0" + checksum: 8d690ff13b0356df7e0ebbe6c59b4712f754f4b724d4f473d3cc5b3fdcf978e3a5dc3078717858a2ceb50b0f84d0660a7f22a96cdc50fb877d0c9bb31593d23a + languageName: node + linkType: hard + +"commander@npm:^8.3.0": + version: 8.3.0 + resolution: "commander@npm:8.3.0" + checksum: 8b043bb8322ea1c39664a1598a95e0495bfe4ca2fad0d84a92d7d1d8d213e2a155b441d2470c8e08de7c4a28cf2bc6e169211c49e1b21d9f7edc6ae4d9356060 + languageName: node + linkType: hard + +"common-path-prefix@npm:^3.0.0": + version: 3.0.0 + resolution: "common-path-prefix@npm:3.0.0" + checksum: c4a74294e1b1570f4a8ab435285d185a03976c323caa16359053e749db4fde44e3e6586c29cd051100335e11895767cbbd27ea389108e327d62f38daf4548fdb + languageName: node + linkType: hard + +"common-tags@npm:^1.8.0": + version: 1.8.2 + resolution: "common-tags@npm:1.8.2" + checksum: 23efe47ff0a1a7c91489271b3a1e1d2a171c12ec7f9b35b29b2fce51270124aff0ec890087e2bc2182c1cb746e232ab7561aaafe05f1e7452aea733d2bfe3f63 + languageName: node + linkType: hard + +"commondir@npm:^1.0.1": + version: 1.0.1 + resolution: "commondir@npm:1.0.1" + checksum: 33a124960e471c25ee19280c9ce31ccc19574b566dc514fe4f4ca4c34fa8b0b57cf437671f5de380e11353ea9426213fca17687dd2ef03134fea2dbc53809fd6 + languageName: node + linkType: hard + +"component-classes@npm:^1.2.5": + version: 1.2.6 + resolution: "component-classes@npm:1.2.6" + dependencies: + component-indexof: 0.0.3 + checksum: 5b2f7a7c897c3eec94b8d09bab0e1725ad596fae661a5ed850f924855c8fa73e783050b9b998a5732ba619ca0b4b550a1a2a50652bf8f34bd3773277547e3b0c + languageName: node + linkType: hard + +"component-emitter@npm:^1.2.1": + version: 1.3.0 + resolution: "component-emitter@npm:1.3.0" + checksum: 68774a0a3754fb6c0ba53c2e88886dfbd0c773931066abb1d7fd1b0c893b2a838d8f088ab4dca1f18cc1a4fc2e6932019eba3ded2d931b5ba2241ce40e93a24f + languageName: node + linkType: hard + +"component-indexof@npm:0.0.3": + version: 0.0.3 + resolution: "component-indexof@npm:0.0.3" + checksum: 0acb68802318f69fe60b1a48b9df7d36c2ace0837f7fb9e0c7bd4915dc4682c276be1cf1c1686e8c023f24b5e43edf4aaadc5d6dae04378f43f7869e89896966 + languageName: node + linkType: hard + +"compressible@npm:~2.0.16": + version: 2.0.18 + resolution: "compressible@npm:2.0.18" + dependencies: + mime-db: ">= 1.43.0 < 2" + checksum: 8a03712bc9f5b9fe530cc5a79e164e665550d5171a64575d7dcf3e0395d7b4afa2d79ab176c61b5b596e28228b350dd07c1a2a6ead12fd81d1b6cd632af2fef7 + languageName: node + linkType: hard + +"compression-webpack-plugin@npm:^10.0.0": + version: 10.0.0 + resolution: "compression-webpack-plugin@npm:10.0.0" + dependencies: + schema-utils: ^4.0.0 + serialize-javascript: ^6.0.0 + peerDependencies: + webpack: ^5.1.0 + checksum: 9a804645dacb6f1b2e836434258cee374ecc6b589594177a3e3da5b0f8e09b0e2c2c7ca22f47d2214ee88d4e598c9754cd7e2a2081664e55ce6ad425464713e3 + languageName: node + linkType: hard + +"compression@npm:^1.7.4": + version: 1.7.4 + resolution: "compression@npm:1.7.4" + 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 + checksum: 138db836202a406d8a14156a5564fb1700632a76b6e7d1546939472895a5304f2b23c80d7a22bf44c767e87a26e070dbc342ea63bb45ee9c863354fa5556bbbc + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f + languageName: node + linkType: hard + +"concat-stream@npm:^1.5.0": + version: 1.6.2 + resolution: "concat-stream@npm:1.6.2" + dependencies: + buffer-from: ^1.0.0 + inherits: ^2.0.3 + readable-stream: ^2.2.2 + typedarray: ^0.0.6 + checksum: 2e9864e18282946dabbccb212c5c7cec0702745e3671679eb8291812ca7fd12023f7d8cb36493942a62f770ac96a7f90009dc5c82ad69893438371720fa92617 + languageName: node + linkType: hard + +"config-chain@npm:^1.1.13": + version: 1.1.13 + resolution: "config-chain@npm:1.1.13" + dependencies: + ini: ^1.3.4 + proto-list: ~1.2.1 + checksum: 39d1df18739d7088736cc75695e98d7087aea43646351b028dfabd5508d79cf6ef4c5bcd90471f52cd87ae470d1c5490c0a8c1a292fbe6ee9ff688061ea0963e + languageName: node + linkType: hard + +"configstore@npm:^3.0.0": + version: 3.1.5 + resolution: "configstore@npm:3.1.5" + dependencies: + dot-prop: ^4.2.1 + graceful-fs: ^4.1.2 + make-dir: ^1.0.0 + unique-string: ^1.0.0 + write-file-atomic: ^2.0.0 + xdg-basedir: ^3.0.0 + checksum: a68edffee893b1803a108c4083dee481967f7eec232f83499bc86973d93d1e2728c1ea98cb1a4c7c583bc172abbdf197888ba0b0c12640631792186aa233918b + languageName: node + linkType: hard + +"connect-history-api-fallback@npm:^2.0.0": + version: 2.0.0 + resolution: "connect-history-api-fallback@npm:2.0.0" + checksum: 90fa8b16ab76e9531646cc70b010b1dbd078153730c510d3142f6cf07479ae8a812c5a3c0e40a28528dd1681a62395d0cfdef67da9e914c4772ac85d69a3ed87 + languageName: node + linkType: hard + +"console-browserify@npm:1.1.x": + version: 1.1.0 + resolution: "console-browserify@npm:1.1.0" + dependencies: + date-now: ^0.1.4 + checksum: 5d130bcb251bba45d50a857348a63356e9d0d0f268210b65928e0c8420b4d7442a87b547d6bd3d71e7439fe04902e9e211f77eac48795635f767350568b383f5 + languageName: node + linkType: hard + +"console-browserify@npm:^1.1.0": + version: 1.2.0 + resolution: "console-browserify@npm:1.2.0" + checksum: 89b99a53b7d6cee54e1e64fa6b1f7ac24b844b4019c5d39db298637e55c1f4ffa5c165457ad984864de1379df2c8e1886cbbdac85d9dbb6876a9f26c3106f226 + languageName: node + linkType: hard + +"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0, console-control-strings@npm:~1.1.0": + version: 1.1.0 + resolution: "console-control-strings@npm:1.1.0" + checksum: 7ab51d30b52d461412cd467721bb82afe695da78fff8f29fe6f6b9cbaac9a2328e27a22a966014df9532100f6dd85370460be8130b9c677891ba36d96a343f50 + languageName: node + linkType: hard + +"constants-browserify@npm:^1.0.0": + version: 1.0.0 + resolution: "constants-browserify@npm:1.0.0" + checksum: ab49b1d59a433ed77c964d90d19e08b2f77213fb823da4729c0baead55e3c597f8f97ebccfdfc47bd896d43854a117d114c849a6f659d9986420e97da0f83ac5 + languageName: node + linkType: hard + +"content-disposition@npm:0.5.4": + version: 0.5.4 + resolution: "content-disposition@npm:0.5.4" + dependencies: + safe-buffer: 5.2.1 + checksum: bac0316ebfeacb8f381b38285dc691c9939bf0a78b0b7c2d5758acadad242d04783cee5337ba7d12a565a19075af1b3c11c728e1e4946de73c6ff7ce45f3f1bb + languageName: node + linkType: hard + +"content-type@npm:~1.0.4": + version: 1.0.4 + resolution: "content-type@npm:1.0.4" + checksum: 19e08f406f9ae3f80fb4607c75fbde1f22546647877e8047c9fa0b1c61e38f3ede853f51e915c95fd499c2e1c7478cb23c35cfb804d0e8e0495e8db88cfaed75 + languageName: node + linkType: hard + +"convert-source-map@npm:^1.1.0, convert-source-map@npm:^1.4.0, convert-source-map@npm:^1.5.0, convert-source-map@npm:^1.6.0, convert-source-map@npm:^1.7.0": + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: 281da55454bf8126cbc6625385928c43479f2060984180c42f3a86c8b8c12720a24eac260624a7d1e090004028d2dee78602330578ceec1a08e27cb8bb0a8a5b + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"cookie-signature@npm:1.0.6": + version: 1.0.6 + resolution: "cookie-signature@npm:1.0.6" + checksum: b36fd0d4e3fef8456915fcf7742e58fbfcc12a17a018e0eb9501c9d5ef6893b596466f03b0564b81af29ff2538fd0aa4b9d54fe5ccbfb4c90ea50ad29fe2d221 + languageName: node + linkType: hard + +"cookie@npm:0.5.0": + version: 0.5.0 + resolution: "cookie@npm:0.5.0" + checksum: c01ca3ef8d7b8187bae434434582288681273b5a9ed27521d4d7f9f7928fe0c920df0decd9f9d3bbd2d14ac432b8c8cf42b98b3bdd5bfe0e6edddeebebe8b61d + languageName: node + linkType: hard + +"copy-concurrently@npm:^1.0.0": + version: 1.0.5 + resolution: "copy-concurrently@npm:1.0.5" + dependencies: + aproba: ^1.1.1 + fs-write-stream-atomic: ^1.0.8 + iferr: ^0.1.5 + mkdirp: ^0.5.1 + rimraf: ^2.5.4 + run-queue: ^1.0.0 + checksum: c2ce213cb27ee3df584d16eb6c9bfe99cfb531585007533c3e4c752521b4fbf0b2f7f90807d79c496683330808ecd9fdbd9ab9ddfa0913150b7f5097423348ce + languageName: node + linkType: hard + +"copy-descriptor@npm:^0.1.0": + version: 0.1.1 + resolution: "copy-descriptor@npm:0.1.1" + checksum: 161f6760b7348c941007a83df180588fe2f1283e0867cc027182734e0f26134e6cc02de09aa24a95dc267b2e2025b55659eef76c8019df27bc2d883033690181 + languageName: node + linkType: hard + +"copy-to-clipboard@npm:^3.3.1": + version: 3.3.3 + resolution: "copy-to-clipboard@npm:3.3.3" + dependencies: + toggle-selection: ^1.0.6 + checksum: 3ebf5e8ee00601f8c440b83ec08d838e8eabb068c1fae94a9cda6b42f288f7e1b552f3463635f419af44bf7675afc8d0390d30876cf5c2d5d35f86d9c56a3e5f + languageName: node + linkType: hard + +"copy-webpack-plugin@npm:^11.0.0": + version: 11.0.0 + resolution: "copy-webpack-plugin@npm:11.0.0" + dependencies: + fast-glob: ^3.2.11 + glob-parent: ^6.0.1 + globby: ^13.1.1 + normalize-path: ^3.0.0 + schema-utils: ^4.0.0 + serialize-javascript: ^6.0.0 + peerDependencies: + webpack: ^5.1.0 + checksum: a667dd226b26f148584a35fb705f5af926d872584912cf9fd203c14f2b3a68f473a1f5cf768ec1dd5da23820823b850e5d50458b685c468e4a224b25c12a15b4 + languageName: node + linkType: hard + +"core-js-compat@npm:^3.25.1, core-js-compat@npm:^3.8.1": + version: 3.27.2 + resolution: "core-js-compat@npm:3.27.2" + dependencies: + browserslist: ^4.21.4 + checksum: 1bb3b63ae46420b55d78171750f58f73f1a0df8312d3be37111588f068f06a2c18f397737f5603c80fa78bd05f224c93fb60459ff05d80f7dbeeb8a97fbdf457 + languageName: node + linkType: hard + +"core-js-pure@npm:^3.23.3": + version: 3.27.2 + resolution: "core-js-pure@npm:3.27.2" + checksum: 861bb21d83914a01319ddadb42d7b39d253f87b638feaa958208146c0b045f9de984af1a0752dc0484a099b65257746c4a91f8f2c396ce80524126882984079c + languageName: node + linkType: hard + +"core-js@npm:^2.4.0": + version: 2.6.12 + resolution: "core-js@npm:2.6.12" + checksum: 00128efe427789120a06b819adc94cc72b96955acb331cb71d09287baf9bd37bebd191d91f1ee4939c893a050307ead4faea08876f09115112612b6a05684b63 + languageName: node + linkType: hard + +"core-js@npm:^3.0.4, core-js@npm:^3.26.0, core-js@npm:^3.6.0, core-js@npm:^3.6.5, core-js@npm:^3.8.2, core-js@npm:^3.8.3": + version: 3.27.2 + resolution: "core-js@npm:3.27.2" + checksum: dd0041b8bea1033935bb055e15ce81c09eed7f2548485783993bf93923d4e9908b70cdbccac03f9bf6393497eca1d46b476e3eef773fe2ce7d957d1e552ebdbc + languageName: node + linkType: hard + +"core-util-is@npm:1.0.2": + version: 1.0.2 + resolution: "core-util-is@npm:1.0.2" + checksum: 980a37a93956d0de8a828ce508f9b9e3317039d68922ca79995421944146700e4aaf490a6dbfebcb1c5292a7184600c7710b957d724be1e37b8254c6bc0fe246 + languageName: node + linkType: hard + +"core-util-is@npm:~1.0.0": + version: 1.0.3 + resolution: "core-util-is@npm:1.0.3" + checksum: 90a0e40abbddfd7618f8ccd63a74d88deea94e77d0e8dbbea059fa7ebebb8fbb4e2909667fe26f3a467073de1a542ebe6ae4c73a73745ac5833786759cd906c9 + languageName: node + linkType: hard + +"cosmiconfig@npm:^6.0.0": + version: 6.0.0 + resolution: "cosmiconfig@npm:6.0.0" + dependencies: + "@types/parse-json": ^4.0.0 + import-fresh: ^3.1.0 + parse-json: ^5.0.0 + path-type: ^4.0.0 + yaml: ^1.7.2 + checksum: 666ed8732d0bf7d7fe6f8516c8ee6041e0622032e8fa26201577b883d2767ad105d03f38b34b93d1f02f26b22a89e7bab4443b9d2e7f931f48d0e944ffa038b5 + languageName: node + linkType: hard + +"cosmiconfig@npm:^7.0.0, cosmiconfig@npm:^7.0.1": + version: 7.1.0 + resolution: "cosmiconfig@npm:7.1.0" + 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 + checksum: b923ff6af581638128e5f074a5450ba12c0300b71302398ea38dbeabd33bbcaa0245ca9adbedfcf284a07da50f99ede5658c80bb3e39e2ce770a99d28a21ef03 + languageName: node + linkType: hard + +"country-data@npm:0.0.31": + version: 0.0.31 + resolution: "country-data@npm:0.0.31" + dependencies: + currency-symbol-map: ~2 + underscore: ">1.4.4" + checksum: 6e647f273b9b2ca91b02089a872148f141d8e61304ee506e0b1352c8e0de08a3c963c7e6ce9632c88debeb5e5604648acaf31813ef72fdae48315da101233ebd + languageName: node + linkType: hard + +"cp-file@npm:^7.0.0": + version: 7.0.0 + resolution: "cp-file@npm:7.0.0" + dependencies: + graceful-fs: ^4.1.2 + make-dir: ^3.0.0 + nested-error-stacks: ^2.0.0 + p-event: ^4.1.0 + checksum: db3ef3e3e466742f392ae71edb9b2cdbb314e855d97630a65de57bc1097bacf6e844f6d9d44882b8678c0de26ba7e656c2c915960435970067823372e807eafa + languageName: node + linkType: hard + +"cpy@npm:^8.1.2": + version: 8.1.2 + resolution: "cpy@npm:8.1.2" + dependencies: + arrify: ^2.0.1 + cp-file: ^7.0.0 + globby: ^9.2.0 + has-glob: ^1.0.0 + junk: ^3.1.0 + nested-error-stacks: ^2.1.0 + p-all: ^2.1.0 + p-filter: ^2.1.0 + p-map: ^3.0.0 + checksum: 84611fdd526a0582ae501a0fa1e1d55e16348c69110eb17be5fc0c087b7b2aa6caec014286b669e4f123750d01e0c4db77d32fdcdb9840c3df4d161a137a345a + languageName: node + linkType: hard + +"create-ecdh@npm:^4.0.0": + version: 4.0.4 + resolution: "create-ecdh@npm:4.0.4" + dependencies: + bn.js: ^4.1.0 + elliptic: ^6.5.3 + checksum: 77b11a51360fec9c3bce7a76288fc0deba4b9c838d5fb354b3e40c59194d23d66efe6355fd4b81df7580da0661e1334a235a2a5c040b7569ba97db428d466e7f + languageName: node + linkType: hard + +"create-error-class@npm:^3.0.0": + version: 3.0.2 + resolution: "create-error-class@npm:3.0.2" + dependencies: + capture-stack-trace: ^1.0.0 + checksum: e7978884999f7195b20a56c327acf1d742b45c721098691863bd2a933180aa411d5dbe790d3565f3eca6105b829a647497d52e3e00edf1d5c19c1d116def69b6 + languageName: node + linkType: hard + +"create-hash@npm:^1.1.0, create-hash@npm:^1.1.2, create-hash@npm:^1.2.0": + version: 1.2.0 + resolution: "create-hash@npm:1.2.0" + dependencies: + cipher-base: ^1.0.1 + inherits: ^2.0.1 + md5.js: ^1.3.4 + ripemd160: ^2.0.1 + sha.js: ^2.4.0 + checksum: d402e60e65e70e5083cb57af96d89567954d0669e90550d7cec58b56d49c4b193d35c43cec8338bc72358198b8cbf2f0cac14775b651e99238e1cf411490f915 + languageName: node + linkType: hard + +"create-hmac@npm:^1.1.0, create-hmac@npm:^1.1.4, create-hmac@npm:^1.1.7": + version: 1.1.7 + resolution: "create-hmac@npm:1.1.7" + dependencies: + cipher-base: ^1.0.3 + create-hash: ^1.1.0 + inherits: ^2.0.1 + ripemd160: ^2.0.0 + safe-buffer: ^5.0.1 + sha.js: ^2.4.8 + checksum: 24332bab51011652a9a0a6d160eed1e8caa091b802335324ae056b0dcb5acbc9fcf173cf10d128eba8548c3ce98dfa4eadaa01bd02f44a34414baee26b651835 + languageName: node + linkType: hard + +"create-react-class@npm:^15.6.3": + version: 15.7.0 + resolution: "create-react-class@npm:15.7.0" + dependencies: + loose-envify: ^1.3.1 + object-assign: ^4.1.1 + checksum: bce4b46e6d85b424cb50ca8089266c7664fcecfd81abaafb829680fae2e2e60dc6999cac88f5a16a38473ed284859f2328935a42fc5cd1b7cc48888fdd8363c9 + languageName: node + linkType: hard + +"create-require@npm:^1.1.0": + version: 1.1.1 + resolution: "create-require@npm:1.1.1" + checksum: 157cbc59b2430ae9a90034a5f3a1b398b6738bf510f713edc4d4e45e169bc514d3d99dd34d8d01ca7ae7830b5b8b537e46ae8f3c8f932371b0875c0151d7ec91 + languageName: node + linkType: hard + +"cross-fetch@npm:^3.1.5": + version: 3.1.5 + resolution: "cross-fetch@npm:3.1.5" + dependencies: + node-fetch: 2.6.7 + checksum: 29b457f8df11b46b8388a53c947de80bfe04e6466a59c1628c9870b48505b90ec1d28a05b543a0247416a99f1cfe147d1efe373afdeb46a192334ba5fe91b871 + languageName: node + linkType: hard + +"cross-spawn@npm:^5.0.1": + version: 5.1.0 + resolution: "cross-spawn@npm:5.1.0" + dependencies: + lru-cache: ^4.0.1 + shebang-command: ^1.2.0 + which: ^1.2.9 + checksum: 1918621fddb9f8c61e02118b2dbf81f611ccd1544ceaca0d026525341832b8511ce2504c60f935dbc06b35e5ef156fe8c1e72708c27dd486f034e9c0e1e07201 + languageName: node + linkType: hard + +"cross-spawn@npm:^6.0.0": + version: 6.0.5 + resolution: "cross-spawn@npm:6.0.5" + dependencies: + nice-try: ^1.0.4 + path-key: ^2.0.1 + semver: ^5.5.0 + shebang-command: ^1.2.0 + which: ^1.2.9 + checksum: e05544722e9d7189b4292c66e42b7abeb21db0d07c91b785f4ae5fefceb1f89e626da2703744657b287e86dcd4af57b54567cef75159957ff7a8a761d9055012 + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": + version: 7.0.3 + resolution: "cross-spawn@npm:7.0.3" + dependencies: + path-key: ^3.1.0 + shebang-command: ^2.0.0 + which: ^2.0.1 + checksum: 5738c312387081c98d69c98e105b6327b069197f864a60593245d64c8089c8a0a744e16349281210d56835bb9274130d825a78b2ad6853ca13cfbeffc0c31750 + languageName: node + linkType: hard + +"crypto-browserify@npm:^3.11.0, crypto-browserify@npm:^3.12.0": + version: 3.12.0 + resolution: "crypto-browserify@npm:3.12.0" + dependencies: + browserify-cipher: ^1.0.0 + browserify-sign: ^4.0.0 + create-ecdh: ^4.0.0 + create-hash: ^1.1.0 + create-hmac: ^1.1.0 + diffie-hellman: ^5.0.0 + inherits: ^2.0.1 + pbkdf2: ^3.0.3 + public-encrypt: ^4.0.0 + randombytes: ^2.0.0 + randomfill: ^1.0.3 + checksum: 0c20198886576050a6aa5ba6ae42f2b82778bfba1753d80c5e7a090836890dc372bdc780986b2568b4fb8ed2a91c958e61db1f0b6b1cc96af4bd03ffc298ba92 + languageName: node + linkType: hard + +"crypto-random-string@npm:^1.0.0": + version: 1.0.0 + resolution: "crypto-random-string@npm:1.0.0" + checksum: 0cb4dbbb895656919d1de11ba43829a3527edddb85a9c49c9d4c4eb783d3b03fc9f371cefee62c87082fd8758db2798a52a9cad48a7381826190d3c2cf858e4a + languageName: node + linkType: hard + +"crypto-random-string@npm:^2.0.0": + version: 2.0.0 + resolution: "crypto-random-string@npm:2.0.0" + checksum: 288589b2484fe787f9e146f56c4be90b940018f17af1b152e4dde12309042ff5a2bf69e949aab8b8ac253948381529cc6f3e5a2427b73643a71ff177fa122b37 + languageName: node + linkType: hard + +"css-animation@npm:^1.3.2": + version: 1.6.1 + resolution: "css-animation@npm:1.6.1" + dependencies: + babel-runtime: 6.x + component-classes: ^1.2.5 + checksum: fc5ef573f4a676b56c1b588f15cb9ef24086fbb907dd848b35bee1f835f7c0d726db5179e2deeff57865a9ae12c58454cee229949a9e2511b2d47d7d47df7d81 + languageName: node + linkType: hard + +"css-declaration-sorter@npm:^6.3.1": + version: 6.3.1 + resolution: "css-declaration-sorter@npm:6.3.1" + peerDependencies: + postcss: ^8.0.9 + checksum: fc9aa675736eb1c8fc20fd9b8b6abb483c0344a6f1c659d1a9292596bbfe26150a8745a6da23bfa82b0c8a979b6a9ba5d235da0663873f39da1ca42b06caa5c9 + languageName: node + linkType: hard + +"css-line-break@npm:^2.1.0": + version: 2.1.0 + resolution: "css-line-break@npm:2.1.0" + dependencies: + utrie: ^1.0.2 + checksum: b2222d99d5daf7861ecddc050244fdce296fad74b000dcff6bdfb1eb16dc2ef0b9ffe2c1c965e3239bd05ebe9eadb6d5438a91592fa8648d27a338e827cf9048 + languageName: node + linkType: hard + +"css-loader@npm:^3.6.0": + version: 3.6.0 + resolution: "css-loader@npm:3.6.0" + dependencies: + camelcase: ^5.3.1 + cssesc: ^3.0.0 + icss-utils: ^4.1.1 + loader-utils: ^1.2.3 + normalize-path: ^3.0.0 + postcss: ^7.0.32 + postcss-modules-extract-imports: ^2.0.0 + postcss-modules-local-by-default: ^3.0.2 + postcss-modules-scope: ^2.2.0 + postcss-modules-values: ^3.0.0 + postcss-value-parser: ^4.1.0 + schema-utils: ^2.7.0 + semver: ^6.3.0 + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + checksum: ba9065a63f7531d50197207f2c9abb4d75f7e46db27bcfeb6b615a9fb1b1bf48ef4ccdf0f161ff6d35b6fe8752ee3259ee8eeca492666fd2703277d4d3c83534 + languageName: node + linkType: hard + +"css-loader@npm:^5.0.1": + version: 5.2.7 + resolution: "css-loader@npm:5.2.7" + dependencies: + icss-utils: ^5.1.0 + loader-utils: ^2.0.0 + postcss: ^8.2.15 + postcss-modules-extract-imports: ^3.0.0 + postcss-modules-local-by-default: ^4.0.0 + postcss-modules-scope: ^3.0.0 + postcss-modules-values: ^4.0.0 + postcss-value-parser: ^4.1.0 + schema-utils: ^3.0.0 + semver: ^7.3.5 + peerDependencies: + webpack: ^4.27.0 || ^5.0.0 + checksum: 02fbdb0dca92e4a4d2aa27b2817ea51d0af3d662d3295c61f2aa37537b29f9a46a9c2e87d8f5e40a1a97159f35d5c7b9a325f27761b59a38c8e15e8ca3988d2b + languageName: node + linkType: hard + +"css-loader@npm:^6.7.1": + version: 6.7.3 + resolution: "css-loader@npm:6.7.3" + dependencies: + icss-utils: ^5.1.0 + postcss: ^8.4.19 + postcss-modules-extract-imports: ^3.0.0 + postcss-modules-local-by-default: ^4.0.0 + postcss-modules-scope: ^3.0.0 + postcss-modules-values: ^4.0.0 + postcss-value-parser: ^4.2.0 + semver: ^7.3.8 + peerDependencies: + webpack: ^5.0.0 + checksum: 20f435f73d6d776ade4b8dd6c83e7eee65a139f510b2c7575e45d7500ce1b72618b408f4841afc7f34e1aaeef25603ddd10fd4920461907483e1e1e4472bff1f + languageName: node + linkType: hard + +"css-select@npm:^4.1.3": + version: 4.3.0 + resolution: "css-select@npm:4.3.0" + dependencies: + boolbase: ^1.0.0 + css-what: ^6.0.1 + domhandler: ^4.3.1 + domutils: ^2.8.0 + nth-check: ^2.0.1 + checksum: a489d8e5628e61063d5a8fe0fa1cc7ae2478cb334a388a354e91cf2908154be97eac9fa7ed4dffe87a3e06cf6fcaa6016553115335c4fd3377e13dac7bd5a8e1 + languageName: node + linkType: hard + +"css-tree@npm:^1.1.2, css-tree@npm:^1.1.3": + version: 1.1.3 + resolution: "css-tree@npm:1.1.3" + dependencies: + mdn-data: 2.0.14 + source-map: ^0.6.1 + checksum: 499a507bfa39b8b2128f49736882c0dd636b0cd3370f2c69f4558ec86d269113286b7df469afc955de6a68b0dba00bc533e40022a73698081d600072d5d83c1c + languageName: node + linkType: hard + +"css-unit-converter@npm:^1.1.1": + version: 1.1.2 + resolution: "css-unit-converter@npm:1.1.2" + checksum: 540e94213fa5305c49215c6a2ba24d934fa400c0ae0897a87632a3a3a8f4824d168448806180c511dcfb2ecf21cf16be51af0739ff09ef5bcd946f19e9a5624d + languageName: node + linkType: hard + +"css-what@npm:^6.0.1": + version: 6.1.0 + resolution: "css-what@npm:6.1.0" + checksum: a09f5a6b14ba8dcf57ae9a59474722e80f20406c53a61e9aedb0eedc693b135113ffe2983f4efc4b5065ae639442e9ae88df24941ef159c218b231011d733746 + languageName: node + linkType: hard + +"cssesc@npm:^3.0.0": + version: 3.0.0 + resolution: "cssesc@npm:3.0.0" + bin: + cssesc: bin/cssesc + checksum: 6bcfd898662671be15ae7827120472c5667afb3d7429f1f917737f3bf84c4176003228131b643ae74543f17a394446247df090c597bb9a728cce298606ed0aa7 + languageName: node + linkType: hard + +"cssnano-preset-default@npm:^5.2.13": + version: 5.2.13 + resolution: "cssnano-preset-default@npm:5.2.13" + dependencies: + css-declaration-sorter: ^6.3.1 + cssnano-utils: ^3.1.0 + postcss-calc: ^8.2.3 + postcss-colormin: ^5.3.0 + postcss-convert-values: ^5.1.3 + postcss-discard-comments: ^5.1.2 + postcss-discard-duplicates: ^5.1.0 + postcss-discard-empty: ^5.1.1 + postcss-discard-overridden: ^5.1.0 + postcss-merge-longhand: ^5.1.7 + postcss-merge-rules: ^5.1.3 + postcss-minify-font-values: ^5.1.0 + postcss-minify-gradients: ^5.1.1 + postcss-minify-params: ^5.1.4 + postcss-minify-selectors: ^5.2.1 + postcss-normalize-charset: ^5.1.0 + postcss-normalize-display-values: ^5.1.0 + postcss-normalize-positions: ^5.1.1 + postcss-normalize-repeat-style: ^5.1.1 + postcss-normalize-string: ^5.1.0 + postcss-normalize-timing-functions: ^5.1.0 + postcss-normalize-unicode: ^5.1.1 + postcss-normalize-url: ^5.1.0 + postcss-normalize-whitespace: ^5.1.1 + postcss-ordered-values: ^5.1.3 + postcss-reduce-initial: ^5.1.1 + postcss-reduce-transforms: ^5.1.0 + postcss-svgo: ^5.1.0 + postcss-unique-selectors: ^5.1.1 + peerDependencies: + postcss: ^8.2.15 + checksum: c9225ec0a3d91a80b619cde265b559e76ac0a4f1a11cf166769a3c5ba83b05dd55a3d627d3a7b27b3503e2063b9004e94ec5d006cd5a5b45ddf2bac7e2a4ae75 + languageName: node + linkType: hard + +"cssnano-utils@npm:^3.1.0": + version: 3.1.0 + resolution: "cssnano-utils@npm:3.1.0" + peerDependencies: + postcss: ^8.2.15 + checksum: 057508645a3e7584decede1045daa5b362dbfa2f5df96c3527c7d52e41e787a3442a56a8ea0c0af6a757f518e79a459ee580a35c323ad0d0eec912afd67d7630 + languageName: node + linkType: hard + +"cssnano@npm:^5.0.12": + version: 5.1.14 + resolution: "cssnano@npm:5.1.14" + dependencies: + cssnano-preset-default: ^5.2.13 + lilconfig: ^2.0.3 + yaml: ^1.10.2 + peerDependencies: + postcss: ^8.2.15 + checksum: 2360d4046d9b012dbc1f8a6aa03a274d1ea9bc08feb84cb3b5a4031cdce650a71cbdc87457c5ea8aac0ddced351922b28b4f2737683d21d7d73d287c14b068e1 + languageName: node + linkType: hard + +"csso@npm:^4.2.0": + version: 4.2.0 + resolution: "csso@npm:4.2.0" + dependencies: + css-tree: ^1.1.2 + checksum: f8c6b1300efaa0f8855a7905ae3794a29c6496e7f16a71dec31eb6ca7cfb1f058a4b03fd39b66c4deac6cb06bf6b4ba86da7b67d7320389cb9994d52b924b903 + languageName: node + linkType: hard + +"csstype@npm:^3.0.2": + version: 3.1.1 + resolution: "csstype@npm:3.1.1" + checksum: 7c8b8c5923049d84132581c13bae6e1faf999746fe3998ba5f3819a8e1cdc7512ace87b7d0a4a69f0f4b8ba11daf835d4f1390af23e09fc4f0baad52c084753a + languageName: node + linkType: hard + +"currency-symbol-map@npm:~2": + version: 2.2.0 + resolution: "currency-symbol-map@npm:2.2.0" + checksum: 9923d5936176fa530068caf9ec75c5b22b0af1e4e918b8da39faf8da6573b07afa2c440315d59b7da4a63d46683f1c17121ebb94606a400255c3deb3cbd64bb2 + languageName: node + linkType: hard + +"currently-unhandled@npm:^0.4.1": + version: 0.4.1 + resolution: "currently-unhandled@npm:0.4.1" + dependencies: + array-find-index: ^1.0.1 + checksum: 32d197689ec32f035910202c1abb0dc6424dce01d7b51779c685119b380d98535c110ffff67a262fc7e367612a7dfd30d3d3055f9a6634b5a9dd1302de7ef11c + languageName: node + linkType: hard + +"cyclist@npm:^1.0.1": + version: 1.0.1 + resolution: "cyclist@npm:1.0.1" + checksum: 3381d3b66a3b268e6e0abcc1fa8fbeeb9a98391d8455677509f9833813d7680cc737a10141f54c229e42f5b3133250f36f1aa04f56ef4ba9b29fa728c3c48c01 + languageName: node + linkType: hard + +"cypress-image-snapshot@npm:^4.0.1": + version: 4.0.1 + resolution: "cypress-image-snapshot@npm:4.0.1" + dependencies: + chalk: ^2.4.1 + fs-extra: ^7.0.1 + glob: ^7.1.3 + jest-image-snapshot: 4.2.0 + pkg-dir: ^3.0.0 + term-img: ^4.0.0 + peerDependencies: + cypress: ^4.5.0 + checksum: 34e078f9263676663c289dc682d40ecc0ef6f0236e8bb41fcd4868f8e8167da0fb8252c2f1aeacf7bb45a7d7e2bdaf735225e88c35c5e883a33631c0a519e63a + languageName: node + linkType: hard + +"cypress@npm:^12.3.0": + version: 12.3.0 + resolution: "cypress@npm:12.3.0" + dependencies: + "@cypress/request": ^2.88.10 + "@cypress/xvfb": ^1.2.4 + "@types/node": ^14.14.31 + "@types/sinonjs__fake-timers": 8.1.1 + "@types/sizzle": ^2.3.2 + arch: ^2.2.0 + blob-util: ^2.0.2 + bluebird: ^3.7.2 + buffer: ^5.6.0 + cachedir: ^2.3.0 + chalk: ^4.1.0 + check-more-types: ^2.24.0 + cli-cursor: ^3.1.0 + cli-table3: ~0.6.1 + commander: ^5.1.0 + common-tags: ^1.8.0 + dayjs: ^1.10.4 + debug: ^4.3.2 + enquirer: ^2.3.6 + eventemitter2: 6.4.7 + execa: 4.1.0 + executable: ^4.1.1 + extract-zip: 2.0.1 + figures: ^3.2.0 + fs-extra: ^9.1.0 + getos: ^3.2.1 + is-ci: ^3.0.0 + is-installed-globally: ~0.4.0 + lazy-ass: ^1.6.0 + listr2: ^3.8.3 + lodash: ^4.17.21 + log-symbols: ^4.0.0 + minimist: ^1.2.6 + ospath: ^1.2.2 + pretty-bytes: ^5.6.0 + proxy-from-env: 1.0.0 + request-progress: ^3.0.0 + semver: ^7.3.2 + supports-color: ^8.1.1 + tmp: ~0.2.1 + untildify: ^4.0.0 + yauzl: ^2.10.0 + bin: + cypress: bin/cypress + checksum: 116fb3b7510f6307b2550837596ae042bd27bc50ce112269334eba31c7f39516a680cbc7b7f34cf7fd9055178fb2b451893dbfa5abcde8b38f95b8edef1c870d + languageName: node + linkType: hard + +"d3-array@npm:2 - 3, d3-array@npm:2.10.0 - 3, d3-array@npm:^3.1.6": + version: 3.2.2 + resolution: "d3-array@npm:3.2.2" + dependencies: + internmap: 1 - 2 + checksum: a10ce0fc720bafcc163b149676eaf4b0d8bd9b101114bb40e2941ad76eb79b45bcf0e0793022cd4a35280c6b44dadcd326fefc6d8f68b81459ef808fc44bbb03 + languageName: node + linkType: hard + +"d3-color@npm:1 - 3": + version: 3.1.0 + resolution: "d3-color@npm:3.1.0" + checksum: a4e20e1115fa696fce041fbe13fbc80dc4c19150fa72027a7c128ade980bc0eeeba4bcf28c9e21f0bce0e0dbfe7ca5869ef67746541dcfda053e4802ad19783c + languageName: node + linkType: hard + +"d3-ease@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-ease@npm:3.0.1" + checksum: fec8ef826c0cc35cda3092c6841e07672868b1839fcaf556e19266a3a37e6bc7977d8298c0fcb9885e7799bfdcef7db1baaba9cd4dcf4bc5e952cf78574a88b0 + languageName: node + linkType: hard + +"d3-format@npm:1 - 3": + version: 3.1.0 + resolution: "d3-format@npm:3.1.0" + checksum: 049f5c0871ebce9859fc5e2f07f336b3c5bfff52a2540e0bac7e703fce567cd9346f4ad1079dd18d6f1e0eaa0599941c1810898926f10ac21a31fd0a34b4aa75 + languageName: node + linkType: hard + +"d3-interpolate@npm:1.2.0 - 3, d3-interpolate@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-interpolate@npm:3.0.1" + dependencies: + d3-color: 1 - 3 + checksum: 19f4b4daa8d733906671afff7767c19488f51a43d251f8b7f484d5d3cfc36c663f0a66c38fe91eee30f40327443d799be17169f55a293a3ba949e84e57a33e6a + languageName: node + linkType: hard + +"d3-path@npm:^3.1.0": + version: 3.1.0 + resolution: "d3-path@npm:3.1.0" + checksum: dc1d58ec87fa8319bd240cf7689995111a124b141428354e9637aa83059eb12e681f77187e0ada5dedfce346f7e3d1f903467ceb41b379bfd01cd8e31721f5da + languageName: node + linkType: hard + +"d3-scale@npm:^4.0.2": + version: 4.0.2 + resolution: "d3-scale@npm:4.0.2" + 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 + checksum: 65d9ad8c2641aec30ed5673a7410feb187a224d6ca8d1a520d68a7d6eac9d04caedbff4713d1e8545be33eb7fec5739983a7ab1d22d4e5ad35368c6729d362f1 + languageName: node + linkType: hard + +"d3-shape@npm:^3.1.0": + version: 3.2.0 + resolution: "d3-shape@npm:3.2.0" + dependencies: + d3-path: ^3.1.0 + checksum: f1c9d1f09926daaf6f6193ae3b4c4b5521e81da7d8902d24b38694517c7f527ce3c9a77a9d3a5722ad1e3ff355860b014557b450023d66a944eabf8cfde37132 + languageName: node + linkType: hard + +"d3-time-format@npm:2 - 4": + version: 4.1.0 + resolution: "d3-time-format@npm:4.1.0" + dependencies: + d3-time: 1 - 3 + checksum: 735e00fb25a7fd5d418fac350018713ae394eefddb0d745fab12bbff0517f9cdb5f807c7bbe87bb6eeb06249662f8ea84fec075f7d0cd68609735b2ceb29d206 + languageName: node + linkType: hard + +"d3-time@npm:1 - 3, d3-time@npm:2.1.1 - 3, d3-time@npm:^3.0.0": + version: 3.1.0 + resolution: "d3-time@npm:3.1.0" + dependencies: + d3-array: 2 - 3 + checksum: a984f77e1aaeaa182679b46fbf57eceb6ebdb5f67d7578d6f68ef933f8eeb63737c0949991618a8d29472dbf43736c7d7f17c452b2770f8c1271191cba724ca1 + languageName: node + linkType: hard + +"d3-timer@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-timer@npm:3.0.1" + checksum: d4c63cb4bb5461d7038aac561b097cd1c5673969b27cbdd0e87fa48d9300a538b9e6f39b4a7f0e3592ef4f963d858c8a9f0e92754db73116770856f2fc04561a + languageName: node + linkType: hard + +"d@npm:1, d@npm:^1.0.1": + version: 1.0.1 + resolution: "d@npm:1.0.1" + dependencies: + es5-ext: ^0.10.50 + type: ^1.0.1 + checksum: 1fedcb3b956a461f64d86b94b347441beff5cef8910b6ac4ec509a2c67eeaa7093660a98b26601ac91f91260238add73bdf25867a9c0cb783774642bc4c1523f + languageName: node + linkType: hard + +"dashdash@npm:^1.12.0": + version: 1.14.1 + resolution: "dashdash@npm:1.14.1" + dependencies: + assert-plus: ^1.0.0 + checksum: 64589a15c5bd01fa41ff7007e0f2c6552c5ef2028075daa16b188a3721f4ba001841bf306dfc2eee6e2e6e7f76b38f5f17fb21fa847504192290ffa9e150118a + languageName: node + linkType: hard + +"date-now@npm:^0.1.4": + version: 0.1.4 + resolution: "date-now@npm:0.1.4" + checksum: 0e0a04d91deac395dfabc6f279b1bb7fbc66816552104b8dc5a7a5c32340a79eb2e2a27c83a20b6a46c0737dd2c55bf92aa44321911ba1f03adad413ad70ee3e + languageName: node + linkType: hard + +"dayjs@npm:^1.10.4": + version: 1.11.7 + resolution: "dayjs@npm:1.11.7" + checksum: 41a54853c8b8bf0fa94a5559eec98b3e4d11b31af81a9558a159d40adeaafb1f3414e8c41a4e3277281d97687d8252f400015e1f715b47f8c24d88a9ebd43626 + languageName: node + linkType: hard + +"deasync-promise@npm:^1.0.1": + version: 1.0.1 + resolution: "deasync-promise@npm:1.0.1" + dependencies: + deasync: ^0.1.7 + checksum: e8ef2701c41251c9ac82aea9fa52e1478d83eaf51a4e484f6be7321dfe67ded098e54cda25536cdc7390145a397cad0456468f3301a9770cbe42ec3861710592 + languageName: node + linkType: hard + +"deasync@npm:^0.1.7": + version: 0.1.28 + resolution: "deasync@npm:0.1.28" + dependencies: + bindings: ^1.5.0 + node-addon-api: ^1.7.1 + checksum: ba8591905cb50131a131362ac5ed18d9f96f3b0b31e85b080ca426fd8668fd424b50b3b709d031c9b98a772128c4413421e827da53a8810930d9cf248408b0e0 + languageName: node + linkType: hard + +"debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.3.3, debug@npm:^2.6.8": + version: 2.6.9 + resolution: "debug@npm:2.6.9" + dependencies: + ms: 2.0.0 + checksum: 121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589 + languageName: node + linkType: hard + +"debug@npm:3.1.0": + version: 3.1.0 + resolution: "debug@npm:3.1.0" + dependencies: + ms: 2.0.0 + checksum: 5bff34a352d7b2eaa31886eeaf2ee534b5461ec0548315b2f9f80bd1d2533cab7df1fa52e130ce27bc31c3945fbffb0fc72baacdceb274b95ce853db89254ea4 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:~4.3.1, debug@npm:~4.3.2": + version: 4.3.4 + resolution: "debug@npm:4.3.4" + dependencies: + ms: 2.1.2 + peerDependenciesMeta: + supports-color: + optional: true + checksum: cedbec45298dd5c501d01b92b119cd3faebe5438c3917ff11ae1bff86a6c722930ac9c8659792824013168ba6db7c4668225d845c633fbdafbbf902a6389f736 + languageName: node + linkType: hard + +"debug@npm:^3.0.0, debug@npm:^3.1.0": + version: 3.2.7 + resolution: "debug@npm:3.2.7" + dependencies: + ms: ^2.1.1 + checksum: 37d96ae42cbc71c14844d2ae3ba55adf462ec89fd3a999459dec3833944cd999af6007ff29c780f1c61153bcaaf2c842d1e4ce1ec621e4fc4923244942e4a02a + languageName: node + linkType: hard + +"debuglog@npm:*, debuglog@npm:^1.0.1": + version: 1.0.1 + resolution: "debuglog@npm:1.0.1" + checksum: d98ac9abe6a528fcbb4d843b1caf5a9116998c76e1263d8ff4db2c086aa96fa7ea4c752a81050fa2e4304129ef330e6e4dc9dd4d47141afd7db80bf699f08219 + languageName: node + linkType: hard + +"decamelize@npm:^1.1.2, decamelize@npm:^1.2.0": + version: 1.2.0 + resolution: "decamelize@npm:1.2.0" + checksum: 85c39fe8fbf0482d4a1e224ef0119db5c1897f8503bcef8b826adff7a1b11414972f6fef2d7dec2ee0b4be3863cf64ac1439137ae9e6af23a3d8dcbe26a5b4b2 + languageName: node + linkType: hard + +"decimal.js-light@npm:^2.4.1": + version: 2.5.1 + resolution: "decimal.js-light@npm:2.5.1" + checksum: 4fd33f535aac9e5bd832796831b65d9ec7914ad129c7437b3ab991b0c2eaaa5a57e654e6174c4a17f1b3895ea366f0c1ab4955cdcdf7cfdcf3ad5a58b456c020 + languageName: node + linkType: hard + +"decode-uri-component@npm:^0.2.0, decode-uri-component@npm:^0.2.2": + version: 0.2.2 + resolution: "decode-uri-component@npm:0.2.2" + checksum: 1f4fa54eb740414a816b3f6c24818fbfcabd74ac478391e9f4e2282c994127db02010ce804f3d08e38255493cfe68608b3f5c8e09fd6efc4ae46c807691f7a31 + languageName: node + linkType: hard + +"dedent@npm:^0.7.0": + version: 0.7.0 + resolution: "dedent@npm:0.7.0" + checksum: 7c3aa00ddfe3e5fcd477958e156156a5137e3bb6ff1493ca05edff4decf29a90a057974cc77e75951f8eb801c1816cb45aea1f52d628cdd000b82b36ab839d1b + languageName: node + linkType: hard + +"deep-diff@npm:^1.0.2": + version: 1.0.2 + resolution: "deep-diff@npm:1.0.2" + checksum: cc3e315ba95963eba4bbb79ed88d0a37d80ba19bd3b0039b79d2ad0e19e48b0e15c692b49bcd617bbe0dcc7358d40464c993889313dd8bf806bb25978b12375d + languageName: node + linkType: hard + +"deep-equal@npm:^2.0.5": + version: 2.2.0 + resolution: "deep-equal@npm:2.2.0" + dependencies: + call-bind: ^1.0.2 + es-get-iterator: ^1.1.2 + get-intrinsic: ^1.1.3 + is-arguments: ^1.1.1 + is-array-buffer: ^3.0.1 + is-date-object: ^1.0.5 + is-regex: ^1.1.4 + is-shared-array-buffer: ^1.0.2 + isarray: ^2.0.5 + object-is: ^1.1.5 + object-keys: ^1.1.1 + object.assign: ^4.1.4 + regexp.prototype.flags: ^1.4.3 + side-channel: ^1.0.4 + which-boxed-primitive: ^1.0.2 + which-collection: ^1.0.1 + which-typed-array: ^1.1.9 + checksum: 31de99f3c1b516ef67ba82cbe54fdc1691cdd93ab8ede561eee94f7f8baff6594ddc0860c48707f6cd12e4efd5421e3450e20c40ca71906a9d0abe9017944cd3 + languageName: node + linkType: hard + +"deep-extend@npm:^0.6.0": + version: 0.6.0 + resolution: "deep-extend@npm:0.6.0" + checksum: 1c6b0abcdb901e13a44c7d699116d3d4279fdb261983122a3783e7273844d5f2537dc2e1c454a23fcf645917f93fbf8d07101c1d03c015a87faa662755212566 + languageName: node + linkType: hard + +"deep-is@npm:^0.1.3, deep-is@npm:~0.1.3": + version: 0.1.4 + resolution: "deep-is@npm:0.1.4" + checksum: 7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c + languageName: node + linkType: hard + +"deepmerge@npm:^4.2.2": + version: 4.2.2 + resolution: "deepmerge@npm:4.2.2" + checksum: d6136eee869057fea7a829aa2d10073ed49db5216e42a77cc737dd385334aab9b68dae22020a00c24c073d5f79cbbdd3f11b8d4fc87700d112ddaa0e1f968ef2 + languageName: node + linkType: hard + +"default-browser-id@npm:^1.0.4": + version: 1.0.4 + resolution: "default-browser-id@npm:1.0.4" + dependencies: + bplist-parser: ^0.1.0 + meow: ^3.1.0 + untildify: ^2.0.0 + bin: + default-browser-id: cli.js + checksum: a00a2ab66beab70490b4d76258a1f2eadfadca6414bf67ab78aa25b33dc3de0c4c813bb8f204271aa7a08281c39474487db0229e325112456464fb97a0522a8a + languageName: node + linkType: hard + +"default-gateway@npm:^6.0.3": + version: 6.0.3 + resolution: "default-gateway@npm:6.0.3" + dependencies: + execa: ^5.0.0 + checksum: 5184f9e6e105d24fb44ade9e8741efa54bb75e84625c1ea78c4ef8b81dff09ca52d6dbdd1185cf0dc655bb6b282a64fffaf7ed2dd561b8d9ad6f322b1f039aba + languageName: node + linkType: hard + +"defaults@npm:^1.0.3": + version: 1.0.4 + resolution: "defaults@npm:1.0.4" + dependencies: + clone: ^1.0.2 + checksum: 9cfbe498f5c8ed733775db62dfd585780387d93c17477949e1670bfcfb9346e0281ce8c4bf9f4ac1fc0f9b851113bd6dc9e41182ea1644ccd97de639fa13c35a + languageName: node + linkType: hard + +"define-lazy-prop@npm:^2.0.0": + version: 2.0.0 + resolution: "define-lazy-prop@npm:2.0.0" + checksum: db6c63864a9d3b7dc9def55d52764968a5af296de87c1b2cc71d8be8142e445208071953649e0386a8cc37cfcf9a2067a47207f1eb9ff250c2a269658fdae422 + languageName: node + linkType: hard + +"define-properties@npm:^1.1.2, define-properties@npm:^1.1.3, define-properties@npm:^1.1.4": + version: 1.1.4 + resolution: "define-properties@npm:1.1.4" + dependencies: + has-property-descriptors: ^1.0.0 + object-keys: ^1.1.1 + checksum: 1e09acd814c3761f2355d9c8a18fbc2b5d2e1073e1302245c134e96aacbff51b152e2a6f5f5db23af3c43e26f4e3a0d42f569aa4135f49046246c934bfb8e1dc + languageName: node + linkType: hard + +"define-property@npm:^0.2.5": + version: 0.2.5 + resolution: "define-property@npm:0.2.5" + dependencies: + is-descriptor: ^0.1.0 + checksum: 9986915c0893818dedc9ca23eaf41370667762fd83ad8aa4bf026a28563120dbaacebdfbfbf2b18d3b929026b9c6ee972df1dbf22de8fafb5fe6ef18361e4750 + languageName: node + linkType: hard + +"define-property@npm:^1.0.0": + version: 1.0.0 + resolution: "define-property@npm:1.0.0" + dependencies: + is-descriptor: ^1.0.0 + checksum: d7cf09db10d55df305f541694ed51dafc776ad9bb8a24428899c9f2d36b11ab38dce5527a81458d1b5e7c389f8cbe803b4abad6e91a0037a329d153b84fc975e + languageName: node + linkType: hard + +"define-property@npm:^2.0.2": + version: 2.0.2 + resolution: "define-property@npm:2.0.2" + dependencies: + is-descriptor: ^1.0.2 + isobject: ^3.0.1 + checksum: f91a08ad008fa764172a2c072adc7312f10217ade89ddaea23018321c6d71b2b68b8c229141ed2064179404e345c537f1a2457c379824813695b51a6ad3e4969 + languageName: node + linkType: hard + +"defined@npm:^1.0.0": + version: 1.0.1 + resolution: "defined@npm:1.0.1" + checksum: 357212c95fd69c3b431f4766440f1b10a8362d2663b86e3d7c139fe7fc98a1d5a4996b8b55ca62e97fb882f9887374b76944d29f9650a07993d98e7be86a804a + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + +"delegates@npm:^1.0.0": + version: 1.0.0 + resolution: "delegates@npm:1.0.0" + checksum: ba05874b91148e1db4bf254750c042bf2215febd23a6d3cda2e64896aef79745fbd4b9996488bd3cafb39ce19dbce0fd6e3b6665275638befffe1c9b312b91b5 + languageName: node + linkType: hard + +"depd@npm:2.0.0": + version: 2.0.0 + resolution: "depd@npm:2.0.0" + checksum: 58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c + languageName: node + linkType: hard + +"depd@npm:^1.1.2, depd@npm:~1.1.2": + version: 1.1.2 + resolution: "depd@npm:1.1.2" + checksum: acb24aaf936ef9a227b6be6d495f0d2eb20108a9a6ad40585c5bda1a897031512fef6484e4fdbb80bd249fdaa82841fa1039f416ece03188e677ba11bcfda249 + languageName: node + linkType: hard + +"deploy-aws-s3-cloudfront@npm:^3.6.0": + version: 3.8.0 + resolution: "deploy-aws-s3-cloudfront@npm:3.8.0" + dependencies: + aws-sdk: 2 + enquirer: 2 + fast-glob: 3 + md5-file: 5 + micromatch: 4 + mime-types: 2 + winston: 3 + yargs: 17 + bin: + deploy-aws-s3-cloudfront: bin/deploy-aws-s3-cloudfront + checksum: 3b0a5f078c6de8a781512f236cee0811593d153b4e2dc438fb3bdd8004688cec490a07a7221087b78f953db08899917d4e79252d1e6567feba716ca26fe3d41f + languageName: node + linkType: hard + +"des.js@npm:^1.0.0": + version: 1.0.1 + resolution: "des.js@npm:1.0.1" + dependencies: + inherits: ^2.0.1 + minimalistic-assert: ^1.0.0 + checksum: 69bf742d1c381e01d75151bdcaac71a18d251d7debfc9b6ae5ee4b4edaf39691ae203c5ec9173ba89aedb3ddc622cdff4fca065448c6c2afb1140d9fb826339d + languageName: node + linkType: hard + +"destroy@npm:1.2.0": + version: 1.2.0 + resolution: "destroy@npm:1.2.0" + checksum: bd7633942f57418f5a3b80d5cb53898127bcf53e24cdf5d5f4396be471417671f0fee48a4ebe9a1e9defbde2a31280011af58a57e090ff822f589b443ed4e643 + languageName: node + linkType: hard + +"detab@npm:2.0.4": + version: 2.0.4 + resolution: "detab@npm:2.0.4" + dependencies: + repeat-string: ^1.5.4 + checksum: 969c7f5a04fc3f8c52eb3b9db2fd4ba20b9b9ce56c5659ebf4cf93ba6c1be68b651665d053affbe99e76733cf7d134546cdd6be038af368f8365f42a646d5fb8 + languageName: node + linkType: hard + +"detect-indent@npm:~5.0.0": + version: 5.0.0 + resolution: "detect-indent@npm:5.0.0" + checksum: 58d985dd5b4d5e5aad6fe7d8ecc74538fa92c807c894794b8505569e45651bf01a38755b65d9d3d17e512239a26d3131837cbef43cf4226968d5abf175bbcc9d + languageName: node + linkType: hard + +"detect-newline@npm:^2.1.0": + version: 2.1.0 + resolution: "detect-newline@npm:2.1.0" + checksum: cb75c36c59da87115f49fe4aa22507f6c5271bac94c63a056af5d9dea2919208de57b6f0fb4543d6cf635965d10b42729d443589caa302cc76e1fa9f48e55f05 + languageName: node + linkType: hard + +"detect-newline@npm:^3.0.0": + version: 3.1.0 + resolution: "detect-newline@npm:3.1.0" + checksum: c38cfc8eeb9fda09febb44bcd85e467c970d4e3bf526095394e5a4f18bc26dd0cf6b22c69c1fa9969261521c593836db335c2795218f6d781a512aea2fb8209d + languageName: node + linkType: hard + +"detect-node@npm:^2.0.4": + version: 2.1.0 + resolution: "detect-node@npm:2.1.0" + checksum: f039f601790f2e9d4654e499913259a798b1f5246ae24f86ab5e8bd4aaf3bce50484234c494f11fb00aecb0c6e2733aa7b1cf3f530865640b65fbbd65b2c4e09 + languageName: node + linkType: hard + +"detect-package-manager@npm:^2.0.1": + version: 2.0.1 + resolution: "detect-package-manager@npm:2.0.1" + dependencies: + execa: ^5.1.1 + checksum: 56ffd65228d1ff3ead5ea7f8ab951a517a29270de27510b790c9a8b77d4f36efbd61493e170ca77ee3dc13cbb5218583ce65b78ad14a59dc48565c9bcbbf3c71 + languageName: node + linkType: hard + +"detect-port@npm:^1.3.0": + version: 1.5.1 + resolution: "detect-port@npm:1.5.1" + dependencies: + address: ^1.0.1 + debug: 4 + bin: + detect: bin/detect-port.js + detect-port: bin/detect-port.js + checksum: f2b204ad3a9f8e8b53fea35fcc97469f31a8e3e786a2f59fbc886397e33b5f130c5f964bf001b9a64d990047c3824f6a439308461ff19801df04ab48a754639e + languageName: node + linkType: hard + +"detective@npm:^5.2.1": + version: 5.2.1 + resolution: "detective@npm:5.2.1" + dependencies: + acorn-node: ^1.8.2 + defined: ^1.0.0 + minimist: ^1.2.6 + bin: + detective: bin/detective.js + checksum: 0d3bdfe49ef094165e7876d83ae1a9e0a07d037785ab0edc7b50df9e4390e0a050167670f3d2d506457c7b00b612471ba840898964422c425e50fe046a379e55 + languageName: node + linkType: hard + +"dezalgo@npm:^1.0.0, dezalgo@npm:^1.0.4": + version: 1.0.4 + resolution: "dezalgo@npm:1.0.4" + dependencies: + asap: ^2.0.0 + wrappy: 1 + checksum: 8a870ed42eade9a397e6141fe5c025148a59ed52f1f28b1db5de216b4d57f0af7a257070c3af7ce3d5508c1ce9dd5009028a76f4b2cc9370dc56551d2355fad8 + languageName: node + linkType: hard + +"didyoumean@npm:^1.2.2": + version: 1.2.2 + resolution: "didyoumean@npm:1.2.2" + checksum: 95d0b53d23b851aacff56dfadb7ecfedce49da4232233baecfeecb7710248c4aa03f0aa8995062f0acafaf925adf8536bd7044a2e68316fd7d411477599bc27b + languageName: node + linkType: hard + +"diff-sequences@npm:^29.3.1": + version: 29.3.1 + resolution: "diff-sequences@npm:29.3.1" + checksum: adbacc8d56c42f73c12a90e253c2b7024d2cd52c30bd81a3960a9cfd35164f8ecd3d8dd82ded38b0e48585923b56bd28f9aae942e6d1b23f444213b9d786b925 + languageName: node + linkType: hard + +"diff@npm:^4.0.1": + version: 4.0.2 + resolution: "diff@npm:4.0.2" + checksum: 81b91f9d39c4eaca068eb0c1eb0e4afbdc5bb2941d197f513dd596b820b956fef43485876226d65d497bebc15666aa2aa82c679e84f65d5f2bfbf14ee46e32c1 + languageName: node + linkType: hard + +"diffie-hellman@npm:^5.0.0": + version: 5.0.3 + resolution: "diffie-hellman@npm:5.0.3" + dependencies: + bn.js: ^4.1.0 + miller-rabin: ^4.0.0 + randombytes: ^2.0.0 + checksum: ce53ccafa9ca544b7fc29b08a626e23a9b6562efc2a98559a0c97b4718937cebaa9b5d7d0a05032cc9c1435e9b3c1532b9e9bf2e0ede868525922807ad6e1ecf + languageName: node + linkType: hard + +"dir-glob@npm:^2.2.2": + version: 2.2.2 + resolution: "dir-glob@npm:2.2.2" + dependencies: + path-type: ^3.0.0 + checksum: 67575fd496df80ec90969f1a9f881f03b4ef614ca2c07139df81a12f9816250780dff906f482def0f897dd748d22fa13c076b52ac635e0024f7d434846077a3a + languageName: node + linkType: hard + +"dir-glob@npm:^3.0.1": + version: 3.0.1 + resolution: "dir-glob@npm:3.0.1" + dependencies: + path-type: ^4.0.0 + checksum: dcac00920a4d503e38bb64001acb19df4efc14536ada475725e12f52c16777afdee4db827f55f13a908ee7efc0cb282e2e3dbaeeb98c0993dd93d1802d3bf00c + languageName: node + linkType: hard + +"dlv@npm:^1.1.3": + version: 1.1.3 + resolution: "dlv@npm:1.1.3" + checksum: 03eb4e769f19a027fd5b43b59e8a05e3fd2100ac239ebb0bf9a745de35d449e2f25cfaf3aa3934664551d72856f4ae8b7822016ce5c42c2d27c18ae79429ec42 + languageName: node + linkType: hard + +"dnd-core@npm:15.1.2": + version: 15.1.2 + resolution: "dnd-core@npm:15.1.2" + dependencies: + "@react-dnd/asap": 4.0.1 + "@react-dnd/invariant": 3.0.1 + redux: ^4.1.2 + checksum: 6498b961f7f7c4f6cfdc52c4ab86f448efccde584105ac88b149d46b5eb8510c3a2f6a89ae39b64e2f49011a7ab835fc27edeeeb0b54cc1608c00271d72156bd + languageName: node + linkType: hard + +"dnd-core@npm:^16.0.1": + version: 16.0.1 + resolution: "dnd-core@npm:16.0.1" + dependencies: + "@react-dnd/asap": ^5.0.1 + "@react-dnd/invariant": ^4.0.1 + redux: ^4.2.0 + checksum: 6b852c576c88b0a42e618efb37e046334f5e9914b8d38ad139933dd9595b6caf2a484953a6301094d23119c17479549553d71e92fd77fa37318122ea1e579f65 + languageName: node + linkType: hard + +"dns-equal@npm:^1.0.0": + version: 1.0.0 + resolution: "dns-equal@npm:1.0.0" + checksum: da966e5275ac50546e108af6bc29aaae2164d2ae96d60601b333c4a3aff91f50b6ca14929cf91f20a9cad1587b356323e300cea3ff6588a6a816988485f445f1 + languageName: node + linkType: hard + +"dns-packet@npm:^5.2.2": + version: 5.4.0 + resolution: "dns-packet@npm:5.4.0" + dependencies: + "@leichtgewicht/ip-codec": ^2.0.1 + checksum: bd5ecfd7d8b9cacd4d0029819699051c4e231d8fa6ed96e1573f7fee4b9147c3406207a260adbd7fb5c6d08a7db7641836467f450fa88e2ec5075f482e39ed77 + languageName: node + linkType: hard + +"doctrine@npm:^2.1.0": + version: 2.1.0 + resolution: "doctrine@npm:2.1.0" + dependencies: + esutils: ^2.0.2 + checksum: b6416aaff1f380bf56c3b552f31fdf7a69b45689368deca72d28636f41c16bb28ec3ebc40ace97db4c1afc0ceeb8120e8492fe0046841c94c2933b2e30a7d5ac + languageName: node + linkType: hard + +"doctrine@npm:^3.0.0": + version: 3.0.0 + resolution: "doctrine@npm:3.0.0" + dependencies: + esutils: ^2.0.2 + checksum: c96bdccabe9d62ab6fea9399fdff04a66e6563c1d6fb3a3a063e8d53c3bb136ba63e84250bbf63d00086a769ad53aef92d2bd483f03f837fc97b71cbee6b2520 + languageName: node + linkType: hard + +"dom-accessibility-api@npm:^0.5.9": + version: 0.5.16 + resolution: "dom-accessibility-api@npm:0.5.16" + checksum: b2c2eda4fae568977cdac27a9f0c001edf4f95a6a6191dfa611e3721db2478d1badc01db5bb4fa8a848aeee13e442a6c2a4386d65ec65a1436f24715a2f8d053 + languageName: node + linkType: hard + +"dom-align@npm:^1.7.0": + version: 1.12.4 + resolution: "dom-align@npm:1.12.4" + checksum: 358f1601fc6b6518c0726ee99e9124212b34ca2828a194c816f247b913415416098cf016391f89741cddccf9b98a98a077469d565630bd4f8143edac81a97186 + languageName: node + linkType: hard + +"dom-converter@npm:^0.2.0": + version: 0.2.0 + resolution: "dom-converter@npm:0.2.0" + dependencies: + utila: ~0.4 + checksum: e96aa63bd8c6ee3cd9ce19c3aecfc2c42e50a460e8087114794d4f5ecf3a4f052b34ea3bf2d73b5d80b4da619073b49905e6d7d788ceb7814ca4c29be5354a11 + languageName: node + linkType: hard + +"dom-helpers@npm:^3.4.0": + version: 3.4.0 + resolution: "dom-helpers@npm:3.4.0" + dependencies: + "@babel/runtime": ^7.1.2 + checksum: 1d2d3e4eadac2c4f4c8c7470a737ab32b7ec28237c4d094ea967ec3184168fd12452196fcc424a5d7860b6176117301aeaecba39467bf1a6e8492a8e5c9639d1 + languageName: node + linkType: hard + +"dom-helpers@npm:^5.0.1, dom-helpers@npm:^5.1.3": + version: 5.2.1 + resolution: "dom-helpers@npm:5.2.1" + dependencies: + "@babel/runtime": ^7.8.7 + csstype: ^3.0.2 + checksum: f735074d66dd759b36b158fa26e9d00c9388ee0e8c9b16af941c38f014a37fc80782de83afefd621681b19ac0501034b4f1c4a3bff5caa1b8667f0212b5e124c + languageName: node + linkType: hard + +"dom-serializer@npm:0": + version: 0.2.2 + resolution: "dom-serializer@npm:0.2.2" + dependencies: + domelementtype: ^2.0.1 + entities: ^2.0.0 + checksum: 5cb595fb77e1a23eca56742f47631e6f4af66ce1982c7ed28b3d0ef21f1f50304c067adc29d3eaf824c572be022cee88627d0ac9b929408f24e923f3c7bed37b + languageName: node + linkType: hard + +"dom-serializer@npm:^1.0.1": + version: 1.4.1 + resolution: "dom-serializer@npm:1.4.1" + dependencies: + domelementtype: ^2.0.1 + domhandler: ^4.2.0 + entities: ^2.0.0 + checksum: 67d775fa1ea3de52035c98168ddcd59418356943b5eccb80e3c8b3da53adb8e37edb2cc2f885802b7b1765bf5022aec21dfc32910d7f9e6de4c3148f095ab5e0 + languageName: node + linkType: hard + +"dom-walk@npm:^0.1.0": + version: 0.1.2 + resolution: "dom-walk@npm:0.1.2" + checksum: 4d2ad9062a9423d890f8577aa202b597a6b85f9489bdde656b9443901b8b322b289655c3affefc58ec2e41931e0828dfee0a1d2db6829a607d76def5901fc5a9 + languageName: node + linkType: hard + +"domain-browser@npm:^1.1.1": + version: 1.2.0 + resolution: "domain-browser@npm:1.2.0" + checksum: a955f482f4b4710fbd77c12a33e77548d63603c30c80f61a80519f27e3db1ba8530b914584cc9e9365d2038753d6b5bd1f4e6c81e432b007b0ec95b8b5e69b1b + languageName: node + linkType: hard + +"domelementtype@npm:1": + version: 1.3.1 + resolution: "domelementtype@npm:1.3.1" + checksum: 6d4f5761060a21eaf3c96545501e9d188745c7e1c31b8d141bf15d8748feeadba868f4ea32877751b8678b286fb1afbe6ae905ca3fb8f0214d8322e482cdbec0 + languageName: node + linkType: hard + +"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0": + version: 2.3.0 + resolution: "domelementtype@npm:2.3.0" + checksum: 686f5a9ef0fff078c1412c05db73a0dce096190036f33e400a07e2a4518e9f56b1e324f5c576a0a747ef0e75b5d985c040b0d51945ce780c0dd3c625a18cd8c9 + languageName: node + linkType: hard + +"domhandler@npm:2.3": + version: 2.3.0 + resolution: "domhandler@npm:2.3.0" + dependencies: + domelementtype: 1 + checksum: f434a1c08392821751b85081fd8ff11b17d7fd6e5da59335af87ee038b816be24d35a12f45d85034e3e137158beb031d5a3df21fcd05a7dd4490e2f01a6d0e82 + languageName: node + linkType: hard + +"domhandler@npm:^4.0.0, domhandler@npm:^4.2.0, domhandler@npm:^4.3.1": + version: 4.3.1 + resolution: "domhandler@npm:4.3.1" + dependencies: + domelementtype: ^2.2.0 + checksum: 5c199c7468cb052a8b5ab80b13528f0db3d794c64fc050ba793b574e158e67c93f8336e87fd81e9d5ee43b0e04aea4d8b93ed7be4899cb726a1601b3ba18538b + languageName: node + linkType: hard + +"dompurify@npm:^2.2.0": + version: 2.4.3 + resolution: "dompurify@npm:2.4.3" + checksum: 4c93f5bc8855bbe7dcb33487c0b252a00309fbd8a6d0ec280abbc3af695b43d1bf7f526c2f323fa697314b0b3de3511c756005dddc6ed90d1a1440a3d6ff89d9 + languageName: node + linkType: hard + +"domutils@npm:1.5": + version: 1.5.1 + resolution: "domutils@npm:1.5.1" + dependencies: + dom-serializer: 0 + domelementtype: 1 + checksum: 8707a18c974be54d33fd846d174d523ddf4955b2fcc1ec713cbe6ff490f60da22106b153fea6269332477eb81dc1a25a83f5b2afaf78b6dc9e2161fd7b80f7ba + languageName: node + linkType: hard + +"domutils@npm:^2.5.2, domutils@npm:^2.8.0": + version: 2.8.0 + resolution: "domutils@npm:2.8.0" + dependencies: + dom-serializer: ^1.0.1 + domelementtype: ^2.2.0 + domhandler: ^4.2.0 + checksum: d58e2ae01922f0dd55894e61d18119924d88091837887bf1438f2327f32c65eb76426bd9384f81e7d6dcfb048e0f83c19b222ad7101176ad68cdc9c695b563db + languageName: node + linkType: hard + +"dot-case@npm:^3.0.4": + version: 3.0.4 + resolution: "dot-case@npm:3.0.4" + dependencies: + no-case: ^3.0.4 + tslib: ^2.0.3 + checksum: 5b859ea65097a7ea870e2c91b5768b72ddf7fa947223fd29e167bcdff58fe731d941c48e47a38ec8aa8e43044c8fbd15cd8fa21689a526bc34b6548197cd5b05 + languageName: node + linkType: hard + +"dot-prop@npm:^4.2.1": + version: 4.2.1 + resolution: "dot-prop@npm:4.2.1" + dependencies: + is-obj: ^1.0.0 + checksum: ea0a98871ef4de0cce05325979517a43b70eb3a3671254fce78f2629c125d5ddb69cfdd5570ace4e41d9f02ced06374ea0444d1aeae70290a19f73e02093318e + languageName: node + linkType: hard + +"dotenv-expand@npm:^5.1.0": + version: 5.1.0 + resolution: "dotenv-expand@npm:5.1.0" + checksum: 24ac633de853ef474d0421cc639328b7134109c8dc2baaa5e3afb7495af5e9237136d7e6971e55668e4dce915487eb140967cdd2b3e99aa439e0f6bf8b56faeb + languageName: node + linkType: hard + +"dotenv@npm:^5.0.1": + version: 5.0.1 + resolution: "dotenv@npm:5.0.1" + checksum: 3f111d39bc21d4088bd84183eeb8d6340292669909e41063bbe5ab0726618e9a4879e546e873a0e3193cda9d16bb99ff195a5bf97472cd9bc08368af9bdaa246 + languageName: node + linkType: hard + +"dotenv@npm:^6.2.0": + version: 6.2.0 + resolution: "dotenv@npm:6.2.0" + checksum: 56886938622c34255c89ec24d584460668a5ca035afe37da7b16bfbac36f8b352d20a6dde51000b30db04fa5cac7b03caf165919fe5e9bd8c91a2735fd61c649 + languageName: node + linkType: hard + +"dotenv@npm:^8.0.0": + version: 8.6.0 + resolution: "dotenv@npm:8.6.0" + checksum: 6750431dea8efbd54b9f2d9681b04e1ccc7989486461dcf058bb708d9e3d63b04115fcdf8840e38ad1e24a4a2e1e7c1560626c5e3ac7bc09371b127c49e2d45f + languageName: node + linkType: hard + +"duplexer3@npm:^0.1.4": + version: 0.1.5 + resolution: "duplexer3@npm:0.1.5" + checksum: 02195030d61c4d6a2a34eca71639f2ea5e05cb963490e5bd9527623c2ac7f50c33842a34d14777ea9cbfd9bc2be5a84065560b897d9fabb99346058a5b86ca98 + languageName: node + linkType: hard + +"duplexer@npm:^0.1.2": + version: 0.1.2 + resolution: "duplexer@npm:0.1.2" + checksum: c57bcd4bdf7e623abab2df43a7b5b23d18152154529d166c1e0da6bee341d84c432d157d7e97b32fecb1bf3a8b8857dd85ed81a915789f550637ed25b8e64fc2 + languageName: node + linkType: hard + +"duplexify@npm:^3.4.2, duplexify@npm:^3.6.0": + version: 3.7.1 + resolution: "duplexify@npm:3.7.1" + dependencies: + end-of-stream: ^1.0.0 + inherits: ^2.0.1 + readable-stream: ^2.0.0 + stream-shift: ^1.0.0 + checksum: 59d1440c1b4e3a4db35ae96933392703ce83518db1828d06b9b6322920d6cbbf0b7159e88be120385fe459e77f1eb0c7622f26e9ec1f47c9ff05c2b35747dbd3 + languageName: node + linkType: hard + +"ecc-jsbn@npm:~0.1.1": + version: 0.1.2 + resolution: "ecc-jsbn@npm:0.1.2" + dependencies: + jsbn: ~0.1.0 + safer-buffer: ^2.1.0 + checksum: 6cf168bae1e2dad2e46561d9af9cbabfbf5ff592176ad4e9f0f41eaaf5fe5e10bb58147fe0a804de62b1ee9dad42c28810c88d652b21b6013c47ba8efa274ca1 + languageName: node + linkType: hard + +"editor@npm:~1.0.0": + version: 1.0.0 + resolution: "editor@npm:1.0.0" + checksum: 64b786af539d7675cb3c4503f1660e63a4968c0b7071be9a5b2dcfa3b2011444c39b242c228e90623b5014a58c43bd3ee0238f308ccb3ebbf6f666374eecbd50 + languageName: node + linkType: hard + +"ee-first@npm:1.1.1": + version: 1.1.1 + resolution: "ee-first@npm:1.1.1" + checksum: b5bb125ee93161bc16bfe6e56c6b04de5ad2aa44234d8f644813cc95d861a6910903132b05093706de2b706599367c4130eb6d170f6b46895686b95f87d017b7 + languageName: node + linkType: hard + +"ejs@npm:^3.1.6": + version: 3.1.8 + resolution: "ejs@npm:3.1.8" + dependencies: + jake: ^10.8.5 + bin: + ejs: bin/cli.js + checksum: a6bd58633c5b3ae19a2bfea1b94033585ad85c87ec15961f8c89c93ffdafb8b2358af827f37f7552b35d9f5393fdbd98d35a8cbcd0ee2540b7f9f7a194e86a1a + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.4.251": + version: 1.4.284 + resolution: "electron-to-chromium@npm:1.4.284" + checksum: 33a7509755efbc0e13e81cdf0486ed37ea354857213b92a987a81e229083c1b2ee5f663c1103db9e5ec142a611e0daeeee02f757f7184833866f8aecb7046c2b + languageName: node + linkType: hard + +"elliptic@npm:^6.5.3": + version: 6.5.4 + resolution: "elliptic@npm:6.5.4" + dependencies: + bn.js: ^4.11.9 + brorand: ^1.1.0 + hash.js: ^1.0.0 + hmac-drbg: ^1.0.1 + inherits: ^2.0.4 + minimalistic-assert: ^1.0.1 + minimalistic-crypto-utils: ^1.0.1 + checksum: 5f361270292c3b27cf0843e84526d11dec31652f03c2763c6c2b8178548175ff5eba95341dd62baff92b2265d1af076526915d8af6cc9cb7559c44a62f8ca6e2 + languageName: node + linkType: hard + +"emittery@npm:^0.13.1": + version: 0.13.1 + resolution: "emittery@npm:0.13.1" + checksum: 1573d0ae29ab34661b6c63251ff8f5facd24ccf6a823f19417ae8ba8c88ea450325788c67f16c99edec8de4b52ce93a10fe441ece389fd156e88ee7dab9bfa35 + languageName: node + linkType: hard + +"emoji-regex@npm:^7.0.1": + version: 7.0.3 + resolution: "emoji-regex@npm:7.0.3" + checksum: a8917d695c3a3384e4b7230a6a06fd2de6b3db3709116792e8b7b36ddbb3db4deb28ad3e983e70d4f2a1f9063b5dab9025e4e26e9ca08278da4fbb73e213743f + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 + languageName: node + linkType: hard + +"emojis-list@npm:^3.0.0": + version: 3.0.0 + resolution: "emojis-list@npm:3.0.0" + checksum: 7dc4394b7b910444910ad64b812392159a21e1a7ecc637c775a440227dcb4f80eff7fe61f4453a7d7603fa23d23d30cc93fe9e4b5ed985b88d6441cd4a35117b + languageName: node + linkType: hard + +"enabled@npm:2.0.x": + version: 2.0.0 + resolution: "enabled@npm:2.0.0" + checksum: 3b2c2af9bc7f8b9e291610f2dde4a75cf6ee52a68f4dd585482fbdf9a55d65388940e024e56d40bb03e05ef6671f5f53021fa8b72a20e954d7066ec28166713f + languageName: node + linkType: hard + +"encodeurl@npm:~1.0.2": + version: 1.0.2 + resolution: "encodeurl@npm:1.0.2" + checksum: f6c2387379a9e7c1156c1c3d4f9cb7bb11cf16dd4c1682e1f6746512564b053df5781029b6061296832b59fb22f459dbe250386d217c2f6e203601abb2ee0bec + languageName: node + linkType: hard + +"encoding@npm:^0.1.11, encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: ^0.6.2 + checksum: 36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 + languageName: node + linkType: hard + +"end-of-stream@npm:^1.0.0, end-of-stream@npm:^1.1.0": + version: 1.4.4 + resolution: "end-of-stream@npm:1.4.4" + dependencies: + once: ^1.4.0 + checksum: 870b423afb2d54bb8d243c63e07c170409d41e20b47eeef0727547aea5740bd6717aca45597a9f2745525667a6b804c1e7bede41f856818faee5806dd9ff3975 + languageName: node + linkType: hard + +"endent@npm:^2.0.1": + version: 2.1.0 + resolution: "endent@npm:2.1.0" + dependencies: + dedent: ^0.7.0 + fast-json-parse: ^1.0.3 + objectorarray: ^1.0.5 + checksum: 8cd6dae45e693ae2b2cbff2384348d3a5e2a06cc0396dddca8165e46bd2fd8d5394d44d338ba653bbfce4aead90eca1ec1abe7203843c84155c645d283b6b884 + languageName: node + linkType: hard + +"engine.io-client@npm:~6.2.3": + version: 6.2.3 + resolution: "engine.io-client@npm:6.2.3" + dependencies: + "@socket.io/component-emitter": ~3.1.0 + debug: ~4.3.1 + engine.io-parser: ~5.0.3 + ws: ~8.2.3 + xmlhttprequest-ssl: ~2.0.0 + checksum: f0f336627c26ba2712480f247e50273c717b13969970c6abc633c26ef993f9aa69584636605f18c4ca1e853408ab7add430d73053193e41af451665cf615f7da + languageName: node + linkType: hard + +"engine.io-parser@npm:~5.0.3": + version: 5.0.6 + resolution: "engine.io-parser@npm:5.0.6" + checksum: ef29b7877f9b9a9278ae2077685fea7926b8ae82fc72e8d81b4a622658e64205607000dfa4062a08c9c68037859f865c348318f42aa41aa3065bc01a5be2cb8f + languageName: node + linkType: hard + +"enhanced-resolve@npm:^4.5.0": + version: 4.5.0 + resolution: "enhanced-resolve@npm:4.5.0" + dependencies: + graceful-fs: ^4.1.2 + memory-fs: ^0.5.0 + tapable: ^1.0.0 + checksum: d95fc630606ea35bed21c4a029bbb1681919571a2d1d2011c7fc42a26a9e48ed3d74a89949ce331e1fd3229850a303e3218b887b92951330f16bdfbb93a10e64 + languageName: node + linkType: hard + +"enhanced-resolve@npm:^5.10.0": + version: 5.12.0 + resolution: "enhanced-resolve@npm:5.12.0" + dependencies: + graceful-fs: ^4.2.4 + tapable: ^2.2.0 + checksum: 5738924cfe3641d04b89c2856fee3d109d7bd71bbe234fb7f54843dda65f293e5f3eee6d5970ced70dbb09016085b961e60d1eb26cac72a21044479954b6cdfd + languageName: node + linkType: hard + +"enquirer@npm:2, enquirer@npm:^2.3.6": + version: 2.3.6 + resolution: "enquirer@npm:2.3.6" + dependencies: + ansi-colors: ^4.1.1 + checksum: 8e070e052c2c64326a2803db9084d21c8aaa8c688327f133bf65c4a712586beb126fd98c8a01cfb0433e82a4bd3b6262705c55a63e0f7fb91d06b9cedbde9a11 + languageName: node + linkType: hard + +"entities@npm:1.0": + version: 1.0.0 + resolution: "entities@npm:1.0.0" + checksum: fd382add860bab507c942a054ef98445028bf988d16f53cbae24c70533c280d4ea116a5bc6308f6ca66901818faf4f495316f9873c6337af7cffaaf3859da407 + languageName: node + linkType: hard + +"entities@npm:^2.0.0": + version: 2.2.0 + resolution: "entities@npm:2.2.0" + checksum: 7fba6af1f116300d2ba1c5673fc218af1961b20908638391b4e1e6d5850314ee2ac3ec22d741b3a8060479911c99305164aed19b6254bde75e7e6b1b2c3f3aa3 + languageName: node + linkType: hard + +"entities@npm:^4.4.0": + version: 4.4.0 + resolution: "entities@npm:4.4.0" + checksum: b7971419897622d3996bbbff99249e166caaaf3ea95d3841d6dc5d3bf315f133b649fbe932623e3cc527d871112e7563a8284e24f23e472126aa90c4e9c3215b + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"envinfo@npm:^7.7.3": + version: 7.8.1 + resolution: "envinfo@npm:7.8.1" + bin: + envinfo: dist/cli.js + checksum: 01efe7fcf55d4b84a146bc638ef89a89a70b610957db64636ac7cc4247d627eeb1c808ed79d3cfbe3d4fed5e8ba3d61db79c1ca1a3fea9f38639561eefd68733 + languageName: node + linkType: hard + +"err-code@npm:^1.0.0": + version: 1.1.2 + resolution: "err-code@npm:1.1.2" + checksum: c5c0daadf1f1fb6065e97f1f76d66a72a77d6b38e1e4069e170579ed4d99058acc6d61b9248e8fdd27a74a7f489eb3aa1e376f24342fd4b792b13fcc2065b2c3 + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 + languageName: node + linkType: hard + +"errno@npm:^0.1.3, errno@npm:~0.1.7": + version: 0.1.8 + resolution: "errno@npm:0.1.8" + dependencies: + prr: ~1.0.1 + bin: + errno: cli.js + checksum: 83758951967ec57bf00b5f5b7dc797e6d65a6171e57ea57adcf1bd1a0b477fd9b5b35fae5be1ff18f4090ed156bce1db749fe7e317aac19d485a5d150f6a4936 + languageName: node + linkType: hard + +"error-ex@npm:^1.2.0, error-ex@npm:^1.3.1": + version: 1.3.2 + resolution: "error-ex@npm:1.3.2" + dependencies: + is-arrayish: ^0.2.1 + checksum: ba827f89369b4c93382cfca5a264d059dfefdaa56ecc5e338ffa58a6471f5ed93b71a20add1d52290a4873d92381174382658c885ac1a2305f7baca363ce9cce + languageName: node + linkType: hard + +"error-stack-parser@npm:^2.0.6": + version: 2.1.4 + resolution: "error-stack-parser@npm:2.1.4" + dependencies: + stackframe: ^1.3.4 + checksum: 7679b780043c98b01fc546725484e0cfd3071bf5c906bbe358722972f04abf4fc3f0a77988017665bab367f6ef3fc2d0185f7528f45966b83e7c99c02d5509b9 + languageName: node + linkType: hard + +"es-abstract@npm:^1.19.0, es-abstract@npm:^1.20.4": + version: 1.21.1 + resolution: "es-abstract@npm:1.21.1" + dependencies: + available-typed-arrays: ^1.0.5 + call-bind: ^1.0.2 + es-set-tostringtag: ^2.0.1 + es-to-primitive: ^1.2.1 + function-bind: ^1.1.1 + function.prototype.name: ^1.1.5 + get-intrinsic: ^1.1.3 + 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.4 + is-array-buffer: ^3.0.1 + 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.2 + object-keys: ^1.1.1 + object.assign: ^4.1.4 + regexp.prototype.flags: ^1.4.3 + safe-regex-test: ^1.0.0 + string.prototype.trimend: ^1.0.6 + string.prototype.trimstart: ^1.0.6 + typed-array-length: ^1.0.4 + unbox-primitive: ^1.0.2 + which-typed-array: ^1.1.9 + checksum: f05b7c6a0c2ff951bb358e252daa3b059de6aad2222d1338352a104c252824e9eeba7c18961b7e56b9d1bfb39f99580469144b39f05ec44af170b10dd69d4221 + languageName: node + linkType: hard + +"es-array-method-boxes-properly@npm:^1.0.0": + version: 1.0.0 + resolution: "es-array-method-boxes-properly@npm:1.0.0" + checksum: 4b7617d3fbd460d6f051f684ceca6cf7e88e6724671d9480388d3ecdd72119ddaa46ca31f2c69c5426a82e4b3091c1e81867c71dcdc453565cd90005ff2c382d + languageName: node + linkType: hard + +"es-get-iterator@npm:^1.0.2, es-get-iterator@npm:^1.1.2": + version: 1.1.3 + resolution: "es-get-iterator@npm:1.1.3" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.1.3 + has-symbols: ^1.0.3 + is-arguments: ^1.1.1 + is-map: ^2.0.2 + is-set: ^2.0.2 + is-string: ^1.0.7 + isarray: ^2.0.5 + stop-iteration-iterator: ^1.0.0 + checksum: ebd11effa79851ea75d7f079405f9d0dc185559fd65d986c6afea59a0ff2d46c2ed8675f19f03dce7429d7f6c14ff9aede8d121fbab78d75cfda6a263030bac0 + languageName: node + linkType: hard + +"es-module-lexer@npm:^0.9.0": + version: 0.9.3 + resolution: "es-module-lexer@npm:0.9.3" + checksum: be77d73aee709fdc68d22b9938da81dfee3bc45e8d601629258643fe5bfdab253d6e2540035e035cfa8cf52a96366c1c19b46bcc23b4507b1d44e5907d2e7f6c + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.0.1": + version: 2.0.1 + resolution: "es-set-tostringtag@npm:2.0.1" + dependencies: + get-intrinsic: ^1.1.3 + has: ^1.0.3 + has-tostringtag: ^1.0.0 + checksum: 9af096365e3861bb29755cc5f76f15f66a7eab0e83befca396129090c1d9737e54090278b8e5357e97b5f0a5b0459fca07c40c6740884c2659cbf90ef8e508cc + languageName: node + linkType: hard + +"es-shim-unscopables@npm:^1.0.0": + version: 1.0.0 + resolution: "es-shim-unscopables@npm:1.0.0" + dependencies: + has: ^1.0.3 + checksum: d54a66239fbd19535b3e50333913260394f14d2d7adb136a95396a13ca584bab400cf9cb2ffd9232f3fe2f0362540bd3a708240c493e46e13fe0b90cfcfedc3d + languageName: node + linkType: hard + +"es-to-primitive@npm:^1.2.1": + version: 1.2.1 + resolution: "es-to-primitive@npm:1.2.1" + dependencies: + is-callable: ^1.1.4 + is-date-object: ^1.0.1 + is-symbol: ^1.0.2 + checksum: 0886572b8dc075cb10e50c0af62a03d03a68e1e69c388bd4f10c0649ee41b1fbb24840a1b7e590b393011b5cdbe0144b776da316762653685432df37d6de60f1 + languageName: node + linkType: hard + +"es5-ext@npm:^0.10.35, es5-ext@npm:^0.10.50": + version: 0.10.62 + resolution: "es5-ext@npm:0.10.62" + dependencies: + es6-iterator: ^2.0.3 + es6-symbol: ^3.1.3 + next-tick: ^1.1.0 + checksum: 72dfbec5e4bce24754be9f2c2a1c67c01de3fe000103c115f52891f6a51f44a59674c40a1f6bd2390fcd43987746dccb76efafea91c7bb6295bdca8d63ba3db4 + languageName: node + linkType: hard + +"es5-shim@npm:^4.5.13": + version: 4.6.7 + resolution: "es5-shim@npm:4.6.7" + checksum: f285a58ed1901d46872828776164c0837272d28374a3c74b31a893dc6b16c67d417509c4f587a87c7cdfa9faf9c97c70d774c81d8bf9dcca6d3d6bfdf4a7a28e + languageName: node + linkType: hard + +"es6-error@npm:^4.1.1": + version: 4.1.1 + resolution: "es6-error@npm:4.1.1" + checksum: 357663fb1e845c047d548c3d30f86e005db71e122678f4184ced0693f634688c3f3ef2d7de7d4af732f734de01f528b05954e270f06aa7d133679fb9fe6600ef + languageName: node + linkType: hard + +"es6-iterator@npm:^2.0.3": + version: 2.0.3 + resolution: "es6-iterator@npm:2.0.3" + dependencies: + d: 1 + es5-ext: ^0.10.35 + es6-symbol: ^3.1.1 + checksum: 91f20b799dba28fb05bf623c31857fc1524a0f1c444903beccaf8929ad196c8c9ded233e5ac7214fc63a92b3f25b64b7f2737fcca8b1f92d2d96cf3ac902f5d8 + languageName: node + linkType: hard + +"es6-promise@npm:^4.0.3": + version: 4.2.8 + resolution: "es6-promise@npm:4.2.8" + checksum: 2373d9c5e9a93bdd9f9ed32ff5cb6dd3dd785368d1c21e9bbbfd07d16345b3774ae260f2bd24c8f836a6903f432b4151e7816a7fa8891ccb4e1a55a028ec42c3 + languageName: node + linkType: hard + +"es6-promisify@npm:^5.0.0": + version: 5.0.0 + resolution: "es6-promisify@npm:5.0.0" + dependencies: + es6-promise: ^4.0.3 + checksum: 23284c6a733cbf7842ec98f41eac742c9f288a78753c4fe46652bae826446ced7615b9e8a5c5f121a08812b1cd478ea58630f3e1c3d70835bd5dcd69c7cd75c9 + languageName: node + linkType: hard + +"es6-shim@npm:^0.35.5": + version: 0.35.7 + resolution: "es6-shim@npm:0.35.7" + checksum: b6b4e593bca11763c12061acb1f6727fb0045155d796d4d3b7319f32def2b96a75dd3354992ae4c2fc736fc7ddd2a5491d23437d92efd247c770114875dd61db + languageName: node + linkType: hard + +"es6-symbol@npm:^3.1.0, es6-symbol@npm:^3.1.1, es6-symbol@npm:^3.1.3": + version: 3.1.3 + resolution: "es6-symbol@npm:3.1.3" + dependencies: + d: ^1.0.1 + ext: ^1.1.2 + checksum: 22982f815f00df553a89f4fb74c5048fed85df598482b4bd38dbd173174247949c72982a7d7132a58b147525398400e5f182db59b0916cb49f1e245fb0e22233 + languageName: node + linkType: hard + +"escalade@npm:^3.1.1": + version: 3.1.1 + resolution: "escalade@npm:3.1.1" + checksum: afd02e6ca91ffa813e1108b5e7756566173d6bc0d1eb951cb44d6b21702ec17c1cf116cfe75d4a2b02e05acb0b808a7a9387d0d1ca5cf9c04ad03a8445c3e46d + languageName: node + linkType: hard + +"escape-html@npm:~1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^1.0.2, escape-string-regexp@npm:^1.0.5": + version: 1.0.5 + resolution: "escape-string-regexp@npm:1.0.5" + checksum: a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^2.0.0": + version: 2.0.0 + resolution: "escape-string-regexp@npm:2.0.0" + checksum: 2530479fe8db57eace5e8646c9c2a9c80fa279614986d16dcc6bcaceb63ae77f05a851ba6c43756d816c61d7f4534baf56e3c705e3e0d884818a46808811c507 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + +"escodegen@npm:^2.0.0": + version: 2.0.0 + resolution: "escodegen@npm:2.0.0" + dependencies: + esprima: ^4.0.1 + estraverse: ^5.2.0 + esutils: ^2.0.2 + optionator: ^0.8.1 + source-map: ~0.6.1 + dependenciesMeta: + source-map: + optional: true + bin: + escodegen: bin/escodegen.js + esgenerate: bin/esgenerate.js + checksum: 7d9834841db85d7cce2026c18da56c803564ca18bd6c1e81934cc08329ac4d366fac8b571e8708a81e21143f3dada56a4e34c9a9904c8b066f13abe8d9869871 + languageName: node + linkType: hard + +"eslint-plugin-react-hooks@npm:^4.5.0": + version: 4.6.0 + resolution: "eslint-plugin-react-hooks@npm:4.6.0" + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + checksum: 58c7e10ea5792c33346fcf5cb4024e14837035ce412ff99c2dcb7c4f903dc9b17939078f80bfef826301ce326582c396c00e8e0ac9d10ac2cde2b42d33763c65 + languageName: node + linkType: hard + +"eslint-plugin-react@npm:^7.29.4": + version: 7.32.1 + resolution: "eslint-plugin-react@npm:7.32.1" + dependencies: + array-includes: ^3.1.6 + array.prototype.flatmap: ^1.3.1 + array.prototype.tosorted: ^1.1.1 + doctrine: ^2.1.0 + estraverse: ^5.3.0 + jsx-ast-utils: ^2.4.1 || ^3.0.0 + minimatch: ^3.1.2 + object.entries: ^1.1.6 + object.fromentries: ^2.0.6 + object.hasown: ^1.1.2 + object.values: ^1.1.6 + prop-types: ^15.8.1 + resolve: ^2.0.0-next.4 + semver: ^6.3.0 + string.prototype.matchall: ^4.0.8 + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + checksum: bbe4b229108e920230da52745fba0e7d99450125a86f4c031dc720c20891f52a1b219eeade2392ec5fe055c5b7768cdef81fcab102013d21a298d233fdba5edd + languageName: node + linkType: hard + +"eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1": + version: 5.1.1 + resolution: "eslint-scope@npm:5.1.1" + dependencies: + esrecurse: ^4.3.0 + estraverse: ^4.1.1 + checksum: d30ef9dc1c1cbdece34db1539a4933fe3f9b14e1ffb27ecc85987902ee663ad7c9473bbd49a9a03195a373741e62e2f807c4938992e019b511993d163450e70a + languageName: node + linkType: hard + +"eslint-scope@npm:^4.0.3": + version: 4.0.3 + resolution: "eslint-scope@npm:4.0.3" + dependencies: + esrecurse: ^4.1.0 + estraverse: ^4.1.1 + checksum: a2a3fe5845938ce7cfd2e658c309a9bb27a7f9ce94f0cc447ed5f9fa95b16451556d7e1db4c8e5d2aaa02d02850f5346d23091bbe94f7097412ce846504b4dcc + languageName: node + linkType: hard + +"eslint-scope@npm:^7.1.1": + version: 7.1.1 + resolution: "eslint-scope@npm:7.1.1" + dependencies: + esrecurse: ^4.3.0 + estraverse: ^5.2.0 + checksum: 3ae3280cbea34af3b816e941b83888aca063aaa0169966ff7e4c1bfb0715dbbeac3811596e56315e8ceea84007a7403754459ae4f1d19f25487eb02acd951aa7 + languageName: node + linkType: hard + +"eslint-utils@npm:^3.0.0": + version: 3.0.0 + resolution: "eslint-utils@npm:3.0.0" + dependencies: + eslint-visitor-keys: ^2.0.0 + peerDependencies: + eslint: ">=5" + checksum: 45aa2b63667a8d9b474c98c28af908d0a592bed1a4568f3145cd49fb5d9510f545327ec95561625290313fe126e6d7bdfe3fdbdb6f432689fab6b9497d3bfb52 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^2.0.0": + version: 2.1.0 + resolution: "eslint-visitor-keys@npm:2.1.0" + checksum: 9f0e3a2db751d84067d15977ac4b4472efd6b303e369e6ff241a99feac04da758f46d5add022c33d06b53596038dbae4b4aceb27c7e68b8dfc1055b35e495787 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^3.3.0": + version: 3.3.0 + resolution: "eslint-visitor-keys@npm:3.3.0" + checksum: fc6a9b5bdee8d90e35e7564fd9db10fdf507a2c089a4f0d4d3dd091f7f4ac6790547c8b1b7a760642ef819f875ef86dd5bcb8cdf01b0775f57a699f4e6a20a18 + languageName: node + linkType: hard + +"eslint@npm:^8.15.0": + version: 8.32.0 + resolution: "eslint@npm:8.32.0" + dependencies: + "@eslint/eslintrc": ^1.4.1 + "@humanwhocodes/config-array": ^0.11.8 + "@humanwhocodes/module-importer": ^1.0.1 + "@nodelib/fs.walk": ^1.2.8 + ajv: ^6.10.0 + 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.1.1 + eslint-utils: ^3.0.0 + eslint-visitor-keys: ^3.3.0 + espree: ^9.4.0 + esquery: ^1.4.0 + 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 + grapheme-splitter: ^1.0.4 + ignore: ^5.2.0 + import-fresh: ^3.0.0 + imurmurhash: ^0.1.4 + is-glob: ^4.0.0 + is-path-inside: ^3.0.3 + js-sdsl: ^4.1.4 + 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.1 + regexpp: ^3.2.0 + strip-ansi: ^6.0.1 + strip-json-comments: ^3.1.0 + text-table: ^0.2.0 + bin: + eslint: bin/eslint.js + checksum: 51f8371c9f807106047b34b38f7b82669bd41f00d383180076b69188f764d32c82f30194e5cb716dde5733b794a8968db9e2d3b760cbcefe0dde312244bfb21d + languageName: node + linkType: hard + +"espree@npm:^9.4.0": + version: 9.4.1 + resolution: "espree@npm:9.4.1" + dependencies: + acorn: ^8.8.0 + acorn-jsx: ^5.3.2 + eslint-visitor-keys: ^3.3.0 + checksum: f7c8f891f3b247c76ed16259522c772bb35e6a9cb5f5b2e0f111ffc60624e7533c89a0aa1f830d8f8baa2b7676313bb9ce7f64ae00ccffc223ebbf880ab691ee + languageName: node + linkType: hard + +"esprima@npm:^4.0.0, esprima@npm:^4.0.1": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 + languageName: node + linkType: hard + +"esquery@npm:^1.4.0": + version: 1.4.0 + resolution: "esquery@npm:1.4.0" + dependencies: + estraverse: ^5.1.0 + checksum: b9b18178d33c4335210c76e062de979dc38ee6b49deea12bff1b2315e6cfcca1fd7f8bc49f899720ad8ff25967ac95b5b182e81a8b7b59ff09dbd0d978c32f64 + languageName: node + linkType: hard + +"esrecurse@npm:^4.1.0, esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" + dependencies: + estraverse: ^5.2.0 + checksum: 81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 + languageName: node + linkType: hard + +"estraverse@npm:^4.1.1": + version: 4.3.0 + resolution: "estraverse@npm:4.3.0" + checksum: 9cb46463ef8a8a4905d3708a652d60122a0c20bb58dec7e0e12ab0e7235123d74214fc0141d743c381813e1b992767e2708194f6f6e0f9fd00c1b4e0887b8b6d + languageName: node + linkType: hard + +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0, estraverse@npm:^5.3.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 + languageName: node + linkType: hard + +"estree-to-babel@npm:^3.1.0": + version: 3.2.1 + resolution: "estree-to-babel@npm:3.2.1" + dependencies: + "@babel/traverse": ^7.1.6 + "@babel/types": ^7.2.0 + c8: ^7.6.0 + checksum: c7949b141f569528b2608ab715d593a04f7e2e529df04e0b595d0a7dea819b410e71d1f04716e43ac1480942afc5701cb5151ad2906ee8402969651a389881bb + languageName: node + linkType: hard + +"estree-walker@npm:^1.0.1": + version: 1.0.1 + resolution: "estree-walker@npm:1.0.1" + checksum: fa9e5f8c1bbe8d01e314c0f03067b64a4f22d4c58410fc5237060d0c15b81e58c23921c41acc60abbdab490f1fdfcbd6408ede2d03ca704454272e0244d61a55 + languageName: node + linkType: hard + +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 + languageName: node + linkType: hard + +"etag@npm:~1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84 + languageName: node + linkType: hard + +"eventemitter2@npm:6.4.7": + version: 6.4.7 + resolution: "eventemitter2@npm:6.4.7" + checksum: 35d8e9d51b919114eb072d33786274e1475db50efe00960c24c088ce4f76c07a826ccc927602724928efb3d8f09a7d8dd1fa79e410875118c0e9846959287f34 + languageName: node + linkType: hard + +"eventemitter3@npm:^3.1.2": + version: 3.1.2 + resolution: "eventemitter3@npm:3.1.2" + checksum: c67262eccbf85848b7cc6d4abb6c6e34155e15686db2a01c57669fd0d44441a574a19d44d25948b442929e065774cbe5003d8e77eed47674fbf876ac77887793 + languageName: node + linkType: hard + +"eventemitter3@npm:^4.0.0, eventemitter3@npm:^4.0.1": + version: 4.0.7 + resolution: "eventemitter3@npm:4.0.7" + checksum: 5f6d97cbcbac47be798e6355e3a7639a84ee1f7d9b199a07017f1d2f1e2fe236004d14fa5dfaeba661f94ea57805385e326236a6debbc7145c8877fbc0297c6b + languageName: node + linkType: hard + +"events@npm:1.1.1": + version: 1.1.1 + resolution: "events@npm:1.1.1" + checksum: 29ba5a4c7d03dd2f4a2d3d9d4dfd8332225256f666cd69f490975d2eff8d7c73f1fb4872877b2c1f3b485e8fb42462153d65e5a21ea994eb928c3bec9e0c826e + languageName: node + linkType: hard + +"events@npm:^3.0.0, events@npm:^3.2.0": + version: 3.3.0 + resolution: "events@npm:3.3.0" + checksum: d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6 + languageName: node + linkType: hard + +"evp_bytestokey@npm:^1.0.0, evp_bytestokey@npm:^1.0.3": + version: 1.0.3 + resolution: "evp_bytestokey@npm:1.0.3" + dependencies: + md5.js: ^1.3.4 + node-gyp: latest + safe-buffer: ^5.1.1 + checksum: 77fbe2d94a902a80e9b8f5a73dcd695d9c14899c5e82967a61b1fc6cbbb28c46552d9b127cff47c45fcf684748bdbcfa0a50410349109de87ceb4b199ef6ee99 + languageName: node + linkType: hard + +"exec-sh@npm:^0.3.2": + version: 0.3.6 + resolution: "exec-sh@npm:0.3.6" + checksum: de29ed40c263989ea151cfc8561c9a41a443185d1998b0ff7aee248323af3b46db3a1dc5341816297d0c02dca472b188640490aa4ba3cae017f531f98102607d + languageName: node + linkType: hard + +"execa@npm:4.1.0": + version: 4.1.0 + resolution: "execa@npm:4.1.0" + dependencies: + cross-spawn: ^7.0.0 + get-stream: ^5.0.0 + human-signals: ^1.1.1 + is-stream: ^2.0.0 + merge-stream: ^2.0.0 + npm-run-path: ^4.0.0 + onetime: ^5.1.0 + signal-exit: ^3.0.2 + strip-final-newline: ^2.0.0 + checksum: 02211601bb1c52710260edcc68fb84c3c030dc68bafc697c90ada3c52cc31375337de8c24826015b8382a58d63569ffd203b79c94fef217d65503e3e8d2c52ba + languageName: node + linkType: hard + +"execa@npm:^0.7.0": + version: 0.7.0 + resolution: "execa@npm:0.7.0" + dependencies: + cross-spawn: ^5.0.1 + get-stream: ^3.0.0 + is-stream: ^1.1.0 + npm-run-path: ^2.0.0 + p-finally: ^1.0.0 + signal-exit: ^3.0.0 + strip-eof: ^1.0.0 + checksum: 812f1776e2a6b2226532e43c1af87d8a12e26de03a06e7e043f653acf5565e0656f5f6c64d66726fefa17178ac129caaa419a50905934e7c4a846417abb25d4a + languageName: node + linkType: hard + +"execa@npm:^1.0.0": + version: 1.0.0 + resolution: "execa@npm:1.0.0" + dependencies: + cross-spawn: ^6.0.0 + get-stream: ^4.0.0 + is-stream: ^1.1.0 + npm-run-path: ^2.0.0 + p-finally: ^1.0.0 + signal-exit: ^3.0.0 + strip-eof: ^1.0.0 + checksum: cc71707c9aa4a2552346893ee63198bf70a04b5a1bc4f8a0ef40f1d03c319eae80932c59191f037990d7d102193e83a38ec72115fff814ec2fb3099f3661a590 + languageName: node + linkType: hard + +"execa@npm:^5.0.0, execa@npm:^5.1.1": + version: 5.1.1 + resolution: "execa@npm:5.1.1" + 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 + checksum: c8e615235e8de4c5addf2fa4c3da3e3aa59ce975a3e83533b4f6a71750fb816a2e79610dc5f1799b6e28976c9ae86747a36a606655bf8cb414a74d8d507b304f + languageName: node + linkType: hard + +"executable@npm:^4.1.1": + version: 4.1.1 + resolution: "executable@npm:4.1.1" + dependencies: + pify: ^2.2.0 + checksum: c3cc5d2d2e3cdb1b7d7b0639ebd5566d113d7ada21cfa07f5226d55ba2a210320116720e07570ed5659ef2ec516bc00c8f0488dac75d112fd324ef25c2100173 + languageName: node + linkType: hard + +"exenv@npm:^1.2.2": + version: 1.2.2 + resolution: "exenv@npm:1.2.2" + checksum: 4e96b355a6b9b9547237288ca779dd673b2e698458b409e88b50df09feb7c85ef94c07354b6b87bc3ed0193a94009a6f7a3c71956da12f45911c0d0f5aa3caa0 + languageName: node + linkType: hard + +"exit@npm:0.1.2, exit@npm:0.1.x, exit@npm:^0.1.2": + version: 0.1.2 + resolution: "exit@npm:0.1.2" + checksum: 71d2ad9b36bc25bb8b104b17e830b40a08989be7f7d100b13269aaae7c3784c3e6e1e88a797e9e87523993a25ba27c8958959a554535370672cfb4d824af8989 + languageName: node + linkType: hard + +"expand-brackets@npm:^2.1.4": + version: 2.1.4 + resolution: "expand-brackets@npm:2.1.4" + dependencies: + debug: ^2.3.3 + define-property: ^0.2.5 + extend-shallow: ^2.0.1 + posix-character-classes: ^0.1.0 + regex-not: ^1.0.0 + snapdragon: ^0.8.1 + to-regex: ^3.0.1 + checksum: 3e2fb95d2d7d7231486493fd65db913927b656b6fcdfcce41e139c0991a72204af619ad4acb1be75ed994ca49edb7995ef241dbf8cf44dc3c03d211328428a87 + languageName: node + linkType: hard + +"expect@npm:^29.3.1": + version: 29.3.1 + resolution: "expect@npm:29.3.1" + dependencies: + "@jest/expect-utils": ^29.3.1 + jest-get-type: ^29.2.0 + jest-matcher-utils: ^29.3.1 + jest-message-util: ^29.3.1 + jest-util: ^29.3.1 + checksum: 0662836949648b65ea80c0fc4777818bd384c00d2ad3d442ec1bea69a604097f94673a432245ae750b09bc8d292f381a31dcc59cf0433a9b2adfba501e257d63 + languageName: node + linkType: hard + +"express@npm:^4.17.1, express@npm:^4.17.3": + version: 4.18.2 + resolution: "express@npm:4.18.2" + 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 + checksum: 75af556306b9241bc1d7bdd40c9744b516c38ce50ae3210658efcbf96e3aed4ab83b3432f06215eae5610c123bc4136957dc06e50dfc50b7d4d775af56c4c59c + languageName: node + linkType: hard + +"ext@npm:^1.1.2": + version: 1.7.0 + resolution: "ext@npm:1.7.0" + dependencies: + type: ^2.7.2 + checksum: a8e5f34e12214e9eee3a4af3b5c9d05ba048f28996450975b369fc86e5d0ef13b6df0615f892f5396a9c65d616213c25ec5b0ad17ef42eac4a500512a19da6c7 + languageName: node + linkType: hard + +"extend-shallow@npm:^2.0.1": + version: 2.0.1 + resolution: "extend-shallow@npm:2.0.1" + dependencies: + is-extendable: ^0.1.0 + checksum: ee1cb0a18c9faddb42d791b2d64867bd6cfd0f3affb711782eb6e894dd193e2934a7f529426aac7c8ddb31ac5d38000a00aa2caf08aa3dfc3e1c8ff6ba340bd9 + languageName: node + linkType: hard + +"extend-shallow@npm:^3.0.0, extend-shallow@npm:^3.0.2": + version: 3.0.2 + resolution: "extend-shallow@npm:3.0.2" + dependencies: + assign-symbols: ^1.0.0 + is-extendable: ^1.0.1 + checksum: f39581b8f98e3ad94995e33214fff725b0297cf09f2725b6f624551cfb71e0764accfd0af80becc0182af5014d2a57b31b85ec999f9eb8a6c45af81752feac9a + languageName: node + linkType: hard + +"extend@npm:^3.0.0, extend@npm:~3.0.2": + version: 3.0.2 + resolution: "extend@npm:3.0.2" + checksum: 73bf6e27406e80aa3e85b0d1c4fd987261e628064e170ca781125c0b635a3dabad5e05adbf07595ea0cf1e6c5396cacb214af933da7cbaf24fe75ff14818e8f9 + languageName: node + linkType: hard + +"extglob@npm:^2.0.4": + version: 2.0.4 + resolution: "extglob@npm:2.0.4" + dependencies: + array-unique: ^0.3.2 + define-property: ^1.0.0 + expand-brackets: ^2.1.4 + extend-shallow: ^2.0.1 + fragment-cache: ^0.2.1 + regex-not: ^1.0.0 + snapdragon: ^0.8.1 + to-regex: ^3.0.1 + checksum: e1a891342e2010d046143016c6c03d58455c2c96c30bf5570ea07929984ee7d48fad86b363aee08f7a8a638f5c3a66906429b21ecb19bc8e90df56a001cd282c + languageName: node + linkType: hard + +"extract-zip@npm:2.0.1": + version: 2.0.1 + resolution: "extract-zip@npm:2.0.1" + dependencies: + "@types/yauzl": ^2.9.1 + debug: ^4.1.1 + get-stream: ^5.1.0 + yauzl: ^2.10.0 + dependenciesMeta: + "@types/yauzl": + optional: true + bin: + extract-zip: cli.js + checksum: 9afbd46854aa15a857ae0341a63a92743a7b89c8779102c3b4ffc207516b2019337353962309f85c66ee3d9092202a83cdc26dbf449a11981272038443974aee + languageName: node + linkType: hard + +"extsprintf@npm:1.3.0": + version: 1.3.0 + resolution: "extsprintf@npm:1.3.0" + checksum: f75114a8388f0cbce68e277b6495dc3930db4dde1611072e4a140c24e204affd77320d004b947a132e9a3b97b8253017b2b62dce661975fb0adced707abf1ab5 + languageName: node + linkType: hard + +"extsprintf@npm:^1.2.0": + version: 1.4.1 + resolution: "extsprintf@npm:1.4.1" + checksum: e10e2769985d0e9b6c7199b053a9957589d02e84de42832c295798cb422a025e6d4a92e0259c1fb4d07090f5bfde6b55fd9f880ac5855bd61d775f8ab75a7ab0 + languageName: node + linkType: hard + +"faker@npm:^5.5.3": + version: 5.5.3 + resolution: "faker@npm:5.5.3" + checksum: 55ee2fb6425df717253f237b4e952c94efe33da23a5826ca41c6ecb31ccfb49d06c5de64b82b6994ca9c76c05eab4dbf779cea047455fff6e62cc9d585c7d460 + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + +"fast-equals@npm:^2.0.0": + version: 2.0.4 + resolution: "fast-equals@npm:2.0.4" + checksum: 2867aa148c995e4ea921242ab605b157e5cbdc44793ebddf83684a5e6673be5016eb790ec4d8329317b92887e1108fb67ed3d4334529f2a7650c1338e6aa2c5f + languageName: node + linkType: hard + +"fast-glob@npm:3, fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.12, fast-glob@npm:^3.2.9": + version: 3.2.12 + resolution: "fast-glob@npm:3.2.12" + 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 + checksum: 08604fb8ef6442ce74068bef3c3104382bb1f5ab28cf75e4ee904662778b60ad620e1405e692b7edea598ef445f5d387827a965ba034e1892bf54b1dfde97f26 + languageName: node + linkType: hard + +"fast-glob@npm:^2.2.6": + version: 2.2.7 + resolution: "fast-glob@npm:2.2.7" + dependencies: + "@mrmlnc/readdir-enhanced": ^2.2.1 + "@nodelib/fs.stat": ^1.1.2 + glob-parent: ^3.1.0 + is-glob: ^4.0.0 + merge2: ^1.2.3 + micromatch: ^3.1.10 + checksum: 85bc858e298423d5a1b6eed6eee8556005a19d245c4ae9aceac04d56699ea9885ca0a2afc4f76b562416e94fe2048df6b2f306f3d4b7e51ed37b7a52fc1e4fc7 + languageName: node + linkType: hard + +"fast-json-parse@npm:^1.0.3": + version: 1.0.3 + resolution: "fast-json-parse@npm:1.0.3" + checksum: 2c58c7a0f7f1725c9da1272839f9bee3ccc13b77672b18ab4ac470c707999bca39828cd7e79b87c73017f21c3ddff37992d03fa2fd2da124d9bd06c1d02c9b7e + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b + languageName: node + linkType: hard + +"fast-levenshtein@npm:^2.0.6, fast-levenshtein@npm:~2.0.6": + version: 2.0.6 + resolution: "fast-levenshtein@npm:2.0.6" + checksum: 111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 + languageName: node + linkType: hard + +"fast-xml-parser@npm:^3.17.5": + version: 3.21.1 + resolution: "fast-xml-parser@npm:3.21.1" + dependencies: + strnum: ^1.0.4 + bin: + xml2js: cli.js + checksum: 26ed57fd3cb86434959cffa6852ab63231850ff5be6836aef7744b1abc8013fa5696c05179e422965677e116606611242d91ceebf246d91a5247e262d8b59996 + languageName: node + linkType: hard + +"fastest-levenshtein@npm:^1.0.12": + version: 1.0.16 + resolution: "fastest-levenshtein@npm:1.0.16" + checksum: 7e3d8ae812a7f4fdf8cad18e9cde436a39addf266a5986f653ea0d81e0de0900f50c0f27c6d5aff3f686bcb48acbd45be115ae2216f36a6a13a7dbbf5cad878b + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.15.0 + resolution: "fastq@npm:1.15.0" + dependencies: + reusify: ^1.0.4 + checksum: 5ce4f83afa5f88c9379e67906b4d31bc7694a30826d6cc8d0f0473c966929017fda65c2174b0ec89f064ede6ace6c67f8a4fe04cef42119b6a55b0d465554c24 + languageName: node + linkType: hard + +"faye-websocket@npm:^0.11.3": + version: 0.11.4 + resolution: "faye-websocket@npm:0.11.4" + dependencies: + websocket-driver: ">=0.5.1" + checksum: c6052a0bb322778ce9f89af92890f6f4ce00d5ec92418a35e5f4c6864a4fe736fec0bcebd47eac7c0f0e979b01530746b1c85c83cb04bae789271abf19737420 + languageName: node + linkType: hard + +"fb-watchman@npm:^2.0.0": + version: 2.0.2 + resolution: "fb-watchman@npm:2.0.2" + dependencies: + bser: 2.1.1 + checksum: feae89ac148adb8f6ae8ccd87632e62b13563e6fb114cacb5265c51f585b17e2e268084519fb2edd133872f1d47a18e6bfd7e5e08625c0d41b93149694187581 + languageName: node + linkType: hard + +"fbemitter@npm:^3.0.0": + version: 3.0.0 + resolution: "fbemitter@npm:3.0.0" + dependencies: + fbjs: ^3.0.0 + checksum: f130dd8e15dc3fc6709a26586b7a589cd994e1d1024b624f2cc8ef1b12401536a94bb30038e68150a24f9ba18863e9a3fe87941ade2c87667bfbd17f4848d5c7 + languageName: node + linkType: hard + +"fbjs-css-vars@npm:^1.0.0": + version: 1.0.2 + resolution: "fbjs-css-vars@npm:1.0.2" + checksum: dfb64116b125a64abecca9e31477b5edb9a2332c5ffe74326fe36e0a72eef7fc8a49b86adf36c2c293078d79f4524f35e80f5e62546395f53fb7c9e69821f54f + languageName: node + linkType: hard + +"fbjs@npm:^3.0.0, fbjs@npm:^3.0.1": + version: 3.0.4 + resolution: "fbjs@npm:3.0.4" + dependencies: + cross-fetch: ^3.1.5 + fbjs-css-vars: ^1.0.0 + loose-envify: ^1.0.0 + object-assign: ^4.1.0 + promise: ^7.1.1 + setimmediate: ^1.0.5 + ua-parser-js: ^0.7.30 + checksum: 6c605d038d6852f0199a333e0b7f1f3e2602eebd0b815fba505f641912610007a0a8419222909e17ad0e07365d3b8a0bf45cacf9b43366dde0e95e5ced251632 + languageName: node + linkType: hard + +"fd-slicer@npm:~1.1.0": + version: 1.1.0 + resolution: "fd-slicer@npm:1.1.0" + dependencies: + pend: ~1.2.0 + checksum: 304dd70270298e3ffe3bcc05e6f7ade2511acc278bc52d025f8918b48b6aa3b77f10361bddfadfe2a28163f7af7adbdce96f4d22c31b2f648ba2901f0c5fc20e + languageName: node + linkType: hard + +"fecha@npm:^4.2.0": + version: 4.2.3 + resolution: "fecha@npm:4.2.3" + checksum: 0e895965959cf6a22bb7b00f0bf546f2783836310f510ddf63f463e1518d4c96dec61ab33fdfd8e79a71b4856a7c865478ce2ee8498d560fe125947703c9b1cf + languageName: node + linkType: hard + +"fetch-retry@npm:^5.0.2": + version: 5.0.3 + resolution: "fetch-retry@npm:5.0.3" + checksum: ea577f9537c0f289b650bfda1b1d7dff1d0fea230323fc702b983990c73ed01e4e8dd690dece3053a721d1fc51d689469c814e5821fcd78bda9368d34712f379 + languageName: node + linkType: hard + +"fflate@npm:^0.4.8": + version: 0.4.8 + resolution: "fflate@npm:0.4.8" + checksum: 29d1eddaaa5deab61b1c6b0d21282adacadbc4d2c01e94d8b1ee784398151673b9c563e53f97a801bc410a1ae55e8de5378114a743430e643e7a0644ba8e5a42 + languageName: node + linkType: hard + +"figgy-pudding@npm:^3.4.1, figgy-pudding@npm:^3.5.1, figgy-pudding@npm:^3.5.2": + version: 3.5.2 + resolution: "figgy-pudding@npm:3.5.2" + checksum: b21c7adaeb8485ef3c50e056b5dc8c3a6461818343aba141e0d7927aad47a0cb9f1d207ffdf494c380cd60d7c848c46a5ce5cb06987d10e9226fcec419c8af90 + languageName: node + linkType: hard + +"figures@npm:^3.2.0": + version: 3.2.0 + resolution: "figures@npm:3.2.0" + dependencies: + escape-string-regexp: ^1.0.5 + checksum: 9c421646ede432829a50bc4e55c7a4eb4bcb7cc07b5bab2f471ef1ab9a344595bbebb6c5c21470093fbb730cd81bbca119624c40473a125293f656f49cb47629 + languageName: node + linkType: hard + +"file-entry-cache@npm:^6.0.1": + version: 6.0.1 + resolution: "file-entry-cache@npm:6.0.1" + dependencies: + flat-cache: ^3.0.4 + checksum: 58473e8a82794d01b38e5e435f6feaf648e3f36fdb3a56e98f417f4efae71ad1c0d4ebd8a9a7c50c3ad085820a93fc7494ad721e0e4ebc1da3573f4e1c3c7cdd + languageName: node + linkType: hard + +"file-loader@npm:^6.2.0": + version: 6.2.0 + resolution: "file-loader@npm:6.2.0" + dependencies: + loader-utils: ^2.0.0 + schema-utils: ^3.0.0 + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + checksum: e176a57c2037ab0f78e5755dbf293a6b7f0f8392350a120bd03cc2ce2525bea017458ba28fea14ca535ff1848055e86d1a3a216bdb2561ef33395b27260a1dd3 + languageName: node + linkType: hard + +"file-system-cache@npm:^1.0.5": + version: 1.1.0 + resolution: "file-system-cache@npm:1.1.0" + dependencies: + fs-extra: ^10.1.0 + ramda: ^0.28.0 + checksum: a04322ab12e77b5b5d3bed1259f9aa431d26b9e161445f25eb8e539845cc0051a4d501840a726cad83db727b40b88dd113a7e97e8b136690ce108f363eb116e9 + languageName: node + linkType: hard + +"file-uri-to-path@npm:1.0.0": + version: 1.0.0 + resolution: "file-uri-to-path@npm:1.0.0" + checksum: 3b545e3a341d322d368e880e1c204ef55f1d45cdea65f7efc6c6ce9e0c4d22d802d5629320eb779d006fe59624ac17b0e848d83cc5af7cd101f206cb704f5519 + languageName: node + linkType: hard + +"filelist@npm:^1.0.1": + version: 1.0.4 + resolution: "filelist@npm:1.0.4" + dependencies: + minimatch: ^5.0.1 + checksum: 426b1de3944a3d153b053f1c0ebfd02dccd0308a4f9e832ad220707a6d1f1b3c9784d6cadf6b2f68f09a57565f63ebc7bcdc913ccf8012d834f472c46e596f41 + languageName: node + linkType: hard + +"fill-range@npm:^4.0.0": + version: 4.0.0 + resolution: "fill-range@npm:4.0.0" + dependencies: + extend-shallow: ^2.0.1 + is-number: ^3.0.0 + repeat-string: ^1.6.1 + to-regex-range: ^2.1.0 + checksum: ccd57b7c43d7e28a1f8a60adfa3c401629c08e2f121565eece95e2386ebc64dedc7128d8c3448342aabf19db0c55a34f425f148400c7a7be9a606ba48749e089 + languageName: node + linkType: hard + +"fill-range@npm:^7.0.1": + version: 7.0.1 + resolution: "fill-range@npm:7.0.1" + dependencies: + to-regex-range: ^5.0.1 + checksum: 7cdad7d426ffbaadf45aeb5d15ec675bbd77f7597ad5399e3d2766987ed20bda24d5fac64b3ee79d93276f5865608bb22344a26b9b1ae6c4d00bd94bf611623f + languageName: node + linkType: hard + +"filter-obj@npm:^1.1.0": + version: 1.1.0 + resolution: "filter-obj@npm:1.1.0" + checksum: 071e0886b2b50238ca5026c5bbf58c26a7c1a1f720773b8c7813d16ba93d0200de977af14ac143c5ac18f666b2cfc83073f3a5fe6a4e996c49e0863d5500fccf + languageName: node + linkType: hard + +"finalhandler@npm:1.2.0": + version: 1.2.0 + resolution: "finalhandler@npm:1.2.0" + 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 + checksum: 64b7e5ff2ad1fcb14931cd012651631b721ce657da24aedb5650ddde9378bf8e95daa451da43398123f5de161a81e79ff5affe4f9f2a6d2df4a813d6d3e254b7 + languageName: node + linkType: hard + +"find-cache-dir@npm:^2.0.0, find-cache-dir@npm:^2.1.0": + version: 2.1.0 + resolution: "find-cache-dir@npm:2.1.0" + dependencies: + commondir: ^1.0.1 + make-dir: ^2.0.0 + pkg-dir: ^3.0.0 + checksum: 556117fd0af14eb88fb69250f4bba9e905e7c355c6136dff0e161b9cbd1f5285f761b778565a278da73a130f42eccc723d7ad4c002ae547ed1d698d39779dabb + languageName: node + linkType: hard + +"find-cache-dir@npm:^3.3.1": + version: 3.3.2 + resolution: "find-cache-dir@npm:3.3.2" + dependencies: + commondir: ^1.0.1 + make-dir: ^3.0.2 + pkg-dir: ^4.1.0 + checksum: 92747cda42bff47a0266b06014610981cfbb71f55d60f2c8216bc3108c83d9745507fb0b14ecf6ab71112bed29cd6fb1a137ee7436179ea36e11287e3159e587 + languageName: node + linkType: hard + +"find-npm-prefix@npm:^1.0.2": + version: 1.0.2 + resolution: "find-npm-prefix@npm:1.0.2" + checksum: 9e802e40e2b97908264807ebb56a7b38a8d491d69662ac64835c08daaee4d5d50947231d3fb85f57605717bdd274ce5d7ba1fef1c7befd0bd6b8d58faacace25 + languageName: node + linkType: hard + +"find-root@npm:^1.1.0": + version: 1.1.0 + resolution: "find-root@npm:1.1.0" + checksum: 1abc7f3bf2f8d78ff26d9e00ce9d0f7b32e5ff6d1da2857bcdf4746134c422282b091c672cde0572cac3840713487e0a7a636af9aa1b74cb11894b447a521efa + languageName: node + linkType: hard + +"find-up@npm:^1.0.0": + version: 1.1.2 + resolution: "find-up@npm:1.1.2" + dependencies: + path-exists: ^2.0.0 + pinkie-promise: ^2.0.0 + checksum: 51e35c62d9b7efe82d7d5cce966bfe10c2eaa78c769333f8114627e3a8a4a4f50747f5f50bff50b1094cbc6527776f0d3b9ff74d3561ef714a5290a17c80c2bc + languageName: node + linkType: hard + +"find-up@npm:^3.0.0": + version: 3.0.0 + resolution: "find-up@npm:3.0.0" + dependencies: + locate-path: ^3.0.0 + checksum: 2c2e7d0a26db858e2f624f39038c74739e38306dee42b45f404f770db357947be9d0d587f1cac72d20c114deb38aa57316e879eb0a78b17b46da7dab0a3bd6e3 + languageName: node + linkType: hard + +"find-up@npm:^4.0.0, find-up@npm:^4.1.0": + version: 4.1.0 + resolution: "find-up@npm:4.1.0" + dependencies: + locate-path: ^5.0.0 + path-exists: ^4.0.0 + checksum: 0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 + languageName: node + linkType: hard + +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: ^6.0.0 + path-exists: ^4.0.0 + checksum: 062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a + languageName: node + linkType: hard + +"flat-cache@npm:^3.0.4": + version: 3.0.4 + resolution: "flat-cache@npm:3.0.4" + dependencies: + flatted: ^3.1.0 + rimraf: ^3.0.2 + checksum: f274dcbadb09ad8d7b6edf2ee9b034bc40bf0c12638f6c4084e9f1d39208cb104a5ebbb24b398880ef048200eaa116852f73d2d8b72e8c9627aba8c3e27ca057 + languageName: node + linkType: hard + +"flatted@npm:^3.1.0": + version: 3.2.7 + resolution: "flatted@npm:3.2.7" + checksum: 207a87c7abfc1ea6928ea16bac84f9eaa6d44d365620ece419e5c41cf44a5e9902b4c1f59c9605771b10e4565a0cb46e99d78e0464e8aabb42c97de880642257 + languageName: node + linkType: hard + +"flow-bin@npm:^0.115.0": + version: 0.115.0 + resolution: "flow-bin@npm:0.115.0" + bin: + flow: cli.js + checksum: 36db020f972c9038f6cc36f2351e219c69afe6918cc8656fba30912a669b9d9254ddc1a4ab1abd4a10b14d0b367fb3097c72b4e95504c83fec03e211ebf5301d + languageName: node + linkType: hard + +"flush-write-stream@npm:^1.0.0": + version: 1.1.1 + resolution: "flush-write-stream@npm:1.1.1" + dependencies: + inherits: ^2.0.3 + readable-stream: ^2.3.6 + checksum: 2cd4f65b728d5f388197a03dafabc6a5e4f0c2ed1a2d912e288f7aa1c2996dd90875e55b50cf32c78dca55ad2e2dfae5d3db09b223838388033d87cf5920dd87 + languageName: node + linkType: hard + +"flux@npm:^4.0.1": + version: 4.0.3 + resolution: "flux@npm:4.0.3" + dependencies: + fbemitter: ^3.0.0 + fbjs: ^3.0.1 + peerDependencies: + react: ^15.0.2 || ^16.0.0 || ^17.0.0 + checksum: a0b8d9dd5c6fdafc0b4418c506d90e68aea47017bc75df7dc05e1f593c263c8b02a0db986c1f6c6b80643105505b8ee21de95a730ac106791d95cbc2e0b6ba66 + languageName: node + linkType: hard + +"fn.name@npm:1.x.x": + version: 1.1.0 + resolution: "fn.name@npm:1.1.0" + checksum: 8ad62aa2d4f0b2a76d09dba36cfec61c540c13a0fd72e5d94164e430f987a7ce6a743112bbeb14877c810ef500d1f73d7f56e76d029d2e3413f20d79e3460a9a + languageName: node + linkType: hard + +"focus-lock@npm:^0.8.0": + version: 0.8.1 + resolution: "focus-lock@npm:0.8.1" + dependencies: + tslib: ^1.9.3 + checksum: 8e1a1b23834003e3b27af79e16f75650fc382fc6458b35bc53cb0c5929b61b69c75caa3db5e9e0bbf260064fd14af3f1d96cbb7db5470eca90ad8fd5a873226a + languageName: node + linkType: hard + +"follow-redirects@npm:^1.0.0": + version: 1.15.2 + resolution: "follow-redirects@npm:1.15.2" + peerDependenciesMeta: + debug: + optional: true + checksum: da5932b70e63944d38eecaa16954bac4347036f08303c913d166eda74809d8797d38386e3a0eb1d2fe37d2aaff2764cce8e9dbd99459d860cf2cdfa237923b5f + languageName: node + linkType: hard + +"for-each@npm:^0.3.3": + version: 0.3.3 + resolution: "for-each@npm:0.3.3" + dependencies: + is-callable: ^1.1.3 + checksum: 22330d8a2db728dbf003ec9182c2d421fbcd2969b02b4f97ec288721cda63eb28f2c08585ddccd0f77cb2930af8d958005c9e72f47141dc51816127a118f39aa + languageName: node + linkType: hard + +"for-in@npm:^1.0.2": + version: 1.0.2 + resolution: "for-in@npm:1.0.2" + checksum: 42bb609d564b1dc340e1996868b67961257fd03a48d7fdafd4f5119530b87f962be6b4d5b7e3a3fc84c9854d149494b1d358e0b0ce9837e64c4c6603a49451d6 + languageName: node + linkType: hard + +"foreground-child@npm:^2.0.0": + version: 2.0.0 + resolution: "foreground-child@npm:2.0.0" + dependencies: + cross-spawn: ^7.0.0 + signal-exit: ^3.0.2 + checksum: 6719982783a448162f9a01500757fb2053bc5dcd4d67c7cd30739b38ccc01b39f84e408c30989d1d8774519c021c0498e2450ab127690fb09d7f2568fd94ffcc + languageName: node + linkType: hard + +"forever-agent@npm:~0.6.1": + version: 0.6.1 + resolution: "forever-agent@npm:0.6.1" + checksum: 364f7f5f7d93ab661455351ce116a67877b66f59aca199559a999bd39e3cfadbfbfacc10415a915255e2210b30c23febe9aec3ca16bf2d1ff11c935a1000e24c + languageName: node + linkType: hard + +"fork-ts-checker-webpack-plugin@npm:^4.1.6": + version: 4.1.6 + resolution: "fork-ts-checker-webpack-plugin@npm:4.1.6" + dependencies: + "@babel/code-frame": ^7.5.5 + chalk: ^2.4.1 + micromatch: ^3.1.10 + minimatch: ^3.0.4 + semver: ^5.6.0 + tapable: ^1.0.0 + worker-rpc: ^0.1.0 + checksum: 2dddbe0d3bf2b84f4a5daada41091003decf881cffdef3bab72a699d0bfe3003e2d312405b304894153b5cfd0d0180d47f547e256525cdeb20f95de3df14a223 + languageName: node + linkType: hard + +"fork-ts-checker-webpack-plugin@npm:^6.0.4": + version: 6.5.2 + resolution: "fork-ts-checker-webpack-plugin@npm:6.5.2" + dependencies: + "@babel/code-frame": ^7.8.3 + "@types/json-schema": ^7.0.5 + chalk: ^4.1.0 + chokidar: ^3.4.2 + cosmiconfig: ^6.0.0 + deepmerge: ^4.2.2 + fs-extra: ^9.0.0 + glob: ^7.1.6 + memfs: ^3.1.2 + minimatch: ^3.0.4 + schema-utils: 2.7.0 + semver: ^7.3.2 + tapable: ^1.0.0 + peerDependencies: + eslint: ">= 6" + typescript: ">= 2.7" + vue-template-compiler: "*" + webpack: ">= 4" + peerDependenciesMeta: + eslint: + optional: true + vue-template-compiler: + optional: true + checksum: 886e606ef582a8a11da95e054f1d0cca0121dfdebefabf4c17e4d9acc029cab173b3be068fec8d8b666abd182571ae87630fb60c3572651e0b26c9811ec952a5 + languageName: node + linkType: hard + +"form-data@npm:^3.0.0": + version: 3.0.1 + resolution: "form-data@npm:3.0.1" + dependencies: + asynckit: ^0.4.0 + combined-stream: ^1.0.8 + mime-types: ^2.1.12 + checksum: 1ccc3ae064a080a799923f754d49fcebdd90515a8924f0f54de557540b50e7f1fe48ba5f2bd0435a5664aa2d49729107e6aaf2155a9abf52339474c5638b4485 + languageName: node + linkType: hard + +"form-data@npm:~2.3.2": + version: 2.3.3 + resolution: "form-data@npm:2.3.3" + dependencies: + asynckit: ^0.4.0 + combined-stream: ^1.0.6 + mime-types: ^2.1.12 + checksum: 706ef1e5649286b6a61e5bb87993a9842807fd8f149cd2548ee807ea4fb882247bdf7f6e64ac4720029c0cd5c80343de0e22eee1dc9e9882e12db9cc7bc016a4 + languageName: node + linkType: hard + +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: 9b67c3fac86acdbc9ae47ba1ddd5f2f81526fa4c8226863ede5600a3f7c7416ef451f6f1e240a3cc32d0fd79fcfe6beb08fd0da454f360032bde70bf80afbb33 + languageName: node + linkType: hard + +"fraction.js@npm:^4.2.0": + version: 4.2.0 + resolution: "fraction.js@npm:4.2.0" + checksum: b16c0a6a7f045b3416c1afbb174b7afca73bd7eb0c62598a0c734a8b1f888cb375684174daf170abfba314da9f366b7d6445e396359d5fae640883bdb2ed18cb + languageName: node + linkType: hard + +"fragment-cache@npm:^0.2.1": + version: 0.2.1 + resolution: "fragment-cache@npm:0.2.1" + dependencies: + map-cache: ^0.2.2 + checksum: 5891d1c1d1d5e1a7fb3ccf28515c06731476fa88f7a50f4ede8a0d8d239a338448e7f7cc8b73db48da19c229fa30066104fe6489862065a4f1ed591c42fbeabf + languageName: node + linkType: hard + +"fresh@npm:0.5.2": + version: 0.5.2 + resolution: "fresh@npm:0.5.2" + checksum: c6d27f3ed86cc5b601404822f31c900dd165ba63fff8152a3ef714e2012e7535027063bc67ded4cb5b3a49fa596495d46cacd9f47d6328459cf570f08b7d9e5a + languageName: node + linkType: hard + +"from2@npm:^1.3.0": + version: 1.3.0 + resolution: "from2@npm:1.3.0" + dependencies: + inherits: ~2.0.1 + readable-stream: ~1.1.10 + checksum: cfa6db903be495425d0f744c2c947506f7ab47f0b023fddd8524c44082363ddadd138ccccb8400fff2ff02adac5c165b0aaf8b516e701f4df19873b93eff173c + languageName: node + linkType: hard + +"from2@npm:^2.1.0": + version: 2.3.0 + resolution: "from2@npm:2.3.0" + dependencies: + inherits: ^2.0.1 + readable-stream: ^2.0.0 + checksum: f87f7a2e4513244d551454a7f8324ef1f7837864a8701c536417286ec19ff4915606b1dfa8909a21b7591ebd8440ffde3642f7c303690b9a4d7c832d62248aa1 + languageName: node + linkType: hard + +"fs-extra@npm:^10.1.0": + version: 10.1.0 + resolution: "fs-extra@npm:10.1.0" + dependencies: + graceful-fs: ^4.2.0 + jsonfile: ^6.0.1 + universalify: ^2.0.0 + checksum: 5f579466e7109719d162a9249abbeffe7f426eb133ea486e020b89bc6d67a741134076bf439983f2eb79276ceaf6bd7b7c1e43c3fd67fe889863e69072fb0a5e + languageName: node + linkType: hard + +"fs-extra@npm:^7.0.1": + version: 7.0.1 + resolution: "fs-extra@npm:7.0.1" + dependencies: + graceful-fs: ^4.1.2 + jsonfile: ^4.0.0 + universalify: ^0.1.0 + checksum: 1943bb2150007e3739921b8d13d4109abdc3cc481e53b97b7ea7f77eda1c3c642e27ae49eac3af074e3496ea02fde30f411ef410c760c70a38b92e656e5da784 + languageName: node + linkType: hard + +"fs-extra@npm:^9.0.0, fs-extra@npm:^9.0.1, fs-extra@npm:^9.1.0": + version: 9.1.0 + resolution: "fs-extra@npm:9.1.0" + dependencies: + at-least-node: ^1.0.0 + graceful-fs: ^4.2.0 + jsonfile: ^6.0.1 + universalify: ^2.0.0 + checksum: 9b808bd884beff5cb940773018179a6b94a966381d005479f00adda6b44e5e3d4abf765135773d849cc27efe68c349e4a7b86acd7d3306d5932c14f3a4b17a92 + languageName: node + linkType: hard + +"fs-minipass@npm:^1.2.7": + version: 1.2.7 + resolution: "fs-minipass@npm:1.2.7" + dependencies: + minipass: ^2.6.0 + checksum: c8259ce8caab360f16b8c3774fd09dd1d5240d6f3f78fd8efa0a215b5f40edfa90e7b5b5ddc2335a4c50885e29d5983f9fe6ac3ac19320e6917a21dbb9f05c64 + languageName: node + linkType: hard + +"fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0": + version: 2.1.0 + resolution: "fs-minipass@npm:2.1.0" + dependencies: + minipass: ^3.0.0 + checksum: 703d16522b8282d7299337539c3ed6edddd1afe82435e4f5b76e34a79cd74e488a8a0e26a636afc2440e1a23b03878e2122e3a2cfe375a5cf63c37d92b86a004 + languageName: node + linkType: hard + +"fs-monkey@npm:^1.0.3": + version: 1.0.3 + resolution: "fs-monkey@npm:1.0.3" + checksum: 197fd276d224d54a27c6267c69887ec29ccd4bedd83d72b5050abf3b6c6ef83d7b86a85a87f615c24a4e6f9a4888fd151c9f16a37ffb23e37c4c2d14c1da6275 + languageName: node + linkType: hard + +"fs-readdir-recursive@npm:^1.1.0": + version: 1.1.0 + resolution: "fs-readdir-recursive@npm:1.1.0" + checksum: 7e190393952143e674b6d1ad4abcafa1b5d3e337fcc21b0cb051079a7140a54617a7df193d562ef9faf21bd7b2148a38601b3d5c16261fa76f278d88dc69989c + languageName: node + linkType: hard + +"fs-vacuum@npm:^1.2.10, fs-vacuum@npm:~1.2.10": + version: 1.2.10 + resolution: "fs-vacuum@npm:1.2.10" + dependencies: + graceful-fs: ^4.1.2 + path-is-inside: ^1.0.1 + rimraf: ^2.5.2 + checksum: 4a3e7c6dc35e5c64c6891e8258704631a91c83e07451b3de5252b5d394058fabac2b0060a11da6ab6d0ec2a4489ed6b312df2346569b4780aaaaf3997818aa5d + languageName: node + linkType: hard + +"fs-write-stream-atomic@npm:^1.0.8, fs-write-stream-atomic@npm:~1.0.10": + version: 1.0.10 + resolution: "fs-write-stream-atomic@npm:1.0.10" + dependencies: + graceful-fs: ^4.1.2 + iferr: ^0.1.5 + imurmurhash: ^0.1.4 + readable-stream: 1 || 2 + checksum: 293b2b4ed346d35a28f8637a20cb2aef31be86503da501c42c2eda8fefed328bac16ce0e5daa7019f9329d73930c58031eaea2ce0c70f1680943fbfb7cff808b + languageName: node + linkType: hard + +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 + languageName: node + linkType: hard + +"fsevents@npm:^1.2.7": + version: 1.2.13 + resolution: "fsevents@npm:1.2.13" + dependencies: + bindings: ^1.5.0 + nan: ^2.12.1 + checksum: 4427ff08db9ee7327f2c3ad58ec56f9096a917eed861bfffaa2e2be419479cdf37d00750869ab9ecbf5f59f32ad999bd59577d73fc639193e6c0ce52bb253e02 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@npm:^2.1.2, fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": + version: 2.3.2 + resolution: "fsevents@npm:2.3.2" + dependencies: + node-gyp: latest + checksum: be78a3efa3e181cda3cf7a4637cb527bcebb0bd0ea0440105a3bb45b86f9245b307dc10a2507e8f4498a7d4ec349d1910f4d73e4d4495b16103106e07eee735b + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@^1.2.7#~builtin<compat/fsevents>": + version: 1.2.13 + resolution: "fsevents@patch:fsevents@npm%3A1.2.13#~builtin<compat/fsevents>::version=1.2.13&hash=18f3a7" + dependencies: + bindings: ^1.5.0 + nan: ^2.12.1 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@^2.1.2#~builtin<compat/fsevents>, fsevents@patch:fsevents@^2.3.2#~builtin<compat/fsevents>, fsevents@patch:fsevents@~2.3.2#~builtin<compat/fsevents>": + version: 2.3.2 + resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin<compat/fsevents>::version=2.3.2&hash=18f3a7" + dependencies: + node-gyp: latest + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.1": + version: 1.1.1 + resolution: "function-bind@npm:1.1.1" + checksum: 60b74b2407e1942e1ed7f8c284f8ef714d0689dcfce5319985a5b7da3fc727f40b4a59ec72dc55aa83365ad7b8fa4fac3a30d93c850a2b452f29ae03dbc10a1e + languageName: node + linkType: hard + +"function.prototype.name@npm:^1.1.0, function.prototype.name@npm:^1.1.5": + version: 1.1.5 + resolution: "function.prototype.name@npm:1.1.5" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.3 + es-abstract: ^1.19.0 + functions-have-names: ^1.2.2 + checksum: b75fb8c5261f03a54f7cb53a8c99e0c40297efc3cf750c51d3a2e56f6741701c14eda51986d30c24063136a4c32d1643df9d1dd2f2a14b64fa011edd3e7117ae + languageName: node + linkType: hard + +"functions-have-names@npm:^1.2.2": + version: 1.2.3 + resolution: "functions-have-names@npm:1.2.3" + checksum: 33e77fd29bddc2d9bb78ab3eb854c165909201f88c75faa8272e35899e2d35a8a642a15e7420ef945e1f64a9670d6aa3ec744106b2aa42be68ca5114025954ca + languageName: node + linkType: hard + +"gauge@npm:^3.0.0": + version: 3.0.2 + resolution: "gauge@npm:3.0.2" + dependencies: + aproba: ^1.0.3 || ^2.0.0 + color-support: ^1.1.2 + console-control-strings: ^1.0.0 + has-unicode: ^2.0.1 + object-assign: ^4.1.1 + signal-exit: ^3.0.0 + string-width: ^4.2.3 + strip-ansi: ^6.0.1 + wide-align: ^1.1.2 + checksum: 75230ccaf216471e31025c7d5fcea1629596ca20792de50c596eb18ffb14d8404f927cd55535aab2eeecd18d1e11bd6f23ec3c2e9878d2dda1dc74bccc34b913 + languageName: node + linkType: hard + +"gauge@npm:^4.0.3": + version: 4.0.4 + resolution: "gauge@npm:4.0.4" + dependencies: + aproba: ^1.0.3 || ^2.0.0 + color-support: ^1.1.3 + console-control-strings: ^1.1.0 + has-unicode: ^2.0.1 + signal-exit: ^3.0.7 + string-width: ^4.2.3 + strip-ansi: ^6.0.1 + wide-align: ^1.1.5 + checksum: ef10d7981113d69225135f994c9f8c4369d945e64a8fc721d655a3a38421b738c9fe899951721d1b47b73c41fdb5404ac87cc8903b2ecbed95d2800363e7e58c + languageName: node + linkType: hard + +"gauge@npm:~2.7.3": + version: 2.7.4 + resolution: "gauge@npm:2.7.4" + dependencies: + aproba: ^1.0.3 + console-control-strings: ^1.0.0 + has-unicode: ^2.0.0 + object-assign: ^4.1.0 + signal-exit: ^3.0.0 + string-width: ^1.0.1 + strip-ansi: ^3.0.1 + wide-align: ^1.1.0 + checksum: d606346e2e47829e0bc855d0becb36c4ce492feabd61ae92884b89e07812dd8a67a860ca30ece3a4c2e9f2c73bd68ba2b8e558ed362432ffd86de83c08847f84 + languageName: node + linkType: hard + +"genfun@npm:^5.0.0": + version: 5.0.0 + resolution: "genfun@npm:5.0.0" + checksum: 06b060aa8697a01a5be53e194dbc2f957273778647fd0f5343a95e1e2bd54cad6dea2d8d9e6c4ebdbac4932df65a49e3ad7722d79f19f8b138ce3db1dc4b2233 + languageName: node + linkType: hard + +"gensync@npm:^1.0.0-beta.1, gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 + languageName: node + linkType: hard + +"gentle-fs@npm:^2.3.0, gentle-fs@npm:^2.3.1": + version: 2.3.1 + resolution: "gentle-fs@npm:2.3.1" + dependencies: + aproba: ^1.1.2 + chownr: ^1.1.2 + cmd-shim: ^3.0.3 + fs-vacuum: ^1.2.10 + graceful-fs: ^4.1.11 + iferr: ^0.1.5 + infer-owner: ^1.0.4 + mkdirp: ^0.5.1 + path-is-inside: ^1.0.2 + read-cmd-shim: ^1.0.1 + slide: ^1.1.6 + checksum: 0da169960b617711416876928539280ac24fd4cd24674cbd0476de04441510ce5703b2b2ec02e92c7d05719a7e45d127a914a0ba9e00a85eccff2985aaaef0e6 + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.1, get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3": + version: 1.1.3 + resolution: "get-intrinsic@npm:1.1.3" + dependencies: + function-bind: ^1.1.1 + has: ^1.0.3 + has-symbols: ^1.0.3 + checksum: 6f201d5f95ea0dd6c8d0dc2c265603aff0b9e15614cb70f8f4674bb3d2b2369d521efaa84d0b70451d2c00762ebd28402758bf46279c6f2a00d242ebac0d8442 + languageName: node + linkType: hard + +"get-own-enumerable-property-symbols@npm:^3.0.0": + version: 3.0.2 + resolution: "get-own-enumerable-property-symbols@npm:3.0.2" + checksum: 103999855f3d1718c631472437161d76962cbddcd95cc642a34c07bfb661ed41b6c09a9c669ccdff89ee965beb7126b80eec7b2101e20e31e9cc6c4725305e10 + languageName: node + linkType: hard + +"get-package-type@npm:^0.1.0": + version: 0.1.0 + resolution: "get-package-type@npm:0.1.0" + checksum: e34cdf447fdf1902a1f6d5af737eaadf606d2ee3518287abde8910e04159368c268568174b2e71102b87b26c2020486f126bfca9c4fb1ceb986ff99b52ecd1be + languageName: node + linkType: hard + +"get-stdin@npm:^4.0.1": + version: 4.0.1 + resolution: "get-stdin@npm:4.0.1" + checksum: 68fc39a0af6050bcad791fb3df72999e7636401f11f574bf24af07b1c640d30c01cf38aa39ee55665a93ee7a7753eeb6d1fce6c434dd1f458ee0f8fd02775809 + languageName: node + linkType: hard + +"get-stdin@npm:^5.0.1": + version: 5.0.1 + resolution: "get-stdin@npm:5.0.1" + checksum: 309f933f08a4d6783681674451027802299124e596324cd628c5e5138bbc5de843bbaa345a8ce0fc72304869f9de3b50086407aca551e292b13f7cb02351479e + languageName: node + linkType: hard + +"get-stream@npm:^3.0.0": + version: 3.0.0 + resolution: "get-stream@npm:3.0.0" + checksum: 003f5f3b8870da59c6aafdf6ed7e7b07b48c2f8629cd461bd3900726548b6b8cfa2e14d6b7814fbb08f07a42f4f738407fa70b989928b2783a76b278505bba22 + languageName: node + linkType: hard + +"get-stream@npm:^4.0.0, get-stream@npm:^4.1.0": + version: 4.1.0 + resolution: "get-stream@npm:4.1.0" + dependencies: + pump: ^3.0.0 + checksum: 294d876f667694a5ca23f0ca2156de67da950433b6fb53024833733975d32582896dbc7f257842d331809979efccf04d5e0b6b75ad4d45744c45f193fd497539 + languageName: node + linkType: hard + +"get-stream@npm:^5.0.0, get-stream@npm:^5.1.0": + version: 5.2.0 + resolution: "get-stream@npm:5.2.0" + dependencies: + pump: ^3.0.0 + checksum: 43797ffd815fbb26685bf188c8cfebecb8af87b3925091dd7b9a9c915993293d78e3c9e1bce125928ff92f2d0796f3889b92b5ec6d58d1041b574682132e0a80 + languageName: node + linkType: hard + +"get-stream@npm:^6.0.0": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: 49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 + languageName: node + linkType: hard + +"get-symbol-description@npm:^1.0.0": + version: 1.0.0 + resolution: "get-symbol-description@npm:1.0.0" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.1.1 + checksum: 23bc3b44c221cdf7669a88230c62f4b9e30393b61eb21ba4400cb3e346801bd8f95fe4330ee78dbae37aecd874646d53e3e76a17a654d0c84c77f6690526d6bb + languageName: node + linkType: hard + +"get-value@npm:^2.0.3, get-value@npm:^2.0.6": + version: 2.0.6 + resolution: "get-value@npm:2.0.6" + checksum: f069c132791b357c8fc4adfe9e2929b0a2c6e95f98ca7bc6fcbc27f8a302e552f86b4ae61ec56d9e9ac2544b93b6a39743d479866a37b43fcc104088ba74f0d9 + languageName: node + linkType: hard + +"getos@npm:^3.2.1": + version: 3.2.1 + resolution: "getos@npm:3.2.1" + dependencies: + async: ^3.2.0 + checksum: 21556fca1da4dfc8f1707261b4f9ff19b9e9bfefa76478249d2abddba3cd014bd6c5360634add1590b27e0b27d422e8f997dddaa0234aae1fa4c54f33f82e841 + languageName: node + linkType: hard + +"getpass@npm:^0.1.1": + version: 0.1.7 + resolution: "getpass@npm:0.1.7" + dependencies: + assert-plus: ^1.0.0 + checksum: c13f8530ecf16fc509f3fa5cd8dd2129ffa5d0c7ccdf5728b6022d52954c2d24be3706b4cdf15333eec52f1fbb43feb70a01dabc639d1d10071e371da8aaa52f + languageName: node + linkType: hard + +"github-slugger@npm:^1.0.0": + version: 1.5.0 + resolution: "github-slugger@npm:1.5.0" + checksum: 116f99732925f939cbfd6f2e57db1aa7e111a460db0d103e3b3f2fce6909d44311663d4542350706cad806345b9892358cc3b153674f88eeae77f43380b3bfca + languageName: node + linkType: hard + +"glob-parent@npm:^3.1.0": + version: 3.1.0 + resolution: "glob-parent@npm:3.1.0" + dependencies: + is-glob: ^3.1.0 + path-dirname: ^1.0.0 + checksum: bfa89ce5ae1dfea4c2ece7b61d2ea230d87fcbec7472915cfdb3f4caf688a91ecb0dc86ae39b1e17505adce7e64cae3b971d64dc66091f3a0131169fd631b00d + languageName: node + linkType: hard + +"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: ^4.0.1 + checksum: cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee + languageName: node + linkType: hard + +"glob-parent@npm:^6.0.1, glob-parent@npm:^6.0.2": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" + dependencies: + is-glob: ^4.0.3 + checksum: 317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 + languageName: node + linkType: hard + +"glob-promise@npm:^3.4.0": + version: 3.4.0 + resolution: "glob-promise@npm:3.4.0" + dependencies: + "@types/glob": "*" + peerDependencies: + glob: "*" + checksum: 8862e309f46a97c9491d35900257c9174f72b8358a8f485cdba88eb2d5b0f9cf496dfe635711bbd871e89165f50ec3acd435cbef5bfc5efbc14508abced778ac + languageName: node + linkType: hard + +"glob-promise@npm:^5.0.0": + version: 5.0.1 + resolution: "glob-promise@npm:5.0.1" + dependencies: + "@types/glob": ^8.0.0 + npm-install-peers: ^1.2.2 + peerDependencies: + glob: ^8.0.3 + checksum: 1222dc4e29c41dc7aa13bf634358d9e7a4206607d32f38ec436f69ff43341b4ffdb1fdc7c9c893fb32a8013835755d29293a1f68e0579884bf66eff539f54b2b + languageName: node + linkType: hard + +"glob-to-regexp@npm:^0.3.0": + version: 0.3.0 + resolution: "glob-to-regexp@npm:0.3.0" + checksum: f7e8091288d88b397b715281560d86ba4998246c300cb0d51db483db0a4c68cb48b489af8da9c03262745e8aa5337ba596d82dee61ff9467c5d7c27d70b676aa + languageName: node + linkType: hard + +"glob-to-regexp@npm:^0.4.1": + version: 0.4.1 + resolution: "glob-to-regexp@npm:0.4.1" + checksum: 0486925072d7a916f052842772b61c3e86247f0a80cc0deb9b5a3e8a1a9faad5b04fb6f58986a09f34d3e96cd2a22a24b7e9882fb1cf904c31e9a310de96c429 + languageName: node + linkType: hard + +"glob@npm:^7.1.1, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.2.0, glob@npm:^7.2.3": + version: 7.2.3 + resolution: "glob@npm:7.2.3" + 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 + checksum: 65676153e2b0c9095100fe7f25a778bf45608eeb32c6048cf307f579649bcc30353277b3b898a3792602c65764e5baa4f643714dfbdfd64ea271d210c7a425fe + languageName: node + linkType: hard + +"glob@npm:^8.0.1": + version: 8.1.0 + resolution: "glob@npm:8.1.0" + dependencies: + fs.realpath: ^1.0.0 + inflight: ^1.0.4 + inherits: 2 + minimatch: ^5.0.1 + once: ^1.3.0 + checksum: cb0b5cab17a59c57299376abe5646c7070f8acb89df5595b492dba3bfb43d301a46c01e5695f01154e6553168207cb60d4eaf07d3be4bc3eb9b0457c5c561d0f + languageName: node + linkType: hard + +"global-dirs@npm:^0.1.0": + version: 0.1.1 + resolution: "global-dirs@npm:0.1.1" + dependencies: + ini: ^1.3.4 + checksum: 3608072e58962396c124ad5a1cfb3f99ee76c998654a3432d82977b3c3eeb09dc8a5a2a9849b2b8113906c8d0aad89ce362c22e97cec5fe34405bbf4f3cdbe7a + languageName: node + linkType: hard + +"global-dirs@npm:^3.0.0": + version: 3.0.1 + resolution: "global-dirs@npm:3.0.1" + dependencies: + ini: 2.0.0 + checksum: ef65e2241a47ff978f7006a641302bc7f4c03dfb98783d42bf7224c136e3a06df046e70ee3a010cf30214114755e46c9eb5eb1513838812fbbe0d92b14c25080 + languageName: node + linkType: hard + +"global@npm:^4.4.0": + version: 4.4.0 + resolution: "global@npm:4.4.0" + dependencies: + min-document: ^2.19.0 + process: ^0.11.10 + checksum: 4a467aec6602c00a7c5685f310574ab04e289ad7f894f0f01c9c5763562b82f4b92d1e381ce6c5bbb12173e2a9f759c1b63dda6370cfb199970267e14d90aa91 + languageName: node + linkType: hard + +"globals@npm:^11.1.0": + version: 11.12.0 + resolution: "globals@npm:11.12.0" + checksum: 758f9f258e7b19226bd8d4af5d3b0dcf7038780fb23d82e6f98932c44e239f884847f1766e8fa9cc5635ccb3204f7fa7314d4408dd4002a5e8ea827b4018f0a1 + languageName: node + linkType: hard + +"globals@npm:^13.19.0": + version: 13.19.0 + resolution: "globals@npm:13.19.0" + dependencies: + type-fest: ^0.20.2 + checksum: d2bb3164ed9f5ec82b91e96d6a5ffc1cca3cb10f6c41df9687cd7712ba82f5534ed028b11c5717d71c938403bf8ffc97bb06f5f2eab8c1b91e6273b08b33b5e6 + languageName: node + linkType: hard + +"globals@npm:^9.18.0": + version: 9.18.0 + resolution: "globals@npm:9.18.0" + checksum: 5ab74cb67cf060a9fceede4a0f2babc4c2c0b90dbb13847d2659defdf2121c60035ef23823c8417ce8c11bdaa7b412396077f2b3d2a7dedab490a881a0a96754 + languageName: node + linkType: hard + +"globalthis@npm:^1.0.0, globalthis@npm:^1.0.3": + version: 1.0.3 + resolution: "globalthis@npm:1.0.3" + dependencies: + define-properties: ^1.1.3 + checksum: 0db6e9af102a5254630351557ac15e6909bc7459d3e3f6b001e59fe784c96d31108818f032d9095739355a88467459e6488ff16584ee6250cd8c27dec05af4b0 + languageName: node + linkType: hard + +"globby@npm:^11.0.2, globby@npm:^11.1.0": + version: 11.1.0 + resolution: "globby@npm:11.1.0" + 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 + checksum: b39511b4afe4bd8a7aead3a27c4ade2b9968649abab0a6c28b1a90141b96ca68ca5db1302f7c7bd29eab66bf51e13916b8e0a3d0ac08f75e1e84a39b35691189 + languageName: node + linkType: hard + +"globby@npm:^13.1.1": + version: 13.1.3 + resolution: "globby@npm:13.1.3" + dependencies: + dir-glob: ^3.0.1 + fast-glob: ^3.2.11 + ignore: ^5.2.0 + merge2: ^1.4.1 + slash: ^4.0.0 + checksum: 34199932fad67ae6a4cca764eaad8e7678efabd4321f553bfb8a52046e03f8e8e2f9c14216a6734b692b7c26c4da1b1cfe9ce23733d28d1777d73f4bf34b09c7 + languageName: node + linkType: hard + +"globby@npm:^9.2.0": + version: 9.2.0 + resolution: "globby@npm:9.2.0" + dependencies: + "@types/glob": ^7.1.1 + array-union: ^1.0.2 + dir-glob: ^2.2.2 + fast-glob: ^2.2.6 + glob: ^7.1.3 + ignore: ^4.0.3 + pify: ^4.0.1 + slash: ^2.0.0 + checksum: 2bd47ec43797b81000f3619feff96803b22591961788c06d746f6c8ba2deb14676b591ee625eb74b197c0047b2236e4a7a2ad662417661231b317c1de67aee94 + languageName: node + linkType: hard + +"glur@npm:^1.1.2": + version: 1.1.2 + resolution: "glur@npm:1.1.2" + checksum: 756fcbc7f1a8576755811e31367feeaffbd13b7f20d788672bccbd65956839065e256621a7576f4ab321352b28a0aea442d64567bca23882526b891767ffbe3e + languageName: node + linkType: hard + +"gopd@npm:^1.0.1": + version: 1.0.1 + resolution: "gopd@npm:1.0.1" + dependencies: + get-intrinsic: ^1.1.3 + checksum: 505c05487f7944c552cee72087bf1567debb470d4355b1335f2c262d218ebbff805cd3715448fe29b4b380bae6912561d0467233e4165830efd28da241418c63 + languageName: node + linkType: hard + +"got@npm:^6.7.1": + version: 6.7.1 + resolution: "got@npm:6.7.1" + dependencies: + create-error-class: ^3.0.0 + duplexer3: ^0.1.4 + get-stream: ^3.0.0 + is-redirect: ^1.0.0 + is-retry-allowed: ^1.0.0 + is-stream: ^1.0.0 + lowercase-keys: ^1.0.0 + safe-buffer: ^5.0.1 + timed-out: ^4.0.0 + unzip-response: ^2.0.1 + url-parse-lax: ^1.0.0 + checksum: 10a3b2254b3c1dd61a93f7c3dd3061bfc13c4c7c1fc9a7cfa348e5d4f619c20dcf8f8638da4a46372ea9a646fd56fe4cf291174c8a50ab4c8f1f70809c767a15 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.10, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": + version: 4.2.10 + resolution: "graceful-fs@npm:4.2.10" + checksum: 4223a833e38e1d0d2aea630c2433cfb94ddc07dfc11d511dbd6be1d16688c5be848acc31f9a5d0d0ddbfb56d2ee5a6ae0278aceeb0ca6a13f27e06b9956fb952 + languageName: node + linkType: hard + +"grapheme-splitter@npm:^1.0.4": + version: 1.0.4 + resolution: "grapheme-splitter@npm:1.0.4" + checksum: 108415fb07ac913f17040dc336607772fcea68c7f495ef91887edddb0b0f5ff7bc1d1ab181b125ecb2f0505669ef12c9a178a3bbd2dd8e042d8c5f1d7c90331a + languageName: node + linkType: hard + +"gzip-size@npm:^6.0.0": + version: 6.0.0 + resolution: "gzip-size@npm:6.0.0" + dependencies: + duplexer: ^0.1.2 + checksum: 4ccb924626c82125897a997d1c84f2377846a6ef57fbee38f7c0e6b41387fba4d00422274440747b58008b5d60114bac2349c2908e9aba55188345281af40a3f + languageName: node + linkType: hard + +"handle-thing@npm:^2.0.0": + version: 2.0.1 + resolution: "handle-thing@npm:2.0.1" + checksum: 7ae34ba286a3434f1993ebd1cc9c9e6b6d8ea672182db28b1afc0a7119229552fa7031e3e5f3cd32a76430ece4e94b7da6f12af2eb39d6239a7693e4bd63a998 + languageName: node + linkType: hard + +"handlebars@npm:^4.7.7": + version: 4.7.7 + resolution: "handlebars@npm:4.7.7" + dependencies: + minimist: ^1.2.5 + neo-async: ^2.6.0 + source-map: ^0.6.1 + uglify-js: ^3.1.4 + wordwrap: ^1.0.0 + dependenciesMeta: + uglify-js: + optional: true + bin: + handlebars: bin/handlebars + checksum: 4c0913fc0018a2a2e358ee94e4fe83f071762b8bec51a473d187e6642e94e569843adcf550ffe329554c63ad450c062f3a05447bd2e3fff5ebfe698e214225c6 + languageName: node + linkType: hard + +"har-schema@npm:^2.0.0": + version: 2.0.0 + resolution: "har-schema@npm:2.0.0" + checksum: 3856cb76152658e0002b9c2b45b4360bb26b3e832c823caed8fcf39a01096030bf09fa5685c0f7b0f2cb3ecba6e9dce17edaf28b64a423d6201092e6be56e592 + languageName: node + linkType: hard + +"har-validator@npm:~5.1.3": + version: 5.1.5 + resolution: "har-validator@npm:5.1.5" + dependencies: + ajv: ^6.12.3 + har-schema: ^2.0.0 + checksum: f1d606eb1021839e3a905be5ef7cca81c2256a6be0748efb8fefc14312214f9e6c15d7f2eaf37514104071207d84f627b68bb9f6178703da4e06fbd1a0649a5e + languageName: node + linkType: hard + +"has-ansi@npm:^2.0.0": + version: 2.0.0 + resolution: "has-ansi@npm:2.0.0" + dependencies: + ansi-regex: ^2.0.0 + checksum: f54e4887b9f8f3c4bfefd649c48825b3c093987c92c27880ee9898539e6f01aed261e82e73153c3f920fde0db5bf6ebd58deb498ed1debabcb4bc40113ccdf05 + languageName: node + linkType: hard + +"has-bigints@npm:^1.0.1, has-bigints@npm:^1.0.2": + version: 1.0.2 + resolution: "has-bigints@npm:1.0.2" + checksum: 724eb1485bfa3cdff6f18d95130aa190561f00b3fcf9f19dc640baf8176b5917c143b81ec2123f8cddb6c05164a198c94b13e1377c497705ccc8e1a80306e83b + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"has-glob@npm:^1.0.0": + version: 1.0.0 + resolution: "has-glob@npm:1.0.0" + dependencies: + is-glob: ^3.0.0 + checksum: 2546d20b7a667304d8b2e490c2d5a4e20e799a43eb6d97c0d47c0c737bbde082a73731001c791d445b904b3f408d584477df7d2d301183e13c4b3f0a3c81787b + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.0": + version: 1.0.0 + resolution: "has-property-descriptors@npm:1.0.0" + dependencies: + get-intrinsic: ^1.1.1 + checksum: d4ca882b6960d6257bd28baa3ddfa21f068d260411004a093b30ca357c740e11e985771c85216a6d1eef4161e862657f48c4758ec8ab515223b3895200ad164b + languageName: node + linkType: hard + +"has-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "has-proto@npm:1.0.1" + checksum: c8a8fe411f810b23a564bd5546a8f3f0fff6f1b692740eb7a2fdc9df716ef870040806891e2f23ff4653f1083e3895bf12088703dd1a0eac3d9202d3a4768cd0 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3": + version: 1.0.3 + resolution: "has-symbols@npm:1.0.3" + checksum: e6922b4345a3f37069cdfe8600febbca791c94988c01af3394d86ca3360b4b93928bbf395859158f88099cb10b19d98e3bbab7c9ff2c1bd09cf665ee90afa2c3 + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.0": + version: 1.0.0 + resolution: "has-tostringtag@npm:1.0.0" + dependencies: + has-symbols: ^1.0.2 + checksum: 1cdba76b7d13f65198a92b8ca1560ba40edfa09e85d182bf436d928f3588a9ebd260451d569f0ed1b849c4bf54f49c862aa0d0a77f9552b1855bb6deb526c011 + languageName: node + linkType: hard + +"has-unicode@npm:^2.0.0, has-unicode@npm:^2.0.1, has-unicode@npm:~2.0.1": + version: 2.0.1 + resolution: "has-unicode@npm:2.0.1" + checksum: ebdb2f4895c26bb08a8a100b62d362e49b2190bcfd84b76bc4be1a3bd4d254ec52d0dd9f2fbcc093fc5eb878b20c52146f9dfd33e2686ed28982187be593b47c + languageName: node + linkType: hard + +"has-value@npm:^0.3.1": + version: 0.3.1 + resolution: "has-value@npm:0.3.1" + dependencies: + get-value: ^2.0.3 + has-values: ^0.1.4 + isobject: ^2.0.0 + checksum: 7a7c2e9d07bc9742c81806150adb154d149bc6155267248c459cd1ce2a64b0759980d26213260e4b7599c8a3754551179f155ded88d0533a0d2bc7bc29028432 + languageName: node + linkType: hard + +"has-value@npm:^1.0.0": + version: 1.0.0 + resolution: "has-value@npm:1.0.0" + dependencies: + get-value: ^2.0.6 + has-values: ^1.0.0 + isobject: ^3.0.0 + checksum: 17cdccaf50f8aac80a109dba2e2ee5e800aec9a9d382ef9deab66c56b34269e4c9ac720276d5ffa722764304a1180ae436df077da0dd05548cfae0209708ba4d + languageName: node + linkType: hard + +"has-values@npm:^0.1.4": + version: 0.1.4 + resolution: "has-values@npm:0.1.4" + checksum: a8f00ad862c20289798c35243d5bd0b0a97dd44b668c2204afe082e0265f2d0bf3b89fc8cc0ef01a52b49f10aa35cf85c336ee3a5f1cac96ed490f5e901cdbf2 + languageName: node + linkType: hard + +"has-values@npm:^1.0.0": + version: 1.0.0 + resolution: "has-values@npm:1.0.0" + dependencies: + is-number: ^3.0.0 + kind-of: ^4.0.0 + checksum: a6f2a1cc6b2e43eacc68e62e71ad6890def7f4b13d2ef06b4ad3ee156c23e470e6df144b9b467701908e17633411f1075fdff0cab45fb66c5e0584d89b25f35e + languageName: node + linkType: hard + +"has@npm:^1.0.3": + version: 1.0.3 + resolution: "has@npm:1.0.3" + dependencies: + function-bind: ^1.1.1 + checksum: e1da0d2bd109f116b632f27782cf23182b42f14972ca9540e4c5aa7e52647407a0a4a76937334fddcb56befe94a3494825ec22b19b51f5e5507c3153fd1a5e1b + languageName: node + linkType: hard + +"hash-base@npm:^3.0.0": + version: 3.1.0 + resolution: "hash-base@npm:3.1.0" + dependencies: + inherits: ^2.0.4 + readable-stream: ^3.6.0 + safe-buffer: ^5.2.0 + checksum: 663eabcf4173326fbb65a1918a509045590a26cc7e0964b754eef248d281305c6ec9f6b31cb508d02ffca383ab50028180ce5aefe013e942b44a903ac8dc80d0 + languageName: node + linkType: hard + +"hash.js@npm:^1.0.0, hash.js@npm:^1.0.3": + version: 1.1.7 + resolution: "hash.js@npm:1.1.7" + dependencies: + inherits: ^2.0.3 + minimalistic-assert: ^1.0.1 + checksum: 41ada59494eac5332cfc1ce6b7ebdd7b88a3864a6d6b08a3ea8ef261332ed60f37f10877e0c825aaa4bddebf164fbffa618286aeeec5296675e2671cbfa746c4 + languageName: node + linkType: hard + +"hast-to-hyperscript@npm:^9.0.0": + version: 9.0.1 + resolution: "hast-to-hyperscript@npm:9.0.1" + dependencies: + "@types/unist": ^2.0.3 + comma-separated-tokens: ^1.0.0 + property-information: ^5.3.0 + space-separated-tokens: ^1.0.0 + style-to-object: ^0.3.0 + unist-util-is: ^4.0.0 + web-namespaces: ^1.0.0 + checksum: 630f0db8e1c78d8d6e4f8bd19dec4b6ff6c3048ba0b07b8e34bb812dfbbdc96f4c16abca16c3bfc64e7757921f42790a7bd4a693d6ce99375f99dead65a19a12 + languageName: node + linkType: hard + +"hast-util-from-parse5@npm:^6.0.0": + version: 6.0.1 + resolution: "hast-util-from-parse5@npm:6.0.1" + dependencies: + "@types/parse5": ^5.0.0 + hastscript: ^6.0.0 + property-information: ^5.0.0 + vfile: ^4.0.0 + vfile-location: ^3.2.0 + web-namespaces: ^1.0.0 + checksum: c5e7ee40347c3850ece717e37c3e277ca233848ebca341f68c2afbefdb912da415a2fd06940edc3ea4882ad520e1cac7bf3fcf66c31ae97e1bcf953fcb6a7db5 + languageName: node + linkType: hard + +"hast-util-parse-selector@npm:^2.0.0": + version: 2.2.5 + resolution: "hast-util-parse-selector@npm:2.2.5" + checksum: 29b7ee77960ded6a99d30c287d922243071cc07b39f2006f203bd08ee54eb8f66bdaa86ef6527477c766e2382d520b60ee4e4087f189888c35d8bcc020173648 + languageName: node + linkType: hard + +"hast-util-raw@npm:6.0.1": + version: 6.0.1 + resolution: "hast-util-raw@npm:6.0.1" + dependencies: + "@types/hast": ^2.0.0 + hast-util-from-parse5: ^6.0.0 + hast-util-to-parse5: ^6.0.0 + html-void-elements: ^1.0.0 + parse5: ^6.0.0 + unist-util-position: ^3.0.0 + vfile: ^4.0.0 + web-namespaces: ^1.0.0 + xtend: ^4.0.0 + zwitch: ^1.0.0 + checksum: 0ed0a2731251a4853710eda38e0bb79ee1ad8ccea69b391c16eb20895895818bced1c2c9eaf8853280f0aa6dc71d22b9eb6c9aab770dd1a225bb44d522eef1ef + languageName: node + linkType: hard + +"hast-util-to-parse5@npm:^6.0.0": + version: 6.0.0 + resolution: "hast-util-to-parse5@npm:6.0.0" + dependencies: + hast-to-hyperscript: ^9.0.0 + property-information: ^5.0.0 + web-namespaces: ^1.0.0 + xtend: ^4.0.0 + zwitch: ^1.0.0 + checksum: 49d6c2389fd3170741cdb0483666bccd7e9e436fe386bcbd3931b019e4c006b5bb48022e07967e1021336e744e901082d6479cfa4bc2082efa3b1e5bdab2a36f + languageName: node + linkType: hard + +"hastscript@npm:^6.0.0": + version: 6.0.0 + resolution: "hastscript@npm:6.0.0" + dependencies: + "@types/hast": ^2.0.0 + comma-separated-tokens: ^1.0.0 + hast-util-parse-selector: ^2.0.0 + property-information: ^5.0.0 + space-separated-tokens: ^1.0.0 + checksum: f76d9cf373cb075c8523c8ad52709f09f7e02b7c9d3152b8d35c65c265b9f1878bed6023f215a7d16523921036d40a7da292cb6f4399af9b5eccac2a5a5eb330 + languageName: node + linkType: hard + +"he@npm:^1.2.0": + version: 1.2.0 + resolution: "he@npm:1.2.0" + bin: + he: bin/he + checksum: a27d478befe3c8192f006cdd0639a66798979dfa6e2125c6ac582a19a5ebfec62ad83e8382e6036170d873f46e4536a7e795bf8b95bf7c247f4cc0825ccc8c17 + languageName: node + linkType: hard + +"highlight.js@npm:^10.5.0": + version: 10.7.3 + resolution: "highlight.js@npm:10.7.3" + checksum: 073837eaf816922427a9005c56c42ad8786473dc042332dfe7901aa065e92bc3d94ebf704975257526482066abb2c8677cc0326559bb8621e046c21c5991c434 + languageName: node + linkType: hard + +"history@npm:^4.9.0": + version: 4.10.1 + resolution: "history@npm:4.10.1" + dependencies: + "@babel/runtime": ^7.1.2 + loose-envify: ^1.2.0 + resolve-pathname: ^3.0.0 + tiny-invariant: ^1.0.2 + tiny-warning: ^1.0.0 + value-equal: ^1.0.1 + checksum: 35377694e4f10f2cf056a9cb1a8ee083e04e4b4717a63baeee4afd565658a62c7e73700bf9e82aa53dbe1ec94e0a25a83c080d63bad8ee6b274a98d2fbc5ed4c + languageName: node + linkType: hard + +"hmac-drbg@npm:^1.0.1": + version: 1.0.1 + resolution: "hmac-drbg@npm:1.0.1" + dependencies: + hash.js: ^1.0.3 + minimalistic-assert: ^1.0.0 + minimalistic-crypto-utils: ^1.0.1 + checksum: f3d9ba31b40257a573f162176ac5930109816036c59a09f901eb2ffd7e5e705c6832bedfff507957125f2086a0ab8f853c0df225642a88bf1fcaea945f20600d + languageName: node + linkType: hard + +"hoist-non-react-statics@npm:^3.1.0, hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1, hoist-non-react-statics@npm:^3.3.2": + version: 3.3.2 + resolution: "hoist-non-react-statics@npm:3.3.2" + dependencies: + react-is: ^16.7.0 + checksum: fe0889169e845d738b59b64badf5e55fa3cf20454f9203d1eb088df322d49d4318df774828e789898dcb280e8a5521bb59b3203385662ca5e9218a6ca5820e74 + languageName: node + linkType: hard + +"homedir-polyfill@npm:^1.0.1": + version: 1.0.3 + resolution: "homedir-polyfill@npm:1.0.3" + dependencies: + parse-passwd: ^1.0.0 + checksum: 3c099844f94b8b438f124bd5698bdcfef32b2d455115fb8050d7148e7f7b95fc89ba9922586c491f0e1cdebf437b1053c84ecddb8d596e109e9ac69c5b4a9e27 + languageName: node + linkType: hard + +"hosted-git-info@npm:^2.1.4, hosted-git-info@npm:^2.7.1, hosted-git-info@npm:^2.8.9": + version: 2.8.9 + resolution: "hosted-git-info@npm:2.8.9" + checksum: 317cbc6b1bbbe23c2a40ae23f3dafe9fa349ce42a89a36f930e3f9c0530c179a3882d2ef1e4141a4c3674d6faaea862138ec55b43ad6f75e387fda2483a13c70 + languageName: node + linkType: hard + +"hpack.js@npm:^2.1.6": + version: 2.1.6 + resolution: "hpack.js@npm:2.1.6" + dependencies: + inherits: ^2.0.1 + obuf: ^1.0.0 + readable-stream: ^2.0.1 + wbuf: ^1.1.0 + checksum: 55b9e824430bab82a19d079cb6e33042d7d0640325678c9917fcc020c61d8a08ca671b6c942c7f0aae9bb6e4b67ffb50734a72f9e21d66407c3138c1983b70f0 + languageName: node + linkType: hard + +"html-entities@npm:^2.1.0, html-entities@npm:^2.3.2": + version: 2.3.3 + resolution: "html-entities@npm:2.3.3" + checksum: a76cbdbb276d9499dc7ef800d23f3964254e659f04db51c8d1ff6abfe21992c69b7217ecfd6e3c16ff0aa027ba4261d77f0dba71f55639c16a325bbdf69c535d + languageName: node + linkType: hard + +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 + languageName: node + linkType: hard + +"html-minifier-terser@npm:^5.0.1": + version: 5.1.1 + resolution: "html-minifier-terser@npm:5.1.1" + dependencies: + camel-case: ^4.1.1 + clean-css: ^4.2.3 + commander: ^4.1.1 + he: ^1.2.0 + param-case: ^3.0.3 + relateurl: ^0.2.7 + terser: ^4.6.3 + bin: + html-minifier-terser: cli.js + checksum: b38e678aa8065358c31ab58ada6efa1563e6e8d74c198ed1a1240b9d4ffcec077e2c5ce42b87f4fdefd7dd9041f82beb5cbd804c4f4179afc6f0f6e89b63f5f6 + languageName: node + linkType: hard + +"html-minifier-terser@npm:^6.0.2": + version: 6.1.0 + resolution: "html-minifier-terser@npm:6.1.0" + dependencies: + camel-case: ^4.1.2 + clean-css: ^5.2.2 + commander: ^8.3.0 + he: ^1.2.0 + param-case: ^3.0.4 + relateurl: ^0.2.7 + terser: ^5.10.0 + bin: + html-minifier-terser: cli.js + checksum: 1aa4e4f01cf7149e3ac5ea84fb7a1adab86da40d38d77a6fff42852b5ee3daccb78b615df97264e3a6a5c33e57f0c77f471d607ca1e1debd1dab9b58286f4b5a + languageName: node + linkType: hard + +"html-tags@npm:^3.1.0": + version: 3.2.0 + resolution: "html-tags@npm:3.2.0" + checksum: fc8ac525e193354bf51b64f0e32a729a2e222b6c0f34cedab0259a35ddc5b7e31ddb556b516ea1a5725339a1085098a5f47ff385a3fa50291523d426b54012da + languageName: node + linkType: hard + +"html-to-image@npm:^1.9.0": + version: 1.11.4 + resolution: "html-to-image@npm:1.11.4" + checksum: 16d5104ec2914da78080abc660bfeec4a193b1ea36bf4ed62db3ac249e655c29e2b655b8cbfa8cd6901dd4e5dc1be210b89d2004ba9ce02ba082a8c74126818b + languageName: node + linkType: hard + +"html-void-elements@npm:^1.0.0": + version: 1.0.5 + resolution: "html-void-elements@npm:1.0.5" + checksum: 97b6c108d7d6b31a45deddf95a65eb074bd0f358b55a61f3a031e055812eec368076ca23f0181674c5212166168988f35312756a3b376490e31e73d9a51f5549 + languageName: node + linkType: hard + +"html-webpack-plugin@npm:^4.0.0": + version: 4.5.2 + resolution: "html-webpack-plugin@npm:4.5.2" + dependencies: + "@types/html-minifier-terser": ^5.0.0 + "@types/tapable": ^1.0.5 + "@types/webpack": ^4.41.8 + html-minifier-terser: ^5.0.1 + loader-utils: ^1.2.3 + lodash: ^4.17.20 + pretty-error: ^2.1.1 + tapable: ^1.1.3 + util.promisify: 1.0.0 + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + checksum: 405f01eb8d5554bd0330c462003e215a793518809e29df0121d20ba2a9717078df33089fda0464c62453ce3af12b6a1fee51dd24761a56f610f509e1e5d503e9 + languageName: node + linkType: hard + +"html-webpack-plugin@npm:^5.0.0, html-webpack-plugin@npm:^5.5.0": + version: 5.5.0 + resolution: "html-webpack-plugin@npm:5.5.0" + dependencies: + "@types/html-minifier-terser": ^6.0.0 + html-minifier-terser: ^6.0.2 + lodash: ^4.17.21 + pretty-error: ^4.0.0 + tapable: ^2.0.0 + peerDependencies: + webpack: ^5.20.0 + checksum: d10fa5888db9ee2afe1d8544107d3d8eb0f30fd88a3304842725e91f9b86cd70fae9954342e6d513bdf9bb13f345c5f51c09421dbd96285593ea7ee8444b188e + languageName: node + linkType: hard + +"html2canvas@npm:^1.0.0-rc.5, html2canvas@npm:^1.4.1": + version: 1.4.1 + resolution: "html2canvas@npm:1.4.1" + dependencies: + css-line-break: ^2.1.0 + text-segmentation: ^1.0.3 + checksum: 6de86f75762b00948edf2ea559f16da0a1ec3facc4a8a7d3f35fcec59bb0c5970463478988ae3d9082152e0173690d46ebf4082e7ac803dd4817bae1d355c0db + languageName: node + linkType: hard + +"htmlparser2@npm:3.8.x": + version: 3.8.3 + resolution: "htmlparser2@npm:3.8.3" + dependencies: + domelementtype: 1 + domhandler: 2.3 + domutils: 1.5 + entities: 1.0 + readable-stream: 1.1 + checksum: 253a673976c1e2c2b8429e45830a5a89c3f23bb6dd34f5958aefbb104a8caf1268515b535277714deb034d894aa0bd315e901cbf2ec906e8ed0d1e49b2d73b3e + languageName: node + linkType: hard + +"htmlparser2@npm:^6.1.0": + version: 6.1.0 + resolution: "htmlparser2@npm:6.1.0" + dependencies: + domelementtype: ^2.0.1 + domhandler: ^4.0.0 + domutils: ^2.5.2 + entities: ^2.0.0 + checksum: 3058499c95634f04dc66be8c2e0927cd86799413b2d6989d8ae542ca4dbf5fa948695d02c27d573acf44843af977aec6d9a7bdd0f6faa6b2d99e2a729b2a31b6 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^3.8.1": + version: 3.8.1 + resolution: "http-cache-semantics@npm:3.8.1" + checksum: 8925daec009618d5a48c8a36fcb312785fe78c7b22db8008ed58ca84d08fdc41596b63e0507b577ad0bf46e868a74944ab03a037fdb3f31d5d49d3c79df8d9e4 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.0": + version: 4.1.0 + resolution: "http-cache-semantics@npm:4.1.0" + checksum: abe115ddd9f24914a49842f2745ecc8380837bbe30b59b154648c76ebc1bd3d5f8bd05c1789aaa2ae6b79624c591d13c8aa79104ff21078e117140a65ac20654 + languageName: node + linkType: hard + +"http-deceiver@npm:^1.2.7": + version: 1.2.7 + resolution: "http-deceiver@npm:1.2.7" + checksum: 8bb9b716f5fc55f54a451da7f49b9c695c3e45498a789634daec26b61e4add7c85613a4a9e53726c39d09de7a163891ecd6eb5809adb64500a840fd86fe81d03 + languageName: node + linkType: hard + +"http-errors@npm:2.0.0": + version: 2.0.0 + resolution: "http-errors@npm:2.0.0" + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + checksum: fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19 + languageName: node + linkType: hard + +"http-errors@npm:~1.6.2": + version: 1.6.3 + resolution: "http-errors@npm:1.6.3" + dependencies: + depd: ~1.1.2 + inherits: 2.0.3 + setprototypeof: 1.1.0 + statuses: ">= 1.4.0 < 2" + checksum: 17ec4046ee974477778bfdd525936c254b872054703ec2caa4d6f099566b8adade636ae6aeeacb39302c5cd6e28fb407ebd937f500f5010d0b6850750414ff78 + languageName: node + linkType: hard + +"http-parser-js@npm:>=0.5.1": + version: 0.5.8 + resolution: "http-parser-js@npm:0.5.8" + checksum: 4ed89f812c44f84c4ae5d43dd3a0c47942b875b63be0ed2ccecbe6b0018af867d806495fc6e12474aff868721163699c49246585bddea4f0ecc6d2b02e19faf1 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^2.1.0": + version: 2.1.0 + resolution: "http-proxy-agent@npm:2.1.0" + dependencies: + agent-base: 4 + debug: 3.1.0 + checksum: 526294de33953bacb21b883d8bbc01a82e1e9f5a721785345dd538b15b62c7a5d4080b729eb3177ad15d842f931f44002431d5cf9b036cc8cea4bfb5ec172228 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^5.0.0": + version: 5.0.0 + resolution: "http-proxy-agent@npm:5.0.0" + dependencies: + "@tootallnate/once": 2 + agent-base: 6 + debug: 4 + checksum: 32a05e413430b2c1e542e5c74b38a9f14865301dd69dff2e53ddb684989440e3d2ce0c4b64d25eb63cf6283e6265ff979a61cf93e3ca3d23047ddfdc8df34a32 + languageName: node + linkType: hard + +"http-proxy-middleware@npm:^2.0.3": + version: 2.0.6 + resolution: "http-proxy-middleware@npm:2.0.6" + dependencies: + "@types/http-proxy": ^1.17.8 + http-proxy: ^1.18.1 + is-glob: ^4.0.1 + is-plain-obj: ^3.0.0 + micromatch: ^4.0.2 + peerDependencies: + "@types/express": ^4.17.13 + peerDependenciesMeta: + "@types/express": + optional: true + checksum: 25a0e550dd1900ee5048a692e0e9b2b6339d06d487a705d90c47e359e9c6561d648cd7862d001d090e651c9efffa1b6e5160fcf1f299b5fa4935f76e9754eb11 + languageName: node + linkType: hard + +"http-proxy@npm:^1.18.1": + version: 1.18.1 + resolution: "http-proxy@npm:1.18.1" + dependencies: + eventemitter3: ^4.0.0 + follow-redirects: ^1.0.0 + requires-port: ^1.0.0 + checksum: 148dfa700a03fb421e383aaaf88ac1d94521dfc34072f6c59770528c65250983c2e4ec996f2f03aa9f3fe46cd1270a593126068319311e3e8d9e610a37533e94 + languageName: node + linkType: hard + +"http-signature@npm:~1.2.0": + version: 1.2.0 + resolution: "http-signature@npm:1.2.0" + dependencies: + assert-plus: ^1.0.0 + jsprim: ^1.2.2 + sshpk: ^1.7.0 + checksum: 582f7af7f354429e1fb19b3bbb9d35520843c69bb30a25b88ca3c5c2c10715f20ae7924e20cffbed220b1d3a726ef4fe8ccc48568d5744db87be9a79887d6733 + languageName: node + linkType: hard + +"http-signature@npm:~1.3.6": + version: 1.3.6 + resolution: "http-signature@npm:1.3.6" + dependencies: + assert-plus: ^1.0.0 + jsprim: ^2.0.2 + sshpk: ^1.14.1 + checksum: f8d15d8c91a5a80805530e2f401a3f83ed55162058651d86ad00df294b159a54e001b5d00e04983f7542a55865aee02d2d83d68c8499137ff2bc142553d8dfc2 + languageName: node + linkType: hard + +"https-browserify@npm:^1.0.0": + version: 1.0.0 + resolution: "https-browserify@npm:1.0.0" + checksum: e17b6943bc24ea9b9a7da5714645d808670af75a425f29baffc3284962626efdc1eb3aa9bbffaa6e64028a6ad98af5b09fabcb454a8f918fb686abfdc9e9b8ae + languageName: node + linkType: hard + +"https-proxy-agent@npm:^2.2.3": + version: 2.2.4 + resolution: "https-proxy-agent@npm:2.2.4" + dependencies: + agent-base: ^4.3.0 + debug: ^3.1.0 + checksum: 4bdde8fcd9ea0adc4a77282de2b4f9e27955e0441425af0f27f0fe01006946b80eaee6749e08e838d350c06ed2ebd5d11347d3beb88c45eacb0667e27276cdad + languageName: node + linkType: hard + +"https-proxy-agent@npm:^5.0.0": + version: 5.0.1 + resolution: "https-proxy-agent@npm:5.0.1" + dependencies: + agent-base: 6 + debug: 4 + checksum: 6dd639f03434003577c62b27cafdb864784ef19b2de430d8ae2a1d45e31c4fd60719e5637b44db1a88a046934307da7089e03d6089ec3ddacc1189d8de8897d1 + languageName: node + linkType: hard + +"human-signals@npm:^1.1.1": + version: 1.1.1 + resolution: "human-signals@npm:1.1.1" + checksum: 18810ed239a7a5e23fb6c32d0fd4be75d7cd337a07ad59b8dbf0794cb0761e6e628349ee04c409e605fe55344716eab5d0a47a62ba2a2d0d367c89a2b4247b1e + languageName: node + linkType: hard + +"human-signals@npm:^2.1.0": + version: 2.1.0 + resolution: "human-signals@npm:2.1.0" + checksum: 695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a + languageName: node + linkType: hard + +"humanize-ms@npm:^1.2.1": + version: 1.2.1 + resolution: "humanize-ms@npm:1.2.1" + dependencies: + ms: ^2.0.0 + checksum: f34a2c20161d02303c2807badec2f3b49cbfbbb409abd4f95a07377ae01cfe6b59e3d15ac609cffcd8f2521f0eb37b7e1091acf65da99aa2a4f1ad63c21e7e7a + languageName: node + linkType: hard + +"iconv-lite@npm:0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: ">= 2.1.2 < 3" + checksum: c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: ">= 2.1.2 < 3.0.0" + checksum: 98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"icss-utils@npm:^4.0.0, icss-utils@npm:^4.1.1": + version: 4.1.1 + resolution: "icss-utils@npm:4.1.1" + dependencies: + postcss: ^7.0.14 + checksum: 22803c243bb097c2290b4e7c20ed14746f3e00e04856f953b751c7e6bb8c81620764bcf98d200a92d167af0884d19143c089d02e2bc609abcdeb86f465328797 + languageName: node + linkType: hard + +"icss-utils@npm:^5.0.0, icss-utils@npm:^5.1.0": + version: 5.1.0 + resolution: "icss-utils@npm:5.1.0" + peerDependencies: + postcss: ^8.1.0 + checksum: 39c92936fabd23169c8611d2b5cc39e39d10b19b0d223352f20a7579f75b39d5f786114a6b8fc62bee8c5fed59ba9e0d38f7219a4db383e324fb3061664b043d + languageName: node + linkType: hard + +"idb@npm:^7.0.1": + version: 7.1.1 + resolution: "idb@npm:7.1.1" + checksum: 72418e4397638797ee2089f97b45fc29f937b830bc0eb4126f4a9889ecf10320ceacf3a177fe5d7ffaf6b4fe38b20bbd210151549bfdc881db8081eed41c870d + languageName: node + linkType: hard + +"ieee754@npm:1.1.13": + version: 1.1.13 + resolution: "ieee754@npm:1.1.13" + checksum: eaf8c87e014282bfb5b13670991a2ed086eaef35ccc3fb713833863f2e7213041b2c29246adbc5f6561d51d53861c3b11f3b82b28fc6fa1352edeff381f056e5 + languageName: node + linkType: hard + +"ieee754@npm:^1.1.13, ieee754@npm:^1.1.4": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb + languageName: node + linkType: hard + +"iferr@npm:^0.1.5": + version: 0.1.5 + resolution: "iferr@npm:0.1.5" + checksum: e0669b1757d0501b43a158321945d1cc1fe56f28a972df2f88a5818f05c8853c7669ba5d6cfbbf9a1a312850699de6e528626df108d559005df7e15d16ee334c + languageName: node + linkType: hard + +"iferr@npm:^1.0.2": + version: 1.0.2 + resolution: "iferr@npm:1.0.2" + checksum: 16b50ddf9244a2edc9b08d2e4a0b2aca94dbee8344b611523fc0367c36b258c0fd1e5568bddc8c4c5a6087f30598bfca6932535a75a549a70d726b9b906b5ec0 + languageName: node + linkType: hard + +"ignore-walk@npm:^3.0.1": + version: 3.0.4 + resolution: "ignore-walk@npm:3.0.4" + dependencies: + minimatch: ^3.0.4 + checksum: 690372b433887796fa3badd25babab7daf60a1882259dcc130ec78eea79745c2416322e10d1a96b367071204471c532647d20b11cd7ab70bd9b49879e461f956 + languageName: node + linkType: hard + +"ignore@npm:^4.0.3": + version: 4.0.6 + resolution: "ignore@npm:4.0.6" + checksum: 836ee7dc7fd9436096e2dba429359dbb9fa0e33d309e2b2d81692f375f6ca82024fc00567f798613d50c6b989e9cd2ad2b065acf116325cde177f02c86b7d4e0 + languageName: node + linkType: hard + +"ignore@npm:^5.2.0": + version: 5.2.4 + resolution: "ignore@npm:5.2.4" + checksum: 7c7cd90edd9fea6e037f9b9da4b01bf0a86b198ce78345f9bbd983929d68ff14830be31111edc5d70c264921f4962404d75b7262b4d9cc3bc12381eccbd03096 + languageName: node + linkType: hard + +"immutable@npm:^3.7.2": + version: 3.8.2 + resolution: "immutable@npm:3.8.2" + checksum: fb6a2999ad3bda9e51741721e42547076dd492635ee4df9241224055fe953ec843583a700088cc4915f23dc326e5084f4e17f1bbd7388c3e872ef5a242e0ac5e + languageName: node + linkType: hard + +"immutable@npm:^4.0.0, immutable@npm:^4.0.0-rc.12": + version: 4.2.2 + resolution: "immutable@npm:4.2.2" + checksum: 230947e1d2408da5083feee3efdfb8f7cd77ca72037f9824ce5185e33f9cb55a53aa741a9a5b49d2488493af0478a0c94140f1fddb7b7f5af4f71af5b16d4be7 + languageName: node + linkType: hard + +"import-fresh@npm:^3.0.0, import-fresh@npm:^3.1.0, import-fresh@npm:^3.2.1": + version: 3.3.0 + resolution: "import-fresh@npm:3.3.0" + dependencies: + parent-module: ^1.0.0 + resolve-from: ^4.0.0 + checksum: 7f882953aa6b740d1f0e384d0547158bc86efbf2eea0f1483b8900a6f65c5a5123c2cf09b0d542cc419d0b98a759ecaeb394237e97ea427f2da221dc3cd80cc3 + languageName: node + linkType: hard + +"import-lazy@npm:^2.1.0": + version: 2.1.0 + resolution: "import-lazy@npm:2.1.0" + checksum: c5e5f507d26ee23c5b2ed64577155810361ac37863b322cae0c17f16b6a8cdd15adf370288384ddd95ef9de05602fb8d87bf76ff835190eb037333c84db8062c + languageName: node + linkType: hard + +"import-local@npm:^3.0.2": + version: 3.1.0 + resolution: "import-local@npm:3.1.0" + dependencies: + pkg-dir: ^4.2.0 + resolve-cwd: ^3.0.0 + bin: + import-local-fixture: fixtures/cli.js + checksum: c67ecea72f775fe8684ca3d057e54bdb2ae28c14bf261d2607c269c18ea0da7b730924c06262eca9aed4b8ab31e31d65bc60b50e7296c85908a56e2f7d41ecd2 + languageName: node + linkType: hard + +"imurmurhash@npm:*, imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"indent-string@npm:^2.1.0": + version: 2.1.0 + resolution: "indent-string@npm:2.1.0" + dependencies: + repeating: ^2.0.0 + checksum: d38e04bbd9b0e1843164d06e9ac1e106ead5a6f7b5714c94ecebc2555b2d3af075b3ddc4d6f92ac87d5319c0935df60d571d3f45f17a6f0ec707be65f26ae924 + languageName: node + linkType: hard + +"indent-string@npm:^4.0.0": + version: 4.0.0 + resolution: "indent-string@npm:4.0.0" + checksum: 1e1904ddb0cb3d6cce7cd09e27a90184908b7a5d5c21b92e232c93579d314f0b83c246ffb035493d0504b1e9147ba2c9b21df0030f48673fba0496ecd698161f + languageName: node + linkType: hard + +"infer-owner@npm:^1.0.3, infer-owner@npm:^1.0.4": + version: 1.0.4 + resolution: "infer-owner@npm:1.0.4" + checksum: a7b241e3149c26e37474e3435779487f42f36883711f198c45794703c7556bc38af224088bd4d1a221a45b8208ae2c2bcf86200383621434d0c099304481c5b9 + languageName: node + linkType: hard + +"inflight@npm:^1.0.4, inflight@npm:~1.0.6": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: ^1.3.0 + wrappy: 1 + checksum: 7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 + languageName: node + linkType: hard + +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.0, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.1, inherits@npm:~2.0.3": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + languageName: node + linkType: hard + +"inherits@npm:2.0.1": + version: 2.0.1 + resolution: "inherits@npm:2.0.1" + checksum: bfc7b37c21a2cddb272adc65b053b1716612d408bb2c9a4e5c32679dc2b08032aadd67880c405be3dff060a62e45b353fc3d9fa79a3067ad7a3deb6a283cc5c6 + languageName: node + linkType: hard + +"inherits@npm:2.0.3": + version: 2.0.3 + resolution: "inherits@npm:2.0.3" + checksum: 6e56402373149ea076a434072671f9982f5fad030c7662be0332122fe6c0fa490acb3cc1010d90b6eff8d640b1167d77674add52dfd1bb85d545cf29e80e73e7 + languageName: node + linkType: hard + +"ini@npm:2.0.0": + version: 2.0.0 + resolution: "ini@npm:2.0.0" + checksum: 2e0c8f386369139029da87819438b20a1ff3fe58372d93fb1a86e9d9344125ace3a806b8ec4eb160a46e64cbc422fe68251869441676af49b7fc441af2389c25 + languageName: node + linkType: hard + +"ini@npm:^1.3.4, ini@npm:^1.3.5, ini@npm:^1.3.8, ini@npm:~1.3.0": + version: 1.3.8 + resolution: "ini@npm:1.3.8" + checksum: ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a + languageName: node + linkType: hard + +"init-package-json@npm:^1.10.3": + version: 1.10.3 + resolution: "init-package-json@npm:1.10.3" + dependencies: + glob: ^7.1.1 + npm-package-arg: ^4.0.0 || ^5.0.0 || ^6.0.0 + promzard: ^0.3.0 + read: ~1.0.1 + read-package-json: 1 || 2 + semver: 2.x || 3.x || 4 || 5 + validate-npm-package-license: ^3.0.1 + validate-npm-package-name: ^3.0.0 + checksum: 19be4bbf936018814227ea2a85a29554ffcf4a043b2d1f57e21e177df8de5e4529daf0f1159ceb229a2f40cbf11a6425c15a7217dfb90f5297d357a6cc027075 + languageName: node + linkType: hard + +"inline-style-parser@npm:0.1.1": + version: 0.1.1 + resolution: "inline-style-parser@npm:0.1.1" + checksum: 08832a533f51a1e17619f2eabf2f5ec5e956d6dcba1896351285c65df022c9420de61d73256e1dca8015a52abf96cc84ddc3b73b898b22de6589d3962b5e501b + languageName: node + linkType: hard + +"internal-slot@npm:^1.0.3, internal-slot@npm:^1.0.4": + version: 1.0.4 + resolution: "internal-slot@npm:1.0.4" + dependencies: + get-intrinsic: ^1.1.3 + has: ^1.0.3 + side-channel: ^1.0.4 + checksum: 37e320dcb66c764d77d84ce2589ce4891ed97461f4cb0c0e0b71e191e00de5a87c7528a9fec2942e1eda5b891b364895cd423a233c58b5197a00e23a70b71924 + languageName: node + linkType: hard + +"internmap@npm:1 - 2": + version: 2.0.3 + resolution: "internmap@npm:2.0.3" + checksum: 8cedd57f07bbc22501516fbfc70447f0c6812871d471096fad9ea603516eacc2137b633633daf432c029712df0baefd793686388ddf5737e3ea15074b877f7ed + languageName: node + linkType: hard + +"interpret@npm:^2.2.0": + version: 2.2.0 + resolution: "interpret@npm:2.2.0" + checksum: c0ef90daec6c4120bb7a226fa09a9511f6b5618aa9c94cf4641472f486948e643bb3b36efbd0136bbffdee876435af9fdf7bbb4622f5a16778eed5397f8a1946 + languageName: node + linkType: hard + +"invariant@npm:^2.2.2, invariant@npm:^2.2.4": + version: 2.2.4 + resolution: "invariant@npm:2.2.4" + dependencies: + loose-envify: ^1.0.0 + checksum: 5af133a917c0bcf65e84e7f23e779e7abc1cd49cb7fdc62d00d1de74b0d8c1b5ee74ac7766099fb3be1b05b26dfc67bab76a17030d2fe7ea2eef867434362dfc + languageName: node + linkType: hard + +"ip-regex@npm:^2.1.0": + version: 2.1.0 + resolution: "ip-regex@npm:2.1.0" + checksum: 3ce2d8307fa0373ca357eba7504e66e73b8121805fd9eba6a343aeb077c64c30659fa876b11ac7a75635b7529d2ce87723f208a5b9d51571513b5c68c0cc1541 + languageName: node + linkType: hard + +"ip@npm:1.1.5": + version: 1.1.5 + resolution: "ip@npm:1.1.5" + checksum: 877e98d676cd8d0ca01fee8282d11b91fb97be7dd9d0b2d6d98e161db2d4277954f5b55db7cfc8556fe6841cb100d13526a74f50ab0d83d6b130fe8445040175 + languageName: node + linkType: hard + +"ip@npm:^2.0.0": + version: 2.0.0 + resolution: "ip@npm:2.0.0" + checksum: 8d186cc5585f57372847ae29b6eba258c68862055e18a75cc4933327232cb5c107f89800ce29715d542eef2c254fbb68b382e780a7414f9ee7caf60b7a473958 + languageName: node + linkType: hard + +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: 0486e775047971d3fdb5fb4f063829bac45af299ae0b82dcf3afa2145338e08290563a2a70f34b732d795ecc8311902e541a8530eeb30d75860a78ff4e94ce2a + languageName: node + linkType: hard + +"ipaddr.js@npm:^2.0.1": + version: 2.0.1 + resolution: "ipaddr.js@npm:2.0.1" + checksum: 0034dfd7a83e82bec6a569549f42c56eb47d051842e10ff0400d97b18f517131834d7c054893a31900cf9d54cf4d974eed97923e5e5965c298d004849f5f0ac9 + languageName: node + linkType: hard + +"is-absolute-url@npm:^3.0.0": + version: 3.0.3 + resolution: "is-absolute-url@npm:3.0.3" + checksum: 04c415974c32e73a83d3a21a9bea18fc4e2c14fbe6bbd64832cf1e67a75ade2af0e900f552f0b8a447f1305f5ffc9d143ccd8d005dbe715d198c359d342b86f0 + languageName: node + linkType: hard + +"is-accessor-descriptor@npm:^0.1.6": + version: 0.1.6 + resolution: "is-accessor-descriptor@npm:0.1.6" + dependencies: + kind-of: ^3.0.2 + checksum: f2c314b314ec6e8a6e559351bff3c7ee9aed7a5e9c6f61dd8cb9e1382c8bfe33dca3f0e0af13daf9ded9e6e66390ff23b4acfb615d7a249009a51506a7b0f151 + languageName: node + linkType: hard + +"is-accessor-descriptor@npm:^1.0.0": + version: 1.0.0 + resolution: "is-accessor-descriptor@npm:1.0.0" + dependencies: + kind-of: ^6.0.0 + checksum: d68edafd8ef133e9003837f3c80f4e5b82b12ab5456c772d1796857671ae83e3a426ed225a28a7e35bceabbce68c1f1ffdabf47e6d53f5a4d6c4558776ad3c20 + languageName: node + linkType: hard + +"is-alphabetical@npm:1.0.4, is-alphabetical@npm:^1.0.0": + version: 1.0.4 + resolution: "is-alphabetical@npm:1.0.4" + checksum: 1505b1de5a1fd74022c05fb21b0e683a8f5229366bac8dc4d34cf6935bcfd104d1125a5e6b083fb778847629f76e5bdac538de5367bdf2b927a1356164e23985 + languageName: node + linkType: hard + +"is-alphanumerical@npm:^1.0.0": + version: 1.0.4 + resolution: "is-alphanumerical@npm:1.0.4" + dependencies: + is-alphabetical: ^1.0.0 + is-decimal: ^1.0.0 + checksum: d623abae7130a7015c6bf33d99151d4e7005572fd170b86568ff4de5ae86ac7096608b87dd4a1d4dbbd497e392b6396930ba76c9297a69455909cebb68005905 + languageName: node + linkType: hard + +"is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.1": + version: 1.1.1 + resolution: "is-arguments@npm:1.1.1" + dependencies: + call-bind: ^1.0.2 + has-tostringtag: ^1.0.0 + checksum: 5ff1f341ee4475350adfc14b2328b38962564b7c2076be2f5bac7bd9b61779efba99b9f844a7b82ba7654adccf8e8eb19d1bb0cc6d1c1a085e498f6793d4328f + languageName: node + linkType: hard + +"is-array-buffer@npm:^3.0.1": + version: 3.0.1 + resolution: "is-array-buffer@npm:3.0.1" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.1.3 + is-typed-array: ^1.1.10 + checksum: a20fc6be40c2efa9465f56274d4ad9c13b84b5f7efe76ec4897609817f079d5e86f3b392c3a78e12d96e0151bcf23389946b0721bd00a09fc9c14905fd7edb1b + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 + languageName: node + linkType: hard + +"is-arrayish@npm:^0.3.1": + version: 0.3.2 + resolution: "is-arrayish@npm:0.3.2" + checksum: f59b43dc1d129edb6f0e282595e56477f98c40278a2acdc8b0a5c57097c9eff8fe55470493df5775478cf32a4dc8eaf6d3a749f07ceee5bc263a78b2434f6a54 + languageName: node + linkType: hard + +"is-bigint@npm:^1.0.1": + version: 1.0.4 + resolution: "is-bigint@npm:1.0.4" + dependencies: + has-bigints: ^1.0.1 + checksum: eb9c88e418a0d195ca545aff2b715c9903d9b0a5033bc5922fec600eb0c3d7b1ee7f882dbf2e0d5a6e694e42391be3683e4368737bd3c4a77f8ac293e7773696 + languageName: node + linkType: hard + +"is-binary-path@npm:^1.0.0": + version: 1.0.1 + resolution: "is-binary-path@npm:1.0.1" + dependencies: + binary-extensions: ^1.0.0 + checksum: 16e456fa3782eaf3d8e28d382b750507e3d54ff6694df8a1b2c6498da321e2ead311de9c42e653d8fb3213de72bac204b5f97e4a110cda8a72f17b1c1b4eb643 + languageName: node + linkType: hard + +"is-binary-path@npm:~2.1.0": + version: 2.1.0 + resolution: "is-binary-path@npm:2.1.0" + dependencies: + binary-extensions: ^2.0.0 + checksum: a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38 + languageName: node + linkType: hard + +"is-boolean-object@npm:^1.1.0": + version: 1.1.2 + resolution: "is-boolean-object@npm:1.1.2" + dependencies: + call-bind: ^1.0.2 + has-tostringtag: ^1.0.0 + checksum: 6090587f8a8a8534c0f816da868bc94f32810f08807aa72fa7e79f7e11c466d281486ffe7a788178809c2aa71fe3e700b167fe80dd96dad68026bfff8ebf39f7 + languageName: node + linkType: hard + +"is-buffer@npm:^1.1.5": + version: 1.1.6 + resolution: "is-buffer@npm:1.1.6" + checksum: ae18aa0b6e113d6c490ad1db5e8df9bdb57758382b313f5a22c9c61084875c6396d50bbf49315f5b1926d142d74dfb8d31b40d993a383e0a158b15fea7a82234 + languageName: node + linkType: hard + +"is-buffer@npm:^2.0.0": + version: 2.0.5 + resolution: "is-buffer@npm:2.0.5" + checksum: e603f6fced83cf94c53399cff3bda1a9f08e391b872b64a73793b0928be3e5f047f2bcece230edb7632eaea2acdbfcb56c23b33d8a20c820023b230f1485679a + languageName: node + linkType: hard + +"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.7": + version: 1.2.7 + resolution: "is-callable@npm:1.2.7" + checksum: ceebaeb9d92e8adee604076971dd6000d38d6afc40bb843ea8e45c5579b57671c3f3b50d7f04869618242c6cee08d1b67806a8cb8edaaaf7c0748b3720d6066f + languageName: node + linkType: hard + +"is-ci@npm:^1.0.10": + version: 1.2.1 + resolution: "is-ci@npm:1.2.1" + dependencies: + ci-info: ^1.5.0 + bin: + is-ci: bin.js + checksum: 56d8e0e404c5ee9eb4cc846b9fbed043bac587633a8b10caad35b1e4b11edccae742037c4bc2196203e5929643bd257a4caac23b65e99b372a54e68d187bacc9 + languageName: node + linkType: hard + +"is-ci@npm:^2.0.0": + version: 2.0.0 + resolution: "is-ci@npm:2.0.0" + dependencies: + ci-info: ^2.0.0 + bin: + is-ci: bin.js + checksum: 17de4e2cd8f993c56c86472dd53dd9e2c7f126d0ee55afe610557046cdd64de0e8feadbad476edc9eeff63b060523b8673d9094ed2ab294b59efb5a66dd05a9a + languageName: node + linkType: hard + +"is-ci@npm:^3.0.0": + version: 3.0.1 + resolution: "is-ci@npm:3.0.1" + dependencies: + ci-info: ^3.2.0 + bin: + is-ci: bin.js + checksum: 0e81caa62f4520d4088a5bef6d6337d773828a88610346c4b1119fb50c842587ed8bef1e5d9a656835a599e7209405b5761ddf2339668f2d0f4e889a92fe6051 + languageName: node + linkType: hard + +"is-cidr@npm:^3.1.1": + version: 3.1.1 + resolution: "is-cidr@npm:3.1.1" + dependencies: + cidr-regex: ^2.0.10 + checksum: 3fe029737ab0bfdbeae64cc67382837d5e3aef7712f98a21656c8deef93bce65189c1cb29c8d0b998a512683595c0aece6086eb1bcd39402d366d0d4bfb1852b + languageName: node + linkType: hard + +"is-core-module@npm:^2.9.0": + version: 2.11.0 + resolution: "is-core-module@npm:2.11.0" + dependencies: + has: ^1.0.3 + checksum: fd8f78ef4e243c295deafa809f89381d89aff5aaf38bb63266b17ee6e34b6a051baa5bdc2365456863336d56af6a59a4c1df1256b4eff7d6b4afac618586b004 + languageName: node + linkType: hard + +"is-data-descriptor@npm:^0.1.4": + version: 0.1.4 + resolution: "is-data-descriptor@npm:0.1.4" + dependencies: + kind-of: ^3.0.2 + checksum: 32fda7e966b2c1f093230d5ef2aad1bb86e43e7280da50961e38ec31dbd8a50570a2911fd45277d321074a0762adc98e8462bb62820462594128857225e90d21 + languageName: node + linkType: hard + +"is-data-descriptor@npm:^1.0.0": + version: 1.0.0 + resolution: "is-data-descriptor@npm:1.0.0" + dependencies: + kind-of: ^6.0.0 + checksum: bed31385d7d1a0dbb2ab3077faf2188acf42609192dca4e320ed7b3dc14a9d70c00658956cdaa2c0402be136c6b56e183973ad81b730fd90ab427fb6fd3608be + languageName: node + linkType: hard + +"is-date-object@npm:^1.0.1, is-date-object@npm:^1.0.5": + version: 1.0.5 + resolution: "is-date-object@npm:1.0.5" + dependencies: + has-tostringtag: ^1.0.0 + checksum: eed21e5dcc619c48ccef804dfc83a739dbb2abee6ca202838ee1bd5f760fe8d8a93444f0d49012ad19bb7c006186e2884a1b92f6e1c056da7fd23d0a9ad5992e + languageName: node + linkType: hard + +"is-decimal@npm:^1.0.0": + version: 1.0.4 + resolution: "is-decimal@npm:1.0.4" + checksum: a4ad53c4c5c4f5a12214e7053b10326711f6a71f0c63ba1314a77bd71df566b778e4ebd29f9fb6815f07a4dc50c3767fb19bd6fc9fa05e601410f1d64ffeac48 + languageName: node + linkType: hard + +"is-descriptor@npm:^0.1.0": + version: 0.1.6 + resolution: "is-descriptor@npm:0.1.6" + dependencies: + is-accessor-descriptor: ^0.1.6 + is-data-descriptor: ^0.1.4 + kind-of: ^5.0.0 + checksum: 6b8f5617b764ef8c6be3d54830184357e6cdedd8e0eddf1b97d0658616ac170bfdbc7c1ad00e0aa9f5b767acdb9d6c63d4df936501784b34936bd0f9acf3b665 + languageName: node + linkType: hard + +"is-descriptor@npm:^1.0.0, is-descriptor@npm:^1.0.2": + version: 1.0.2 + resolution: "is-descriptor@npm:1.0.2" + dependencies: + is-accessor-descriptor: ^1.0.0 + is-data-descriptor: ^1.0.0 + kind-of: ^6.0.2 + checksum: a05169c7a87feb88fc155e3ada469090cfabb5a548a3f794358b511cc47a0871b8b95e7345be4925a22ef3df585c3923b31943b3ad6255ce563a9d97f2e221e0 + languageName: node + linkType: hard + +"is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": + version: 2.2.1 + resolution: "is-docker@npm:2.2.1" + bin: + is-docker: cli.js + checksum: e828365958d155f90c409cdbe958f64051d99e8aedc2c8c4cd7c89dcf35329daed42f7b99346f7828df013e27deb8f721cf9408ba878c76eb9e8290235fbcdcc + languageName: node + linkType: hard + +"is-dom@npm:^1.0.0": + version: 1.1.0 + resolution: "is-dom@npm:1.1.0" + dependencies: + is-object: ^1.0.1 + is-window: ^1.0.2 + checksum: 0645d388bed188e827b4440af7c2f4c454ad6e6fd4c395d46eae404ca8b64f1bb45e3f33f1a60fbc7f59ca10fb0e5351589b49b3d3bac1b3c5aeec70e2e5be07 + languageName: node + linkType: hard + +"is-extendable@npm:^0.1.0, is-extendable@npm:^0.1.1": + version: 0.1.1 + resolution: "is-extendable@npm:0.1.1" + checksum: dd5ca3994a28e1740d1e25192e66eed128e0b2ff161a7ea348e87ae4f616554b486854de423877a2a2c171d5f7cd6e8093b91f54533bc88a59ee1c9838c43879 + languageName: node + linkType: hard + +"is-extendable@npm:^1.0.1": + version: 1.0.1 + resolution: "is-extendable@npm:1.0.1" + dependencies: + is-plain-object: ^2.0.4 + checksum: 1d6678a5be1563db6ecb121331c819c38059703f0179f52aa80c242c223ee9c6b66470286636c0e63d7163e4d905c0a7d82a096e0b5eaeabb51b9f8d0af0d73f + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.0, is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-finite@npm:^1.0.0": + version: 1.1.0 + resolution: "is-finite@npm:1.1.0" + checksum: ca6bc7a0321b339f098e657bd4cbf4bb2410f5a11f1b9adb1a1a9ab72288b64368e8251326cb1f74e985f2779299cec3e1f1e558b68ce7e1e2c9be17b7cfd626 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^1.0.0": + version: 1.0.0 + resolution: "is-fullwidth-code-point@npm:1.0.0" + dependencies: + number-is-nan: ^1.0.0 + checksum: 12acfcf16142f2d431bf6af25d68569d3198e81b9799b4ae41058247aafcc666b0127d64384ea28e67a746372611fcbe9b802f69175287aba466da3eddd5ba0f + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^2.0.0": + version: 2.0.0 + resolution: "is-fullwidth-code-point@npm:2.0.0" + checksum: e58f3e4a601fc0500d8b2677e26e9fe0cd450980e66adb29d85b6addf7969731e38f8e43ed2ec868a09c101a55ac3d8b78902209269f38c5286bc98f5bc1b4d9 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc + languageName: node + linkType: hard + +"is-function@npm:^1.0.2": + version: 1.0.2 + resolution: "is-function@npm:1.0.2" + checksum: c55289042a0e828a773f1245e2652e0c029efacc78ebe03e61787746fda74e2c41006cd908f20b53c36e45f9e75464475a4b2d68b17f4c7b9f8018bcaec42f9e + languageName: node + linkType: hard + +"is-generator-fn@npm:^2.0.0": + version: 2.1.0 + resolution: "is-generator-fn@npm:2.1.0" + checksum: 2957cab387997a466cd0bf5c1b6047bd21ecb32bdcfd8996b15747aa01002c1c88731802f1b3d34ac99f4f6874b626418bd118658cf39380fe5fff32a3af9c4d + languageName: node + linkType: hard + +"is-generator-function@npm:^1.0.7": + version: 1.0.10 + resolution: "is-generator-function@npm:1.0.10" + dependencies: + has-tostringtag: ^1.0.0 + checksum: df03514df01a6098945b5a0cfa1abff715807c8e72f57c49a0686ad54b3b74d394e2d8714e6f709a71eb00c9630d48e73ca1796c1ccc84ac95092c1fecc0d98b + languageName: node + linkType: hard + +"is-glob@npm:^3.0.0, is-glob@npm:^3.1.0": + version: 3.1.0 + resolution: "is-glob@npm:3.1.0" + dependencies: + is-extglob: ^2.1.0 + checksum: ba816a35dcf5285de924a8a4654df7b183a86381d73ea3bbf3df3cc61b3ba61fdddf90ee205709a2235b210ee600ee86e5e8600093cf291a662607fd032e2ff4 + languageName: node + linkType: hard + +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: ^2.1.1 + checksum: 17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-hexadecimal@npm:^1.0.0": + version: 1.0.4 + resolution: "is-hexadecimal@npm:1.0.4" + checksum: ec4c64e5624c0f240922324bc697e166554f09d3ddc7633fc526084502626445d0a871fbd8cae52a9844e83bd0bb414193cc5a66806d7b2867907003fc70c5ea + languageName: node + linkType: hard + +"is-installed-globally@npm:^0.1.0": + version: 0.1.0 + resolution: "is-installed-globally@npm:0.1.0" + dependencies: + global-dirs: ^0.1.0 + is-path-inside: ^1.0.0 + checksum: e5664812205367240bf7229e56410a4d33a83843e32642938dc5d0ba6d53e5125b24ea9aefcf5d35bc3ab8a868469d631624ce59dead1d6ceadf88b19cca6ab7 + languageName: node + linkType: hard + +"is-installed-globally@npm:~0.4.0": + version: 0.4.0 + resolution: "is-installed-globally@npm:0.4.0" + dependencies: + global-dirs: ^3.0.0 + is-path-inside: ^3.0.2 + checksum: f3e6220ee5824b845c9ed0d4b42c24272701f1f9926936e30c0e676254ca5b34d1b92c6205cae11b283776f9529212c0cdabb20ec280a6451677d6493ca9c22d + languageName: node + linkType: hard + +"is-lambda@npm:^1.0.1": + version: 1.0.1 + resolution: "is-lambda@npm:1.0.1" + checksum: 85fee098ae62ba6f1e24cf22678805473c7afd0fb3978a3aa260e354cb7bcb3a5806cf0a98403188465efedec41ab4348e8e4e79305d409601323855b3839d4d + languageName: node + linkType: hard + +"is-map@npm:^2.0.1, is-map@npm:^2.0.2": + version: 2.0.2 + resolution: "is-map@npm:2.0.2" + checksum: 119ff9137a37fd131a72fab3f4ab8c9d6a24b0a1ee26b4eff14dc625900d8675a97785eea5f4174265e2006ed076cc24e89f6e57ebd080a48338d914ec9168a5 + languageName: node + linkType: hard + +"is-module@npm:^1.0.0": + version: 1.0.0 + resolution: "is-module@npm:1.0.0" + checksum: 795a3914bcae7c26a1c23a1e5574c42eac13429625045737bf3e324ce865c0601d61aee7a5afbca1bee8cb300c7d9647e7dc98860c9bdbc3b7fdc51d8ac0bffc + languageName: node + linkType: hard + +"is-negative-zero@npm:^2.0.2": + version: 2.0.2 + resolution: "is-negative-zero@npm:2.0.2" + checksum: eda024c158f70f2017f3415e471b818d314da5ef5be68f801b16314d4a4b6304a74cbed778acf9e2f955bb9c1c5f2935c1be0c7c99e1ad12286f45366217b6a3 + languageName: node + linkType: hard + +"is-npm@npm:^1.0.0": + version: 1.0.0 + resolution: "is-npm@npm:1.0.0" + checksum: 6d9a9cfbb849ad631e3050d1dad140ded91a10f374b57ff6ccb688b4ee8d5fd139e6aa2c5ab4342b8119242c9c4cba041876fb4046fcae52e76eb073db71dfe7 + languageName: node + linkType: hard + +"is-number-object@npm:^1.0.4": + version: 1.0.7 + resolution: "is-number-object@npm:1.0.7" + dependencies: + has-tostringtag: ^1.0.0 + checksum: aad266da1e530f1804a2b7bd2e874b4869f71c98590b3964f9d06cc9869b18f8d1f4778f838ecd2a11011bce20aeecb53cb269ba916209b79c24580416b74b1b + languageName: node + linkType: hard + +"is-number@npm:^3.0.0": + version: 3.0.0 + resolution: "is-number@npm:3.0.0" + dependencies: + kind-of: ^3.0.2 + checksum: e639c54640b7f029623df24d3d103901e322c0c25ea5bde97cd723c2d0d4c05857a8364ab5c58d963089dbed6bf1d0ffe975cb6aef917e2ad0ccbca653d31b4f + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 + languageName: node + linkType: hard + +"is-obj@npm:^1.0.0, is-obj@npm:^1.0.1": + version: 1.0.1 + resolution: "is-obj@npm:1.0.1" + checksum: 5003acba0af7aa47dfe0760e545a89bbac89af37c12092c3efadc755372cdaec034f130e7a3653a59eb3c1843cfc72ca71eaf1a6c3bafe5a0bab3611a47f9945 + languageName: node + linkType: hard + +"is-object@npm:^1.0.1": + version: 1.0.2 + resolution: "is-object@npm:1.0.2" + checksum: 9cfb80c3a850f453d4a77297e0556bc2040ac6bea5b6e418aee208654938b36bab768169bef3945ccfac7a9bb460edd8034e7c6d8973bcf147d7571e1b53e764 + languageName: node + linkType: hard + +"is-path-inside@npm:^1.0.0": + version: 1.0.1 + resolution: "is-path-inside@npm:1.0.1" + dependencies: + path-is-inside: ^1.0.1 + checksum: 093ab1324e33a95c2d057e1450e1936ee7a3ed25b78c8dc42f576f3dc3489dd8788d431ea2969bb0e081f005de1571792ea99cf7b1b69ab2dd4ca477ae7a8e51 + languageName: node + linkType: hard + +"is-path-inside@npm:^3.0.2, is-path-inside@npm:^3.0.3": + version: 3.0.3 + resolution: "is-path-inside@npm:3.0.3" + checksum: cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05 + languageName: node + linkType: hard + +"is-plain-obj@npm:^2.0.0": + version: 2.1.0 + resolution: "is-plain-obj@npm:2.1.0" + checksum: e5c9814cdaa627a9ad0a0964ded0e0491bfd9ace405c49a5d63c88b30a162f1512c069d5b80997893c4d0181eadc3fed02b4ab4b81059aba5620bfcdfdeb9c53 + languageName: node + linkType: hard + +"is-plain-obj@npm:^3.0.0": + version: 3.0.0 + resolution: "is-plain-obj@npm:3.0.0" + checksum: 8e6483bfb051d42ec9c704c0ede051a821c6b6f9a6c7a3e3b55aa855e00981b0580c8f3b1f5e2e62649b39179b1abfee35d6f8086d999bfaa32c1908d29b07bc + languageName: node + linkType: hard + +"is-plain-object@npm:5.0.0": + version: 5.0.0 + resolution: "is-plain-object@npm:5.0.0" + checksum: 893e42bad832aae3511c71fd61c0bf61aa3a6d853061c62a307261842727d0d25f761ce9379f7ba7226d6179db2a3157efa918e7fe26360f3bf0842d9f28942c + languageName: node + linkType: hard + +"is-plain-object@npm:^2.0.3, is-plain-object@npm:^2.0.4": + version: 2.0.4 + resolution: "is-plain-object@npm:2.0.4" + dependencies: + isobject: ^3.0.1 + checksum: f050fdd5203d9c81e8c4df1b3ff461c4bc64e8b5ca383bcdde46131361d0a678e80bcf00b5257646f6c636197629644d53bd8e2375aea633de09a82d57e942f4 + languageName: node + linkType: hard + +"is-redirect@npm:^1.0.0": + version: 1.0.0 + resolution: "is-redirect@npm:1.0.0" + checksum: 4fb24eaa61548d276499ec5e2f7efbc4ed823b68c7ee3bdfbf29d0f6c45d19c07f417bf3dd86110285c28a35481b46a9996921739b7b84bb8ba5216f250d40de + languageName: node + linkType: hard + +"is-regex@npm:^1.1.2, is-regex@npm:^1.1.4": + version: 1.1.4 + resolution: "is-regex@npm:1.1.4" + dependencies: + call-bind: ^1.0.2 + has-tostringtag: ^1.0.0 + checksum: bb72aae604a69eafd4a82a93002058c416ace8cde95873589a97fc5dac96a6c6c78a9977d487b7b95426a8f5073969124dd228f043f9f604f041f32fcc465fc1 + languageName: node + linkType: hard + +"is-regexp@npm:^1.0.0": + version: 1.0.0 + resolution: "is-regexp@npm:1.0.0" + checksum: 34cacda1901e00f6e44879378f1d2fa96320ea956c1bec27713130aaf1d44f6e7bd963eed28945bfe37e600cb27df1cf5207302680dad8bdd27b9baff8ecf611 + languageName: node + linkType: hard + +"is-retry-allowed@npm:^1.0.0": + version: 1.2.0 + resolution: "is-retry-allowed@npm:1.2.0" + checksum: a80f14e1e11c27a58f268f2927b883b635703e23a853cb7b8436e3456bf2ea3efd5082a4e920093eec7bd372c1ce6ea7cea78a9376929c211039d0cc4a393a44 + languageName: node + linkType: hard + +"is-set@npm:^2.0.1, is-set@npm:^2.0.2": + version: 2.0.2 + resolution: "is-set@npm:2.0.2" + checksum: 5f8bd1880df8c0004ce694e315e6e1e47a3452014be792880bb274a3b2cdb952fdb60789636ca6e084c7947ca8b7ae03ccaf54c93a7fcfed228af810559e5432 + languageName: node + linkType: hard + +"is-shared-array-buffer@npm:^1.0.2": + version: 1.0.2 + resolution: "is-shared-array-buffer@npm:1.0.2" + dependencies: + call-bind: ^1.0.2 + checksum: cfeee6f171f1b13e6cbc6f3b6cc44e192b93df39f3fcb31aa66ffb1d2df3b91e05664311659f9701baba62f5e98c83b0673c628e7adc30f55071c4874fcdccec + languageName: node + linkType: hard + +"is-stream@npm:^1.0.0, is-stream@npm:^1.1.0": + version: 1.1.0 + resolution: "is-stream@npm:1.1.0" + checksum: b8ae7971e78d2e8488d15f804229c6eed7ed36a28f8807a1815938771f4adff0e705218b7dab968270433f67103e4fef98062a0beea55d64835f705ee72c7002 + languageName: node + linkType: hard + +"is-stream@npm:^2.0.0": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 + languageName: node + linkType: hard + +"is-string@npm:^1.0.5, is-string@npm:^1.0.7": + version: 1.0.7 + resolution: "is-string@npm:1.0.7" + dependencies: + has-tostringtag: ^1.0.0 + checksum: 905f805cbc6eedfa678aaa103ab7f626aac9ebbdc8737abb5243acaa61d9820f8edc5819106b8fcd1839e33db21de9f0116ae20de380c8382d16dc2a601921f6 + languageName: node + linkType: hard + +"is-symbol@npm:^1.0.2, is-symbol@npm:^1.0.3": + version: 1.0.4 + resolution: "is-symbol@npm:1.0.4" + dependencies: + has-symbols: ^1.0.2 + checksum: 9381dd015f7c8906154dbcbf93fad769de16b4b961edc94f88d26eb8c555935caa23af88bda0c93a18e65560f6d7cca0fd5a3f8a8e1df6f1abbb9bead4502ef7 + languageName: node + linkType: hard + +"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.3, is-typed-array@npm:^1.1.9": + version: 1.1.10 + resolution: "is-typed-array@npm:1.1.10" + 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 + checksum: b71268a2e5f493f2b95af4cbfe7a65254a822f07d57f20c18f084347cd45f11810915fe37d7a6831fe4b81def24621a042fd1169ec558c50f830b591bc8c1f66 + languageName: node + linkType: hard + +"is-typedarray@npm:^1.0.0, is-typedarray@npm:~1.0.0": + version: 1.0.0 + resolution: "is-typedarray@npm:1.0.0" + checksum: 4c096275ba041a17a13cca33ac21c16bc4fd2d7d7eb94525e7cd2c2f2c1a3ab956e37622290642501ff4310601e413b675cf399ad6db49855527d2163b3eeeec + languageName: node + linkType: hard + +"is-unicode-supported@npm:^0.1.0": + version: 0.1.0 + resolution: "is-unicode-supported@npm:0.1.0" + checksum: 00cbe3455c3756be68d2542c416cab888aebd5012781d6819749fefb15162ff23e38501fe681b3d751c73e8ff561ac09a5293eba6f58fdf0178462ce6dcb3453 + languageName: node + linkType: hard + +"is-utf8@npm:^0.2.0": + version: 0.2.1 + resolution: "is-utf8@npm:0.2.1" + checksum: 3ed45e5b4ddfa04ed7e32c63d29c61b980ecd6df74698f45978b8c17a54034943bcbffb6ae243202e799682a66f90fef526f465dd39438745e9fe70794c1ef09 + languageName: node + linkType: hard + +"is-weakmap@npm:^2.0.1": + version: 2.0.1 + resolution: "is-weakmap@npm:2.0.1" + checksum: 9c9fec9efa7bf5030a4a927f33fff2a6976b93646259f92b517d3646c073cc5b98283a162ce75c412b060a46de07032444b530f0a4c9b6e012ef8f1741c3a987 + languageName: node + linkType: hard + +"is-weakref@npm:^1.0.2": + version: 1.0.2 + resolution: "is-weakref@npm:1.0.2" + dependencies: + call-bind: ^1.0.2 + checksum: 1545c5d172cb690c392f2136c23eec07d8d78a7f57d0e41f10078aa4f5daf5d7f57b6513a67514ab4f073275ad00c9822fc8935e00229d0a2089e1c02685d4b1 + languageName: node + linkType: hard + +"is-weakset@npm:^2.0.1": + version: 2.0.2 + resolution: "is-weakset@npm:2.0.2" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.1.1 + checksum: ef5136bd446ae4603229b897f73efd0720c6ab3ec6cc05c8d5c4b51aa9f95164713c4cad0a22ff1fedf04865ff86cae4648bc1d5eead4b6388e1150525af1cc1 + languageName: node + linkType: hard + +"is-whitespace-character@npm:^1.0.0": + version: 1.0.4 + resolution: "is-whitespace-character@npm:1.0.4" + checksum: 20f02cf42eafb44ff1706a04338dc45095cd691ae6984adb9a211b6b6df8d01e91722129ce55555e4c7c7b0b7d48e217553767f22eb7ec019b9f8dd3bc12cdfb + languageName: node + linkType: hard + +"is-window@npm:^1.0.2": + version: 1.0.2 + resolution: "is-window@npm:1.0.2" + checksum: f954f21c9fce64e6c72f8a908c3aaefa8fd6d1ef819acdfa1be007de70e5424bd2ac774950b38b523fd8ff4b581899efc1156cc6b0505040072e8ff25e57ec18 + languageName: node + linkType: hard + +"is-windows@npm:^1.0.2": + version: 1.0.2 + resolution: "is-windows@npm:1.0.2" + checksum: b32f418ab3385604a66f1b7a3ce39d25e8881dee0bd30816dc8344ef6ff9df473a732bcc1ec4e84fe99b2f229ae474f7133e8e93f9241686cfcf7eebe53ba7a5 + languageName: node + linkType: hard + +"is-word-character@npm:^1.0.0": + version: 1.0.4 + resolution: "is-word-character@npm:1.0.4" + checksum: 2247844064532986dc70869d961dccd1366932a147b52d4ec7f567f87edf7f9855a27b75f66b781db3b3175bbe05a76acbc6392a1a5c64c4c99fe3459dae33bd + languageName: node + linkType: hard + +"is-wsl@npm:^1.1.0": + version: 1.1.0 + resolution: "is-wsl@npm:1.1.0" + checksum: 7ad0012f21092d6f586c7faad84755a8ef0da9b9ec295e4dc82313cce4e1a93a3da3c217265016461f9b141503fe55fa6eb1fd5457d3f05e8d1bdbb48e50c13a + languageName: node + linkType: hard + +"is-wsl@npm:^2.1.1, is-wsl@npm:^2.2.0": + version: 2.2.0 + resolution: "is-wsl@npm:2.2.0" + dependencies: + is-docker: ^2.0.0 + checksum: a6fa2d370d21be487c0165c7a440d567274fbba1a817f2f0bfa41cc5e3af25041d84267baa22df66696956038a43973e72fca117918c91431920bdef490fa25e + languageName: node + linkType: hard + +"isarray@npm:0.0.1": + version: 0.0.1 + resolution: "isarray@npm:0.0.1" + checksum: ed1e62da617f71fe348907c71743b5ed550448b455f8d269f89a7c7ddb8ae6e962de3dab6a74a237b06f5eb7f6ece7a45ada8ce96d87fe972926530f91ae3311 + languageName: node + linkType: hard + +"isarray@npm:1.0.0, isarray@npm:^1.0.0, isarray@npm:~1.0.0": + version: 1.0.0 + resolution: "isarray@npm:1.0.0" + checksum: 18b5be6669be53425f0b84098732670ed4e727e3af33bc7f948aac01782110eb9a18b3b329c5323bcdd3acdaae547ee077d3951317e7f133bff7105264b3003d + languageName: node + linkType: hard + +"isarray@npm:^2.0.5": + version: 2.0.5 + resolution: "isarray@npm:2.0.5" + checksum: 4199f14a7a13da2177c66c31080008b7124331956f47bca57dd0b6ea9f11687aa25e565a2c7a2b519bc86988d10398e3049a1f5df13c9f6b7664154690ae79fd + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isobject@npm:^2.0.0": + version: 2.1.0 + resolution: "isobject@npm:2.1.0" + dependencies: + isarray: 1.0.0 + checksum: c4cafec73b3b2ee11be75dff8dafd283b5728235ac099b07d7873d5182553a707768e208327bbc12931b9422d8822280bf88d894a0024ff5857b3efefb480e7b + languageName: node + linkType: hard + +"isobject@npm:^3.0.0, isobject@npm:^3.0.1": + version: 3.0.1 + resolution: "isobject@npm:3.0.1" + checksum: 03344f5064a82f099a0cd1a8a407f4c0d20b7b8485e8e816c39f249e9416b06c322e8dec5b842b6bb8a06de0af9cb48e7bc1b5352f0fadc2f0abac033db3d4db + languageName: node + linkType: hard + +"isobject@npm:^4.0.0": + version: 4.0.0 + resolution: "isobject@npm:4.0.0" + checksum: 8efcda03af98cbb193737e30ffb77c71ca4e97dbf919f7aacec44b7410a166fa4e9fd71232bf5b00a919f98b5747ae359dbb5a5bc4195c93f6291423b9707df6 + languageName: node + linkType: hard + +"isomorphic-unfetch@npm:^3.1.0": + version: 3.1.0 + resolution: "isomorphic-unfetch@npm:3.1.0" + dependencies: + node-fetch: ^2.6.1 + unfetch: ^4.2.0 + checksum: d3b61fca06304db692b7f76bdfd3a00f410e42cfa7403c3b250546bf71589d18cf2f355922f57198e4cc4a9872d3647b20397a5c3edf1a347c90d57c83cf2a89 + languageName: node + linkType: hard + +"isstream@npm:~0.1.2": + version: 0.1.2 + resolution: "isstream@npm:0.1.2" + checksum: a6686a878735ca0a48e0d674dd6d8ad31aedfaf70f07920da16ceadc7577b46d67179a60b313f2e6860cb097a2c2eb3cbd0b89e921ae89199a59a17c3273d66f + languageName: node + linkType: hard + +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": + version: 3.2.0 + resolution: "istanbul-lib-coverage@npm:3.2.0" + checksum: 10ecb00a50cac2f506af8231ce523ffa1ac1310db0435c8ffaabb50c1d72539906583aa13c84f8835dc103998b9989edc3c1de989d2e2a96a91a9ba44e5db6b9 + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^5.0.4, istanbul-lib-instrument@npm:^5.1.0": + version: 5.2.1 + resolution: "istanbul-lib-instrument@npm:5.2.1" + 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 + checksum: 8a1bdf3e377dcc0d33ec32fe2b6ecacdb1e4358fd0eb923d4326bb11c67622c0ceb99600a680f3dad5d29c66fc1991306081e339b4d43d0b8a2ab2e1d910a6ee + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0": + version: 3.0.0 + resolution: "istanbul-lib-report@npm:3.0.0" + dependencies: + istanbul-lib-coverage: ^3.0.0 + make-dir: ^3.0.0 + supports-color: ^7.1.0 + checksum: 81b0d5187c7603ed71bdea0b701a7329f8146549ca19aa26d91b4a163aea756f9d55c1a6dc1dcd087e24dfcb99baa69e266a68644fbfd5dc98107d6f6f5948d2 + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^4.0.0": + version: 4.0.1 + resolution: "istanbul-lib-source-maps@npm:4.0.1" + dependencies: + debug: ^4.1.1 + istanbul-lib-coverage: ^3.0.0 + source-map: ^0.6.1 + checksum: 19e4cc405016f2c906dff271a76715b3e881fa9faeb3f09a86cb99b8512b3a5ed19cadfe0b54c17ca0e54c1142c9c6de9330d65506e35873994e06634eebeb66 + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.1.3, istanbul-reports@npm:^3.1.4": + version: 3.1.5 + resolution: "istanbul-reports@npm:3.1.5" + dependencies: + html-escaper: ^2.0.0 + istanbul-lib-report: ^3.0.0 + checksum: 3a147171bffdbd3034856410b6ec81637871d17d10986513328fec23df6b666f66bd08ea480f5b7a5b9f7e8abc30f3e3c2e7d1b661fc57cdc479aaaa677b1011 + languageName: node + linkType: hard + +"iterate-iterator@npm:^1.0.1": + version: 1.0.2 + resolution: "iterate-iterator@npm:1.0.2" + checksum: 74609b01a3ebc025601aa68ef40731b05d5e45c9fd4ecf233a14a34f2b3481e6974e1dcff390e87155a0648f056c186336bb4c70df2fdefeab08a9878b2eb1c2 + languageName: node + linkType: hard + +"iterate-value@npm:^1.0.2": + version: 1.0.2 + resolution: "iterate-value@npm:1.0.2" + dependencies: + es-get-iterator: ^1.0.2 + iterate-iterator: ^1.0.1 + checksum: 77d32a5ac84877da2133689ff5e3983aa8214bace7faee3c746bf79d4524cc3fb8c0344a20d3699be20a15f0959ecd582d53a05b97f5d04c306bcd426800a650 + languageName: node + linkType: hard + +"iterm2-version@npm:^4.1.0": + version: 4.2.0 + resolution: "iterm2-version@npm:4.2.0" + dependencies: + app-path: ^3.2.0 + plist: ^3.0.1 + checksum: 38043a4ec36e44f865db32d1043c2e24bf64ff8bede1f737237de6ac0a53760fad9344d645868098a481ceae784f83a6692fb4d42bd0a3e5b83617c22ab70d8d + languageName: node + linkType: hard + +"jake@npm:^10.8.5": + version: 10.8.5 + resolution: "jake@npm:10.8.5" + dependencies: + async: ^3.2.3 + chalk: ^4.0.2 + filelist: ^1.0.1 + minimatch: ^3.0.4 + bin: + jake: ./bin/cli.js + checksum: fc1f59c291b1c5bafad8ccde0e5d97f5f22ceb857f204f15634011e642b9cdf652dae2943b5ffe5ab037fe2f77b263653911ed2a408b2887a6dee31873e5c3d8 + languageName: node + linkType: hard + +"jest-changed-files@npm:^29.2.0": + version: 29.2.0 + resolution: "jest-changed-files@npm:29.2.0" + dependencies: + execa: ^5.0.0 + p-limit: ^3.1.0 + checksum: 2d3ed094ff26e6c3d5151d3bc6314c352c96f2070a3c92278711a214eeae2a6f931d619843f9e3a796c066a2ad1a7cc22f30f9e21c8bbde2fbaddbd10a64f8b8 + languageName: node + linkType: hard + +"jest-circus@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-circus@npm:29.3.1" + dependencies: + "@jest/environment": ^29.3.1 + "@jest/expect": ^29.3.1 + "@jest/test-result": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/node": "*" + chalk: ^4.0.0 + co: ^4.6.0 + dedent: ^0.7.0 + is-generator-fn: ^2.0.0 + jest-each: ^29.3.1 + jest-matcher-utils: ^29.3.1 + jest-message-util: ^29.3.1 + jest-runtime: ^29.3.1 + jest-snapshot: ^29.3.1 + jest-util: ^29.3.1 + p-limit: ^3.1.0 + pretty-format: ^29.3.1 + slash: ^3.0.0 + stack-utils: ^2.0.3 + checksum: 01f706a16b2a89fc1af2df984c4bae5286a2540f0ddc3a252c165825161a3c234c11d85d73856693ac3a5789d199fe6574899323cad1b0905a23e4a8a1af5a56 + languageName: node + linkType: hard + +"jest-cli@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-cli@npm:29.3.1" + dependencies: + "@jest/core": ^29.3.1 + "@jest/test-result": ^29.3.1 + "@jest/types": ^29.3.1 + chalk: ^4.0.0 + exit: ^0.1.2 + graceful-fs: ^4.2.9 + import-local: ^3.0.2 + jest-config: ^29.3.1 + jest-util: ^29.3.1 + jest-validate: ^29.3.1 + prompts: ^2.0.1 + yargs: ^17.3.1 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: df1beb004be8913bcabe536b3071ec6568524b8d94ba480423ad4c89c03660163a54615c0e6e557a7894b24ea2c68bf556ee1af06f2cbf5e4e0c9ac6ae223b90 + languageName: node + linkType: hard + +"jest-config@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-config@npm:29.3.1" + dependencies: + "@babel/core": ^7.11.6 + "@jest/test-sequencer": ^29.3.1 + "@jest/types": ^29.3.1 + babel-jest: ^29.3.1 + chalk: ^4.0.0 + ci-info: ^3.2.0 + deepmerge: ^4.2.2 + glob: ^7.1.3 + graceful-fs: ^4.2.9 + jest-circus: ^29.3.1 + jest-environment-node: ^29.3.1 + jest-get-type: ^29.2.0 + jest-regex-util: ^29.2.0 + jest-resolve: ^29.3.1 + jest-runner: ^29.3.1 + jest-util: ^29.3.1 + jest-validate: ^29.3.1 + micromatch: ^4.0.4 + parse-json: ^5.2.0 + pretty-format: ^29.3.1 + slash: ^3.0.0 + strip-json-comments: ^3.1.1 + peerDependencies: + "@types/node": "*" + ts-node: ">=9.0.0" + peerDependenciesMeta: + "@types/node": + optional: true + ts-node: + optional: true + checksum: 395f9057cc93e59ea433901c1edc9660eb18e3543c214f4064e2be6e193f8e3db452995d91267fc21b6e53ab85b4f6fbb94db31c5543ec5db1c6dc44b157f950 + languageName: node + linkType: hard + +"jest-diff@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-diff@npm:29.3.1" + dependencies: + chalk: ^4.0.0 + diff-sequences: ^29.3.1 + jest-get-type: ^29.2.0 + pretty-format: ^29.3.1 + checksum: 6bbe1fc91f9e7351e995f725029d984392fd0fe2374e64953c2b38a8d857f93b845fcf5d9421cccf2be077f651374f6b7ca6c5970687b2b6521452c15c1e3286 + languageName: node + linkType: hard + +"jest-docblock@npm:^29.2.0": + version: 29.2.0 + resolution: "jest-docblock@npm:29.2.0" + dependencies: + detect-newline: ^3.0.0 + checksum: 2e5ac2ce86475afa4b0f15fa388206f187d936d0612e45ad29e0aa18158ee951c1d83b1f1ec7deee535f7afa80dbbc875d79398879703045a262dbf8344280de + languageName: node + linkType: hard + +"jest-each@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-each@npm:29.3.1" + dependencies: + "@jest/types": ^29.3.1 + chalk: ^4.0.0 + jest-get-type: ^29.2.0 + jest-util: ^29.3.1 + pretty-format: ^29.3.1 + checksum: c40262f290cf396406289d1a3884a02048b155e3d55da061f0b5d32b385cc6030799c88998733392335dd69c78da8aa6bed82399f5e8db642f5ef9e370425fc3 + languageName: node + linkType: hard + +"jest-environment-node@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-environment-node@npm:29.3.1" + dependencies: + "@jest/environment": ^29.3.1 + "@jest/fake-timers": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/node": "*" + jest-mock: ^29.3.1 + jest-util: ^29.3.1 + checksum: b74e1ed332eaab4a15384ddfceb340867aa98cc2c4387d8001fe13087a3586e91f89c79f830f3f4a72547c08283b030cc4267d1a91456ab69b9e29602773b280 + languageName: node + linkType: hard + +"jest-get-type@npm:^29.2.0": + version: 29.2.0 + resolution: "jest-get-type@npm:29.2.0" + checksum: 6466631b344ff8e9d3fa6a47bafa4fc2baf42ec8b4f5de5c99fa1edda128af869da319af4cf770662776e4ed7a3e44656eba690cf5b662c664605b0331762bc7 + languageName: node + linkType: hard + +"jest-haste-map@npm:^26.6.2": + version: 26.6.2 + resolution: "jest-haste-map@npm:26.6.2" + dependencies: + "@jest/types": ^26.6.2 + "@types/graceful-fs": ^4.1.2 + "@types/node": "*" + anymatch: ^3.0.3 + fb-watchman: ^2.0.0 + fsevents: ^2.1.2 + graceful-fs: ^4.2.4 + jest-regex-util: ^26.0.0 + jest-serializer: ^26.6.2 + jest-util: ^26.6.2 + jest-worker: ^26.6.2 + micromatch: ^4.0.2 + sane: ^4.0.3 + walker: ^1.0.7 + dependenciesMeta: + fsevents: + optional: true + checksum: 85a40d8ecf4bfb659613f107c963c7366cdf6dcceb0ca73dc8ca09fbe0e2a63b976940f573db6260c43011993cb804275f447f268c3bc4b680c08baed300701d + languageName: node + linkType: hard + +"jest-haste-map@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-haste-map@npm:29.3.1" + dependencies: + "@jest/types": ^29.3.1 + "@types/graceful-fs": ^4.1.3 + "@types/node": "*" + anymatch: ^3.0.3 + fb-watchman: ^2.0.0 + fsevents: ^2.3.2 + graceful-fs: ^4.2.9 + jest-regex-util: ^29.2.0 + jest-util: ^29.3.1 + jest-worker: ^29.3.1 + micromatch: ^4.0.4 + walker: ^1.0.8 + dependenciesMeta: + fsevents: + optional: true + checksum: 9f1309a727cf91ac15e329ce8f266c289bc9df4ccf1577f2187cd90ef6dbe4d7e2872432e5a8054a500340458e8c0a03f12a50e415cf305720dbd1e229531e08 + languageName: node + linkType: hard + +"jest-image-snapshot@npm:4.2.0": + version: 4.2.0 + resolution: "jest-image-snapshot@npm:4.2.0" + dependencies: + chalk: ^1.1.3 + get-stdin: ^5.0.1 + glur: ^1.1.2 + lodash: ^4.17.4 + mkdirp: ^0.5.1 + pixelmatch: ^5.1.0 + pngjs: ^3.4.0 + rimraf: ^2.6.2 + ssim.js: ^3.1.1 + peerDependencies: + jest: ">=20 <=26" + checksum: 4b9e9d1f9db449971723c83a4e1268a27fdefe771d26abd951f7f4eb80c046172629ad4a0a7f2bf86c7fff081c1985cc0451afa0e0e15062b5b140c57ebbf437 + languageName: node + linkType: hard + +"jest-leak-detector@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-leak-detector@npm:29.3.1" + dependencies: + jest-get-type: ^29.2.0 + pretty-format: ^29.3.1 + checksum: d9d6e107a49cf44d748936a60a3190175b76e5d9aabd9c40c0196dafa8941fd6cb3ef60e57a85f65f5ce466d28b14ad7fceea83f81ec1e8769ae9cde5ceb901a + languageName: node + linkType: hard + +"jest-matcher-utils@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-matcher-utils@npm:29.3.1" + dependencies: + chalk: ^4.0.0 + jest-diff: ^29.3.1 + jest-get-type: ^29.2.0 + pretty-format: ^29.3.1 + checksum: 4efdcc2fa33a403285e26521f795c9c7ad537a30e5b4183a8d97fd9f05251107ef2ef1397dbb420a2517fa91606655cb1103a0c60a52b1f003d928dbea3963e3 + languageName: node + linkType: hard + +"jest-message-util@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-message-util@npm:29.3.1" + dependencies: + "@babel/code-frame": ^7.12.13 + "@jest/types": ^29.3.1 + "@types/stack-utils": ^2.0.0 + chalk: ^4.0.0 + graceful-fs: ^4.2.9 + micromatch: ^4.0.4 + pretty-format: ^29.3.1 + slash: ^3.0.0 + stack-utils: ^2.0.3 + checksum: 09291af22383d14a6ac0e4faea6382e07e38a89b67985ac48fd4604037572c847021d471f11f1866fd696875218996740a10a176acc26fbe072e4394d52129e0 + languageName: node + linkType: hard + +"jest-mock@npm:^27.0.6": + version: 27.5.1 + resolution: "jest-mock@npm:27.5.1" + dependencies: + "@jest/types": ^27.5.1 + "@types/node": "*" + checksum: 6ad58454b37ee3f726930b07efbf40a7c79d2d2d9c7b226708b4b550bc0904de93bcacf714105d11952a5c0bc855e5d59145c8c9dbbb4e69b46e7367abf53b52 + languageName: node + linkType: hard + +"jest-mock@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-mock@npm:29.3.1" + dependencies: + "@jest/types": ^29.3.1 + "@types/node": "*" + jest-util: ^29.3.1 + checksum: d36a54364721e80a95f9af43358e25513d6f052a53b6625bd5238948d9d297ea3c180893cedbcc9e68c1e7b2e9326ac6ae08195b9c0578692a278323fe493ae4 + languageName: node + linkType: hard + +"jest-pnp-resolver@npm:^1.2.2": + version: 1.2.3 + resolution: "jest-pnp-resolver@npm:1.2.3" + peerDependencies: + jest-resolve: "*" + peerDependenciesMeta: + jest-resolve: + optional: true + checksum: 86eec0c78449a2de733a6d3e316d49461af6a858070e113c97f75fb742a48c2396ea94150cbca44159ffd4a959f743a47a8b37a792ef6fdad2cf0a5cba973fac + languageName: node + linkType: hard + +"jest-regex-util@npm:^26.0.0": + version: 26.0.0 + resolution: "jest-regex-util@npm:26.0.0" + checksum: 988675764a08945b90f48e6f5a8640b0d9885a977f100a168061d10037d53808a6cdb7dc8cb6fe9b1332f0523b42bf3edbb6d2cc6c7f7ba582d05d432efb3e60 + languageName: node + linkType: hard + +"jest-regex-util@npm:^29.2.0": + version: 29.2.0 + resolution: "jest-regex-util@npm:29.2.0" + checksum: 7e500f4f6fcbf5f46d284e83c9ade2e47707ee51e90d82e531fe10de05924a13dff89280d7184f1050129a070ce6db6336b41e2fd3bd07b8c32a91e8235f9840 + languageName: node + linkType: hard + +"jest-resolve-dependencies@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-resolve-dependencies@npm:29.3.1" + dependencies: + jest-regex-util: ^29.2.0 + jest-snapshot: ^29.3.1 + checksum: 93c23ac52ec60bc7c5f672acc19dbed113bf152f36f416e59c6f5cf94266349aeb963657dfd7bddcf29eee4c7151aa8a8b4856e47ee07afda56c02fdea0e19cf + languageName: node + linkType: hard + +"jest-resolve@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-resolve@npm:29.3.1" + dependencies: + chalk: ^4.0.0 + graceful-fs: ^4.2.9 + jest-haste-map: ^29.3.1 + jest-pnp-resolver: ^1.2.2 + jest-util: ^29.3.1 + jest-validate: ^29.3.1 + resolve: ^1.20.0 + resolve.exports: ^1.1.0 + slash: ^3.0.0 + checksum: 61aca0adae54fa62262f31e98ee5c8be19a9704d3c5684604a8308fa724b4dca54dd82be2d68307d7e95153dbdb459c19bab57fdc8a26da96d966823e97d4d1e + languageName: node + linkType: hard + +"jest-runner@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-runner@npm:29.3.1" + dependencies: + "@jest/console": ^29.3.1 + "@jest/environment": ^29.3.1 + "@jest/test-result": ^29.3.1 + "@jest/transform": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/node": "*" + chalk: ^4.0.0 + emittery: ^0.13.1 + graceful-fs: ^4.2.9 + jest-docblock: ^29.2.0 + jest-environment-node: ^29.3.1 + jest-haste-map: ^29.3.1 + jest-leak-detector: ^29.3.1 + jest-message-util: ^29.3.1 + jest-resolve: ^29.3.1 + jest-runtime: ^29.3.1 + jest-util: ^29.3.1 + jest-watcher: ^29.3.1 + jest-worker: ^29.3.1 + p-limit: ^3.1.0 + source-map-support: 0.5.13 + checksum: ba1edbf662cd5a8b63a0b58842a6daa3e1fbb98dd30bdd1636ec0662c460b49fd3bd6d0851c5b6a899c952b7cffb649ba7d2b25cabcfe9097efbde49123a1694 + languageName: node + linkType: hard + +"jest-runtime@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-runtime@npm:29.3.1" + dependencies: + "@jest/environment": ^29.3.1 + "@jest/fake-timers": ^29.3.1 + "@jest/globals": ^29.3.1 + "@jest/source-map": ^29.2.0 + "@jest/test-result": ^29.3.1 + "@jest/transform": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/node": "*" + chalk: ^4.0.0 + cjs-module-lexer: ^1.0.0 + collect-v8-coverage: ^1.0.0 + glob: ^7.1.3 + graceful-fs: ^4.2.9 + jest-haste-map: ^29.3.1 + jest-message-util: ^29.3.1 + jest-mock: ^29.3.1 + jest-regex-util: ^29.2.0 + jest-resolve: ^29.3.1 + jest-snapshot: ^29.3.1 + jest-util: ^29.3.1 + slash: ^3.0.0 + strip-bom: ^4.0.0 + checksum: 585cfbfc45280c673ff4137f432f7713284d976a7b769a0a2b520527a93bc15ee59e166255fcbc518387f073019d05eff3b373e33e8d94117ffc98d8ec700ff0 + languageName: node + linkType: hard + +"jest-serializer@npm:^26.6.2": + version: 26.6.2 + resolution: "jest-serializer@npm:26.6.2" + dependencies: + "@types/node": "*" + graceful-fs: ^4.2.4 + checksum: 1c67aa1acefdc0b244f2629aaef12a56e563a5c5cb817970d2b97bdad5e8aae187b269c8d356c42ff9711436499c4da71ec8400e6280dab110be8cc5300884b0 + languageName: node + linkType: hard + +"jest-snapshot@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-snapshot@npm:29.3.1" + dependencies: + "@babel/core": ^7.11.6 + "@babel/generator": ^7.7.2 + "@babel/plugin-syntax-jsx": ^7.7.2 + "@babel/plugin-syntax-typescript": ^7.7.2 + "@babel/traverse": ^7.7.2 + "@babel/types": ^7.3.3 + "@jest/expect-utils": ^29.3.1 + "@jest/transform": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/babel__traverse": ^7.0.6 + "@types/prettier": ^2.1.5 + babel-preset-current-node-syntax: ^1.0.0 + chalk: ^4.0.0 + expect: ^29.3.1 + graceful-fs: ^4.2.9 + jest-diff: ^29.3.1 + jest-get-type: ^29.2.0 + jest-haste-map: ^29.3.1 + jest-matcher-utils: ^29.3.1 + jest-message-util: ^29.3.1 + jest-util: ^29.3.1 + natural-compare: ^1.4.0 + pretty-format: ^29.3.1 + semver: ^7.3.5 + checksum: ca65a637fc9547dea580342247f6adcdd83cf9bacc0af7ad2ff2b8a0d3310a09b983708071382f16957d717b90670b22cbf3849c46e2ec0f2c45d769826e12f3 + languageName: node + linkType: hard + +"jest-util@npm:^26.6.2": + version: 26.6.2 + resolution: "jest-util@npm:26.6.2" + dependencies: + "@jest/types": ^26.6.2 + "@types/node": "*" + chalk: ^4.0.0 + graceful-fs: ^4.2.4 + is-ci: ^2.0.0 + micromatch: ^4.0.2 + checksum: ab93709840f87bdf478d082f5465467c27a20a422cbe456cc2a56961d8c950ea52511995fb6063f62a113737f3dd714b836a1fbde51abef96642a5975e835a01 + languageName: node + linkType: hard + +"jest-util@npm:^29.0.0, jest-util@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-util@npm:29.3.1" + dependencies: + "@jest/types": ^29.3.1 + "@types/node": "*" + chalk: ^4.0.0 + ci-info: ^3.2.0 + graceful-fs: ^4.2.9 + picomatch: ^2.2.3 + checksum: c03606c389cf6f454962e4670fcb5d346e0cef166d71a6d70cde2ffaff9a0744fbf7b0651a01ac07e5ade790e95937bcaa604601ebb4c8dbf3e4c641027e61d0 + languageName: node + linkType: hard + +"jest-validate@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-validate@npm:29.3.1" + dependencies: + "@jest/types": ^29.3.1 + camelcase: ^6.2.0 + chalk: ^4.0.0 + jest-get-type: ^29.2.0 + leven: ^3.1.0 + pretty-format: ^29.3.1 + checksum: 5398f1c324582f290a99f6d68d9345ff9d16bbdcce06dfa4a81b0115aeeef6f9252cb64396c9ab968dafddbe762f68c8874b6c993a991b1c7f6f5e55cccb31a9 + languageName: node + linkType: hard + +"jest-watcher@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-watcher@npm:29.3.1" + dependencies: + "@jest/test-result": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/node": "*" + ansi-escapes: ^4.2.1 + chalk: ^4.0.0 + emittery: ^0.13.1 + jest-util: ^29.3.1 + string-length: ^4.0.1 + checksum: d3d029762c2d431bcff21635d959eb0aa000cc480e2a47277e8d36c57b8a76f6deab721015948cb8448238331813edcb44bec20f29670f80621709b0c0ca30ef + languageName: node + linkType: hard + +"jest-worker@npm:^26.2.1, jest-worker@npm:^26.5.0, jest-worker@npm:^26.6.2": + version: 26.6.2 + resolution: "jest-worker@npm:26.6.2" + dependencies: + "@types/node": "*" + merge-stream: ^2.0.0 + supports-color: ^7.0.0 + checksum: 07e4dba650381604cda253ab6d5837fe0279c8d68c25884995b45bfe149a7a1e1b5a97f304b4518f257dac2a9ddc1808d57d650649c3ab855e9e60cf824d2970 + languageName: node + linkType: hard + +"jest-worker@npm:^27.4.5": + version: 27.5.1 + resolution: "jest-worker@npm:27.5.1" + dependencies: + "@types/node": "*" + merge-stream: ^2.0.0 + supports-color: ^8.0.0 + checksum: 8c4737ffd03887b3c6768e4cc3ca0269c0336c1e4b1b120943958ddb035ed2a0fc6acab6dc99631720a3720af4e708ff84fb45382ad1e83c27946adf3623969b + languageName: node + linkType: hard + +"jest-worker@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-worker@npm:29.3.1" + dependencies: + "@types/node": "*" + jest-util: ^29.3.1 + merge-stream: ^2.0.0 + supports-color: ^8.0.0 + checksum: 8f089e3283c2a84d70d24caacfcf9986592ebde6757d938aa43a2a9d59607724da16a148d9dee93197a25c2fe4f2ee84ade105a88edc4c168ca2ad7881a56837 + languageName: node + linkType: hard + +"jest@npm:^29.3.1": + version: 29.3.1 + resolution: "jest@npm:29.3.1" + dependencies: + "@jest/core": ^29.3.1 + "@jest/types": ^29.3.1 + import-local: ^3.0.2 + jest-cli: ^29.3.1 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 97dae1a4346280c7ba6aa361b48d37e6776d338e308c2f188b4493f435d9c87c923658084b86e6c51f7a48bf5000e3879afee46141c8ee6a4275994cabd3a29a + languageName: node + linkType: hard + +"jmespath@npm:0.16.0": + version: 0.16.0 + resolution: "jmespath@npm:0.16.0" + checksum: 84cdca62c4a3d339701f01cc53decf16581c76ce49e6455119be1c5f6ab09a19e6788372536bd261d348d21cd817981605f8debae67affadba966219a2bac1c5 + languageName: node + linkType: hard + +"jquery@npm:x.*": + version: 3.6.3 + resolution: "jquery@npm:3.6.3" + checksum: e507b74e078761464620f8dd407fc9cc576892ffb8852a94c22b2f3371f028c97c8fb2ceaea895a4dbc0dd97106b3c35ab3e552c36516bf6cfd6ec84489fcef6 + languageName: node + linkType: hard + +"js-sdsl@npm:^4.1.4": + version: 4.2.0 + resolution: "js-sdsl@npm:4.2.0" + checksum: fe6525d84fa506d56b1a6f7754da2702119786869eaa29ec6e7bd723db1e950b5ec2f2c1890fc4d7c705fe1e8ce545a0717c76ad1d60f683a24837ce27943352 + languageName: node + linkType: hard + +"js-string-escape@npm:^1.0.1": + version: 1.0.1 + resolution: "js-string-escape@npm:1.0.1" + checksum: 2c33b9ff1ba6b84681c51ca0997e7d5a1639813c95d5b61cb7ad47e55cc28fa4a0b1935c3d218710d8e6bcee5d0cd8c44755231e3a4e45fc604534d9595a3628 + languageName: node + linkType: hard + +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"js-tokens@npm:^3.0.2": + version: 3.0.2 + resolution: "js-tokens@npm:3.0.2" + checksum: e3c3ee4d12643d90197628eb022a2884a15f08ea7dcac1ce97fdeee43031fbfc7ede674f2cdbbb582dcd4c94388b22e52d56c6cbeb2ac7d1b57c2f33c405e2ba + languageName: node + linkType: hard + +"js-yaml@npm:^3.13.1": + version: 3.14.1 + resolution: "js-yaml@npm:3.14.1" + dependencies: + argparse: ^1.0.7 + esprima: ^4.0.0 + bin: + js-yaml: bin/js-yaml.js + checksum: 6746baaaeac312c4db8e75fa22331d9a04cccb7792d126ed8ce6a0bbcfef0cedaddd0c5098fade53db067c09fe00aa1c957674b4765610a8b06a5a189e46433b + languageName: node + linkType: hard + +"js-yaml@npm:^4.1.0": + version: 4.1.0 + resolution: "js-yaml@npm:4.1.0" + dependencies: + argparse: ^2.0.1 + bin: + js-yaml: bin/js-yaml.js + checksum: 184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f + languageName: node + linkType: hard + +"jsbi@npm:^4.1.0": + version: 4.3.0 + resolution: "jsbi@npm:4.3.0" + checksum: 1817ac1b50ea3f4438bcd84cadc9aee7a8657829f65b55ea6f151f401dbbd3babedbfdd3e4f481bd7b5472abb7823efa640fd7e5eee7c30cea6431f7a8b74696 + languageName: node + linkType: hard + +"jsbn@npm:~0.1.0": + version: 0.1.1 + resolution: "jsbn@npm:0.1.1" + checksum: e046e05c59ff880ee4ef68902dbdcb6d2f3c5d60c357d4d68647dc23add556c31c0e5f41bdb7e69e793dd63468bd9e085da3636341048ef577b18f5b713877c0 + languageName: node + linkType: hard + +"jsesc@npm:^2.5.1": + version: 2.5.2 + resolution: "jsesc@npm:2.5.2" + bin: + jsesc: bin/jsesc + checksum: dbf59312e0ebf2b4405ef413ec2b25abb5f8f4d9bc5fb8d9f90381622ebca5f2af6a6aa9a8578f65903f9e33990a6dc798edd0ce5586894bf0e9e31803a1de88 + languageName: node + linkType: hard + +"jsesc@npm:~0.5.0": + version: 0.5.0 + resolution: "jsesc@npm:0.5.0" + bin: + jsesc: bin/jsesc + checksum: f93792440ae1d80f091b65f8ceddf8e55c4bb7f1a09dee5dcbdb0db5612c55c0f6045625aa6b7e8edb2e0a4feabd80ee48616dbe2d37055573a84db3d24f96d9 + languageName: node + linkType: hard + +"jshint@npm:^2.11.1": + version: 2.13.6 + resolution: "jshint@npm:2.13.6" + dependencies: + cli: ~1.0.0 + console-browserify: 1.1.x + exit: 0.1.x + htmlparser2: 3.8.x + lodash: ~4.17.21 + minimatch: ~3.0.2 + strip-json-comments: 1.0.x + bin: + jshint: bin/jshint + checksum: ce2db8c705a7b93ffe2957fcbc6aa9dd95d37a6cd9c58643033d99157d0ca68ef559d74b29033ae14967db38650efb67d9423d0ed010543265dc8300c8218e0f + languageName: node + linkType: hard + +"json-parse-better-errors@npm:^1.0.0, json-parse-better-errors@npm:^1.0.2": + version: 1.0.2 + resolution: "json-parse-better-errors@npm:1.0.2" + checksum: 2f1287a7c833e397c9ddd361a78638e828fc523038bb3441fd4fc144cfd2c6cd4963ffb9e207e648cf7b692600f1e1e524e965c32df5152120910e4903a47dcb + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0, json-parse-even-better-errors@npm:^2.3.1": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 + languageName: node + linkType: hard + +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce + languageName: node + linkType: hard + +"json-schema-traverse@npm:^1.0.0": + version: 1.0.0 + resolution: "json-schema-traverse@npm:1.0.0" + checksum: 71e30015d7f3d6dc1c316d6298047c8ef98a06d31ad064919976583eb61e1018a60a0067338f0f79cabc00d84af3fcc489bd48ce8a46ea165d9541ba17fb30c6 + languageName: node + linkType: hard + +"json-schema@npm:0.4.0, json-schema@npm:^0.4.0": + version: 0.4.0 + resolution: "json-schema@npm:0.4.0" + checksum: d4a637ec1d83544857c1c163232f3da46912e971d5bf054ba44fdb88f07d8d359a462b4aec46f2745efbc57053365608d88bc1d7b1729f7b4fc3369765639ed3 + languageName: node + linkType: hard + +"json-stable-stringify-without-jsonify@npm:^1.0.1": + version: 1.0.1 + resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" + checksum: cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 + languageName: node + linkType: hard + +"json-stream@npm:^1.0.0": + version: 1.0.0 + resolution: "json-stream@npm:1.0.0" + checksum: 7ab1b555249d2e4e5f66eff861d5f576a5903fe1fcdae9b2103cbd3f8602e05c0aac9390f9fb3ccfc6624a30467846be4baba5f1f5403a710443d86581c15647 + languageName: node + linkType: hard + +"json-stringify-safe@npm:~5.0.1": + version: 5.0.1 + resolution: "json-stringify-safe@npm:5.0.1" + checksum: 7dbf35cd0411d1d648dceb6d59ce5857ec939e52e4afc37601aa3da611f0987d5cee5b38d58329ceddf3ed48bd7215229c8d52059ab01f2444a338bf24ed0f37 + languageName: node + linkType: hard + +"json5@npm:^1.0.1": + version: 1.0.2 + resolution: "json5@npm:1.0.2" + dependencies: + minimist: ^1.2.0 + bin: + json5: lib/cli.js + checksum: 9ee316bf21f000b00752e6c2a3b79ecf5324515a5c60ee88983a1910a45426b643a4f3461657586e8aeca87aaf96f0a519b0516d2ae527a6c3e7eed80f68717f + languageName: node + linkType: hard + +"json5@npm:^2.1.2, json5@npm:^2.1.3, json5@npm:^2.2.0, json5@npm:^2.2.2, json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c + languageName: node + linkType: hard + +"jsonfile@npm:^4.0.0": + version: 4.0.0 + resolution: "jsonfile@npm:4.0.0" + dependencies: + graceful-fs: ^4.1.6 + dependenciesMeta: + graceful-fs: + optional: true + checksum: 7dc94b628d57a66b71fb1b79510d460d662eb975b5f876d723f81549c2e9cd316d58a2ddf742b2b93a4fa6b17b2accaf1a738a0e2ea114bdfb13a32e5377e480 + languageName: node + linkType: hard + +"jsonfile@npm:^6.0.1": + version: 6.1.0 + resolution: "jsonfile@npm:6.1.0" + dependencies: + graceful-fs: ^4.1.6 + universalify: ^2.0.0 + dependenciesMeta: + graceful-fs: + optional: true + checksum: 4f95b5e8a5622b1e9e8f33c96b7ef3158122f595998114d1e7f03985649ea99cb3cd99ce1ed1831ae94c8c8543ab45ebd044207612f31a56fd08462140e46865 + languageName: node + linkType: hard + +"jsonparse@npm:^1.2.0": + version: 1.3.1 + resolution: "jsonparse@npm:1.3.1" + checksum: 89bc68080cd0a0e276d4b5ab1b79cacd68f562467008d176dc23e16e97d4efec9e21741d92ba5087a8433526a45a7e6a9d5ef25408696c402ca1cfbc01a90bf0 + languageName: node + linkType: hard + +"jsonpointer@npm:^5.0.0": + version: 5.0.1 + resolution: "jsonpointer@npm:5.0.1" + checksum: 89929e58b400fcb96928c0504fcf4fc3f919d81e9543ceb055df125538470ee25290bb4984251e172e6ef8fcc55761eb998c118da763a82051ad89d4cb073fe7 + languageName: node + linkType: hard + +"jspdf@npm:^2.5.1": + version: 2.5.1 + resolution: "jspdf@npm:2.5.1" + dependencies: + "@babel/runtime": ^7.14.0 + atob: ^2.1.2 + btoa: ^1.2.1 + canvg: ^3.0.6 + core-js: ^3.6.0 + dompurify: ^2.2.0 + fflate: ^0.4.8 + html2canvas: ^1.0.0-rc.5 + dependenciesMeta: + canvg: + optional: true + core-js: + optional: true + dompurify: + optional: true + html2canvas: + optional: true + checksum: dad15d4f53ead1d2e9d5f6fd9b6e72c7233ba5cbc30d98461eb0ef609aa908b28fd5eaaf2b763b55df945c7ecca2323097d9331f09fee1d6c23c06785520ab5f + languageName: node + linkType: hard + +"jsprim@npm:^1.2.2": + version: 1.4.2 + resolution: "jsprim@npm:1.4.2" + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.4.0 + verror: 1.10.0 + checksum: 5e4bca99e90727c2040eb4c2190d0ef1fe51798ed5714e87b841d304526190d960f9772acc7108fa1416b61e1122bcd60e4460c91793dce0835df5852aab55af + languageName: node + linkType: hard + +"jsprim@npm:^2.0.2": + version: 2.0.2 + resolution: "jsprim@npm:2.0.2" + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.4.0 + verror: 1.10.0 + checksum: 677be2d41df536c92c6d0114a492ef197084018cfbb1a3e10b1fa1aad889564b2e3a7baa6af7949cc2d73678f42368b0be165a26bd4e4de6883a30dd6a24e98d + languageName: node + linkType: hard + +"jsx-ast-utils@npm:^2.4.1 || ^3.0.0": + version: 3.3.3 + resolution: "jsx-ast-utils@npm:3.3.3" + dependencies: + array-includes: ^3.1.5 + object.assign: ^4.1.3 + checksum: fb69ce100931e50d42c8f72a01495b7d090064824ce481cf7746449609c148a29aae6984624cf9066ac14bdf7978f8774461e120d5b50fa90b3bfe0a0e21ff77 + languageName: node + linkType: hard + +"jsx-runtime@npm:^1.2.0": + version: 1.2.0 + resolution: "jsx-runtime@npm:1.2.0" + dependencies: + object-assign: ^3.0.0 + checksum: 26b2f65c8a11fd8f5ed1e499b72aacc28e209fd3e5c6caa1eb600042334acf7ac5fd0a058b48d821ae68552ea3e789eb6fe6ff663987a8db847f6aa010dcd70a + languageName: node + linkType: hard + +"junk@npm:^3.1.0": + version: 3.1.0 + resolution: "junk@npm:3.1.0" + checksum: 820174b9fa9a3af09aeeeeb1022df2481a2b10752ce5f65ac63924a79cb9bba83ea7c288e8d5b448951109742da5ea69a230846f4bf3c17c5c6a1d0603b63db4 + languageName: node + linkType: hard + +"keyboard-key@npm:^1.1.0": + version: 1.1.0 + resolution: "keyboard-key@npm:1.1.0" + checksum: 57f53960472200a521b0845b48c3d56b76078aa161532bc24e2c9382ab154ce35ec0bbf26bb557b8582ae42e173da2f3b8d4125d8754e6e963887b8bdb6e97a1 + languageName: node + linkType: hard + +"kind-of@npm:^3.0.2, kind-of@npm:^3.0.3, kind-of@npm:^3.2.0": + version: 3.2.2 + resolution: "kind-of@npm:3.2.2" + dependencies: + is-buffer: ^1.1.5 + checksum: 7e34bc29d4b02c997f92f080de34ebb92033a96736bbb0bb2410e033a7e5ae6571f1fa37b2d7710018f95361473b816c604234197f4f203f9cf149d8ef1574d9 + languageName: node + linkType: hard + +"kind-of@npm:^4.0.0": + version: 4.0.0 + resolution: "kind-of@npm:4.0.0" + dependencies: + is-buffer: ^1.1.5 + checksum: d6c44c75ee36898142dfc7106afbd50593216c37f96acb81a7ab33ca1a6938ce97d5692b8fc8fccd035f83811a9d97749d68771116441a48eedd0b68e2973165 + languageName: node + linkType: hard + +"kind-of@npm:^5.0.0": + version: 5.1.0 + resolution: "kind-of@npm:5.1.0" + checksum: fe85b7a2ed4b4d5a12e16e01d00d5c336e1760842fe0da38283605b9880c984288935e87b13138909e4d23d2d197a1d492f7393c6638d2c0fab8a900c4fb0392 + languageName: node + linkType: hard + +"kind-of@npm:^6.0.0, kind-of@npm:^6.0.2": + version: 6.0.3 + resolution: "kind-of@npm:6.0.3" + checksum: 61cdff9623dabf3568b6445e93e31376bee1cdb93f8ba7033d86022c2a9b1791a1d9510e026e6465ebd701a6dd2f7b0808483ad8838341ac52f003f512e0b4c4 + languageName: node + linkType: hard + +"kleur@npm:^3.0.3": + version: 3.0.3 + resolution: "kleur@npm:3.0.3" + checksum: cd3a0b8878e7d6d3799e54340efe3591ca787d9f95f109f28129bdd2915e37807bf8918bb295ab86afb8c82196beec5a1adcaf29042ce3f2bd932b038fe3aa4b + languageName: node + linkType: hard + +"klona@npm:^2.0.4, klona@npm:^2.0.5": + version: 2.0.6 + resolution: "klona@npm:2.0.6" + checksum: 94eed2c6c2ce99f409df9186a96340558897b3e62a85afdc1ee39103954d2ebe1c1c4e9fe2b0952771771fa96d70055ede8b27962a7021406374fdb695fd4d01 + languageName: node + linkType: hard + +"kuler@npm:^2.0.0": + version: 2.0.0 + resolution: "kuler@npm:2.0.0" + checksum: 0a4e99d92ca373f8f74d1dc37931909c4d0d82aebc94cf2ba265771160fc12c8df34eaaac80805efbda367e2795cb1f1dd4c3d404b6b1cf38aec94035b503d2d + languageName: node + linkType: hard + +"latest-version@npm:^3.0.0": + version: 3.1.0 + resolution: "latest-version@npm:3.1.0" + dependencies: + package-json: ^4.0.0 + checksum: a850d49d5008e9370e5afc5b2a6bc610c34499b5d9aeaa1cb41e80bfa64a3f666438c65c7d2db6ad3bd62f727d4b805c63e1444bc9c3ad6d51d1c1ebce66758c + languageName: node + linkType: hard + +"lazy-ass@npm:^1.6.0": + version: 1.6.0 + resolution: "lazy-ass@npm:1.6.0" + checksum: 4af6cb9a333fbc811268c745f9173fba0f99ecb817cc9c0fae5dbf986b797b730ff525504128f6623b91aba32b02124553a34b0d14de3762b637b74d7233f3bd + languageName: node + linkType: hard + +"lazy-property@npm:~1.0.0": + version: 1.0.0 + resolution: "lazy-property@npm:1.0.0" + checksum: 15b2113c2d2b0a6832c8aafdac4837d9a14585164a906875d542be383c6c4812c349b0040471a4690fff3e688b9f00c7327cb4dd82f5faf29f2bb500eb29e688 + languageName: node + linkType: hard + +"lazy-universal-dotenv@npm:^3.0.1": + version: 3.0.1 + resolution: "lazy-universal-dotenv@npm:3.0.1" + dependencies: + "@babel/runtime": ^7.5.0 + app-root-dir: ^1.0.2 + core-js: ^3.0.4 + dotenv: ^8.0.0 + dotenv-expand: ^5.1.0 + checksum: d7cf054661bcafe63c61978856f9cfacc2fc694430939681da9729016082fc9c07e4c472e7755452b518234ada38925ec5ad582b0c1f9aae7a43c24f105fdba9 + languageName: node + linkType: hard + +"leven@npm:^3.1.0": + version: 3.1.0 + resolution: "leven@npm:3.1.0" + checksum: cd778ba3fbab0f4d0500b7e87d1f6e1f041507c56fdcd47e8256a3012c98aaee371d4c15e0a76e0386107af2d42e2b7466160a2d80688aaa03e66e49949f42df + languageName: node + linkType: hard + +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: ^1.2.1 + type-check: ~0.4.0 + checksum: effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e + languageName: node + linkType: hard + +"levn@npm:~0.3.0": + version: 0.3.0 + resolution: "levn@npm:0.3.0" + dependencies: + prelude-ls: ~1.1.2 + type-check: ~0.3.2 + checksum: e440df9de4233da0b389cd55bd61f0f6aaff766400bebbccd1231b81801f6dbc1d816c676ebe8d70566394b749fa624b1ed1c68070e9c94999f0bdecc64cb676 + languageName: node + linkType: hard + +"libcipm@npm:^4.0.8": + version: 4.0.8 + resolution: "libcipm@npm:4.0.8" + dependencies: + bin-links: ^1.1.2 + bluebird: ^3.5.1 + figgy-pudding: ^3.5.1 + find-npm-prefix: ^1.0.2 + graceful-fs: ^4.1.11 + ini: ^1.3.5 + lock-verify: ^2.1.0 + mkdirp: ^0.5.1 + npm-lifecycle: ^3.0.0 + npm-logical-tree: ^1.2.1 + npm-package-arg: ^6.1.0 + pacote: ^9.1.0 + read-package-json: ^2.0.13 + rimraf: ^2.6.2 + worker-farm: ^1.6.0 + checksum: 1d083b4bccc328c6febdb89bf08928e4416b8e108686077184ae0741c168dbba281ecdce7cddefb98dd467d82dbe0a8dc6db88686357cd589cab683a1268197a + languageName: node + linkType: hard + +"libnpm@npm:^3.0.1": + version: 3.0.1 + resolution: "libnpm@npm:3.0.1" + dependencies: + bin-links: ^1.1.2 + bluebird: ^3.5.3 + find-npm-prefix: ^1.0.2 + libnpmaccess: ^3.0.2 + libnpmconfig: ^1.2.1 + libnpmhook: ^5.0.3 + libnpmorg: ^1.0.1 + libnpmpublish: ^1.1.2 + libnpmsearch: ^2.0.2 + libnpmteam: ^1.0.2 + lock-verify: ^2.0.2 + npm-lifecycle: ^3.0.0 + npm-logical-tree: ^1.2.1 + npm-package-arg: ^6.1.0 + npm-profile: ^4.0.2 + npm-registry-fetch: ^4.0.0 + npmlog: ^4.1.2 + pacote: ^9.5.3 + read-package-json: ^2.0.13 + stringify-package: ^1.0.0 + checksum: 79f777394407a8c9a2b0b58cf8a9ff579ee30a22275fbfe54dbd92c3719608911fc2390a1e19b8b20fc11f227ba2d06b1dfa21d60c91a07e51229a52eb2eee5e + languageName: node + linkType: hard + +"libnpmaccess@npm:^3.0.2": + version: 3.0.2 + resolution: "libnpmaccess@npm:3.0.2" + dependencies: + aproba: ^2.0.0 + get-stream: ^4.0.0 + npm-package-arg: ^6.1.0 + npm-registry-fetch: ^4.0.0 + checksum: 58db2072cffd18c29b03af931d6cd61f5272b73a002fa6c2e7b6b598c4b1a4f6a161d240ed9543dcfcdc2d6a089a6f7cbbda6cdf7a756c18fec75b63b95473d5 + languageName: node + linkType: hard + +"libnpmconfig@npm:^1.2.1": + version: 1.2.1 + resolution: "libnpmconfig@npm:1.2.1" + dependencies: + figgy-pudding: ^3.5.1 + find-up: ^3.0.0 + ini: ^1.3.5 + checksum: 1f9e2cebe87eeb04979e63c8f8afe45dd147d7e75cea71fde8552be69a067a0ab1c8030d14b08a048be49dd984e7b7d8b1ca9f1e3f85c6f3267389011cb42044 + languageName: node + linkType: hard + +"libnpmhook@npm:^5.0.3": + version: 5.0.3 + resolution: "libnpmhook@npm:5.0.3" + dependencies: + aproba: ^2.0.0 + figgy-pudding: ^3.4.1 + get-stream: ^4.0.0 + npm-registry-fetch: ^4.0.0 + checksum: 1ab77317de50766088a9946ac9dec3e9408c65339a243110259a0c2e348e785a19ea941bf2471d77d7a121b43cc4438aea85a19e8041418fd47822ae3fee1549 + languageName: node + linkType: hard + +"libnpmorg@npm:^1.0.1": + version: 1.0.1 + resolution: "libnpmorg@npm:1.0.1" + dependencies: + aproba: ^2.0.0 + figgy-pudding: ^3.4.1 + get-stream: ^4.0.0 + npm-registry-fetch: ^4.0.0 + checksum: a4827428f2ea8961f01b1e7b1a8cf0258b32de61dffb7fc77ea11dac829669a36cb7844e4bec6b4dc77be22fb86aed6368e24fd6322d9116df1dd99bcd9b43d9 + languageName: node + linkType: hard + +"libnpmpublish@npm:^1.1.2": + version: 1.1.3 + resolution: "libnpmpublish@npm:1.1.3" + dependencies: + aproba: ^2.0.0 + figgy-pudding: ^3.5.1 + get-stream: ^4.0.0 + lodash.clonedeep: ^4.5.0 + normalize-package-data: ^2.4.0 + npm-package-arg: ^6.1.0 + npm-registry-fetch: ^4.0.0 + semver: ^5.5.1 + ssri: ^6.0.1 + checksum: a7bd40d35bac611577ec5b1acc5757c24e404c90e9a93b2df798901d91c675a05ecfc791ed5f531ce9a6d77fad4811d7f713daf4f14ced922555ed0461b787cf + languageName: node + linkType: hard + +"libnpmsearch@npm:^2.0.2": + version: 2.0.2 + resolution: "libnpmsearch@npm:2.0.2" + dependencies: + figgy-pudding: ^3.5.1 + get-stream: ^4.0.0 + npm-registry-fetch: ^4.0.0 + checksum: 8b710c132882d460f49fb8bafb57e31f4bc79a36260332293379acf5f12b011c31fe84f09dc82af9cca4d69edfeffda7d3f67d75c8c52647dde293235c380a7c + languageName: node + linkType: hard + +"libnpmteam@npm:^1.0.2": + version: 1.0.2 + resolution: "libnpmteam@npm:1.0.2" + dependencies: + aproba: ^2.0.0 + figgy-pudding: ^3.4.1 + get-stream: ^4.0.0 + npm-registry-fetch: ^4.0.0 + checksum: 65c09e9b9ee0c05a4dd7463ce72c1bb9758a7dddfd20a998b247c5c8a7faee8ecc0c514577ef9a84bfcf5eadc0cbb73e6f3bcbf83db67f532c289e5fd038132b + languageName: node + linkType: hard + +"libnpx@npm:^10.2.4": + version: 10.2.4 + resolution: "libnpx@npm:10.2.4" + dependencies: + dotenv: ^5.0.1 + npm-package-arg: ^6.0.0 + rimraf: ^2.6.2 + safe-buffer: ^5.1.0 + update-notifier: ^2.3.0 + which: ^1.3.0 + y18n: ^4.0.0 + yargs: ^14.2.3 + checksum: 1f518540fd18599fa2e48419b9ca60355220f5147ba3fea62a27772d79d25cf3bb60a87efc364ff8214b4b54740c3705ddceea7897a1165c523a8cb30e94f6ce + languageName: node + linkType: hard + +"lilconfig@npm:^2.0.3, lilconfig@npm:^2.0.5, lilconfig@npm:^2.0.6": + version: 2.0.6 + resolution: "lilconfig@npm:2.0.6" + checksum: 52bcb478586c629a78b9b06de72de897cd6d771725e70ee91ec16605721afebf43cf54b4d20b6bf904ca70877ddd9531b9578494c694072d1573a6d4aba1545a + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d + languageName: node + linkType: hard + +"listr2@npm:^3.8.3": + version: 3.14.0 + resolution: "listr2@npm:3.14.0" + dependencies: + cli-truncate: ^2.1.0 + colorette: ^2.0.16 + log-update: ^4.0.0 + p-map: ^4.0.0 + rfdc: ^1.3.0 + rxjs: ^7.5.1 + through: ^2.3.8 + wrap-ansi: ^7.0.0 + peerDependencies: + enquirer: ">= 2.3.0 < 3" + peerDependenciesMeta: + enquirer: + optional: true + checksum: 8301703876ad6bf50cd769e9c1169c2aa435951d69d4f54fc202a13c1b6006a9b3afbcf9842440eb22f08beec4d311d365e31d4ed2e0fcabf198d8085b06a421 + languageName: node + linkType: hard + +"load-json-file@npm:^1.0.0": + version: 1.1.0 + resolution: "load-json-file@npm:1.1.0" + dependencies: + graceful-fs: ^4.1.2 + parse-json: ^2.2.0 + pify: ^2.0.0 + pinkie-promise: ^2.0.0 + strip-bom: ^2.0.0 + checksum: 2a5344c2d88643735a938fdca8582c0504e1c290577faa74f56b9cc187fa443832709a15f36e5771f779ec0878215a03abc8faf97ec57bb86092ceb7e0caef22 + languageName: node + linkType: hard + +"loader-runner@npm:^2.4.0": + version: 2.4.0 + resolution: "loader-runner@npm:2.4.0" + checksum: 1f723bd8318453c2d073d7befbf891ba6d2a02f22622688bf7d22e7ba527a0f9476c7fdfedc6bfa2b55c0389d9f406f3a5239ed1b33c9088d77cfed085086a1e + languageName: node + linkType: hard + +"loader-runner@npm:^4.2.0": + version: 4.3.0 + resolution: "loader-runner@npm:4.3.0" + checksum: a44d78aae0907a72f73966fe8b82d1439c8c485238bd5a864b1b9a2a3257832effa858790241e6b37876b5446a78889adf2fcc8dd897ce54c089ecc0a0ce0bf0 + languageName: node + linkType: hard + +"loader-utils@npm:^1.1.0, loader-utils@npm:^1.2.3": + version: 1.4.2 + resolution: "loader-utils@npm:1.4.2" + dependencies: + big.js: ^5.2.2 + emojis-list: ^3.0.0 + json5: ^1.0.1 + checksum: 2b726088b5526f7605615e3e28043ae9bbd2453f4a85898e1151f3c39dbf7a2b65d09f3996bc588d92ac7e717ded529d3e1ea3ea42c433393be84a58234a2f53 + languageName: node + linkType: hard + +"loader-utils@npm:^2.0.0, loader-utils@npm:^2.0.4": + version: 2.0.4 + resolution: "loader-utils@npm:2.0.4" + dependencies: + big.js: ^5.2.2 + emojis-list: ^3.0.0 + json5: ^2.1.2 + checksum: d5654a77f9d339ec2a03d88221a5a695f337bf71eb8dea031b3223420bb818964ba8ed0069145c19b095f6c8b8fd386e602a3fc7ca987042bd8bb1dcc90d7100 + languageName: node + linkType: hard + +"locate-path@npm:^3.0.0": + version: 3.0.0 + resolution: "locate-path@npm:3.0.0" + dependencies: + p-locate: ^3.0.0 + path-exists: ^3.0.0 + checksum: 3db394b7829a7fe2f4fbdd25d3c4689b85f003c318c5da4052c7e56eed697da8f1bce5294f685c69ff76e32cba7a33629d94396976f6d05fb7f4c755c5e2ae8b + languageName: node + linkType: hard + +"locate-path@npm:^5.0.0": + version: 5.0.0 + resolution: "locate-path@npm:5.0.0" + dependencies: + p-locate: ^4.1.0 + checksum: 33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 + languageName: node + linkType: hard + +"locate-path@npm:^6.0.0": + version: 6.0.0 + resolution: "locate-path@npm:6.0.0" + dependencies: + p-locate: ^5.0.0 + checksum: d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 + languageName: node + linkType: hard + +"lock-verify@npm:^2.0.2, lock-verify@npm:^2.1.0, lock-verify@npm:^2.2.2": + version: 2.2.2 + resolution: "lock-verify@npm:2.2.2" + dependencies: + "@iarna/cli": ^2.1.0 + npm-package-arg: ^6.1.0 + semver: ^5.4.1 + bin: + lock-verify: cli.js + checksum: 3211b64d751b7c9b9b63ca821b0c5232d5558f2d25f7775b58885bd8f9278c29395e860c336a466d2c9c7e9ddbfc924bbb3ec24e2686200996a7578f1e386de3 + languageName: node + linkType: hard + +"lockfile@npm:^1.0.4": + version: 1.0.4 + resolution: "lockfile@npm:1.0.4" + dependencies: + signal-exit: ^3.0.2 + checksum: 80b7777ceb43105d9e588733c3efc2514653a5e3a0dae3e61347a1f5381da34dcaa2caaa60c39ed5d4ad31c1735a4831e5639a0ba1c508bfea8dbc9c89777b37 + languageName: node + linkType: hard + +"lodash-es@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash-es@npm:4.17.21" + checksum: fb407355f7e6cd523a9383e76e6b455321f0f153a6c9625e21a8827d10c54c2a2341bd2ae8d034358b60e07325e1330c14c224ff582d04612a46a4f0479ff2f2 + languageName: node + linkType: hard + +"lodash._baseindexof@npm:*": + version: 3.1.0 + resolution: "lodash._baseindexof@npm:3.1.0" + checksum: 48f9c09ec61cb990b2f2c8bd968b16c78146aabb63e1baf7073e877bfa414293b2849f814cd7d92c97947ecb2391055388af5dd2f18b90a5b00857c6ac63a518 + languageName: node + linkType: hard + +"lodash._baseuniq@npm:~4.6.0": + version: 4.6.0 + resolution: "lodash._baseuniq@npm:4.6.0" + dependencies: + lodash._createset: ~4.0.0 + lodash._root: ~3.0.0 + checksum: 07e2ac63efde634685ed12b16664ee04931b258cd2511b703fddd9ddfc624a85542e3f03a6c2fdac369c00559296198daa8efffaf90d71c7a35f5c89f94ebb14 + languageName: node + linkType: hard + +"lodash._bindcallback@npm:*": + version: 3.0.1 + resolution: "lodash._bindcallback@npm:3.0.1" + checksum: c135bc26cfbe6fd11b3d2ffc7b8307fbbcecd3fbb45f6ecec301614207a164388fd7ee3f07e3ad0745bbe4c8d3acefc61b967a07b2d43b5b5f5dfdf661ed945a + languageName: node + linkType: hard + +"lodash._cacheindexof@npm:*": + version: 3.0.2 + resolution: "lodash._cacheindexof@npm:3.0.2" + checksum: a61851d38f8f138c30b30e896cb0b46eecbe957292da54a60fd000bc0621219bdaa6116ef636269fed2e2f96c2d017140adae39a2dcfe7321a33e483a6ff8519 + languageName: node + linkType: hard + +"lodash._createcache@npm:*": + version: 3.1.2 + resolution: "lodash._createcache@npm:3.1.2" + dependencies: + lodash._getnative: ^3.0.0 + checksum: 3dc696d4e0532a5243596efc37e8a088833219058f5292c62df7444cf71e5a98243c60d0e4f87129192a76ef5f41777529c0106f7ac13b7c7ecc3f8920f6914a + languageName: node + linkType: hard + +"lodash._createset@npm:~4.0.0": + version: 4.0.3 + resolution: "lodash._createset@npm:4.0.3" + checksum: 6144f59a63cedb8bf2840970579dc7dfdad55970741a80f9a6e5c6b29b629fc204c846e54e29266ec24b41e3f680bcbeb9fe9332fb9f2bab71eb9c70cacd26d8 + languageName: node + linkType: hard + +"lodash._getnative@npm:*, lodash._getnative@npm:^3.0.0": + version: 3.9.1 + resolution: "lodash._getnative@npm:3.9.1" + checksum: 858cff25fc52353a1e39f44ff87fc1e1e8a85da513818f0caebe50c2795cf5cbce9d71a3e91ec0972bee3b174a74d697a38c6bb16d0b416dcc32322ae152a104 + languageName: node + linkType: hard + +"lodash._root@npm:~3.0.0": + version: 3.0.1 + resolution: "lodash._root@npm:3.0.1" + checksum: 679c8a570381795b6953ec8c680442acb5472c5b399263ed4ea6839630cf4dd5d65aa8c2a0f6934507fc5bc70f2af20bc430cbd847728b8c1672db95555edb51 + languageName: node + linkType: hard + +"lodash.clonedeep@npm:^4.5.0, lodash.clonedeep@npm:~4.5.0": + version: 4.5.0 + resolution: "lodash.clonedeep@npm:4.5.0" + checksum: 2caf0e4808f319d761d2939ee0642fa6867a4bbf2cfce43276698828380756b99d4c4fa226d881655e6ac298dd453fe12a5ec8ba49861777759494c534936985 + languageName: node + linkType: hard + +"lodash.curry@npm:^4.0.1": + version: 4.1.1 + resolution: "lodash.curry@npm:4.1.1" + checksum: f0431947dc9236df879fc13eb40c31a2839c958bd0eaa39170a5758c25a7d85d461716a851ab45a175371950b283480615cdd4b07fb0dd1afff7a2914a90696f + languageName: node + linkType: hard + +"lodash.debounce@npm:^4.0.8": + version: 4.0.8 + resolution: "lodash.debounce@npm:4.0.8" + checksum: 762998a63e095412b6099b8290903e0a8ddcb353ac6e2e0f2d7e7d03abd4275fe3c689d88960eb90b0dde4f177554d51a690f22a343932ecbc50a5d111849987 + languageName: node + linkType: hard + +"lodash.difference@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.difference@npm:4.5.0" + checksum: 5d52859218a7df427547ff1fadbc397879709fe6c788b037df7d6d92b676122c92bd35ec85d364edb596b65dfc6573132f420c9b4ee22bb6b9600cd454c90637 + languageName: node + linkType: hard + +"lodash.flow@npm:^3.3.0": + version: 3.5.0 + resolution: "lodash.flow@npm:3.5.0" + checksum: b3202ddbb79e5aab41719806d0d5ae969f64ae6b59e6bdaaecaa96ec68d6ba429e544017fe0e71ecf5b7ee3cea7b45d43c46b7d67ca159d6cca86fca76c61a31 + languageName: node + linkType: hard + +"lodash.memoize@npm:4.x, lodash.memoize@npm:^4.1.2": + version: 4.1.2 + resolution: "lodash.memoize@npm:4.1.2" + checksum: c8713e51eccc650422716a14cece1809cfe34bc5ab5e242b7f8b4e2241c2483697b971a604252807689b9dd69bfe3a98852e19a5b89d506b000b4187a1285df8 + languageName: node + linkType: hard + +"lodash.merge@npm:^4.6.2": + version: 4.6.2 + resolution: "lodash.merge@npm:4.6.2" + checksum: 402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 + languageName: node + linkType: hard + +"lodash.once@npm:^4.1.1": + version: 4.1.1 + resolution: "lodash.once@npm:4.1.1" + checksum: 46a9a0a66c45dd812fcc016e46605d85ad599fe87d71a02f6736220554b52ffbe82e79a483ad40f52a8a95755b0d1077fba259da8bfb6694a7abbf4a48f1fc04 + languageName: node + linkType: hard + +"lodash.restparam@npm:*": + version: 3.6.1 + resolution: "lodash.restparam@npm:3.6.1" + checksum: b402239b4804c867dc24ed3ec19c5deb5edff8ddd131f7deceec2fdc8ee06dfb75d2aa89099e97c67e05182106562239213ef205684e2e40186cb8211b4c2431 + languageName: node + linkType: hard + +"lodash.sortby@npm:^4.7.0": + version: 4.7.0 + resolution: "lodash.sortby@npm:4.7.0" + checksum: fc48fb54ff7669f33bb32997cab9460757ee99fafaf72400b261c3e10fde21538e47d8cfcbe6a25a31bcb5b7b727c27d52626386fc2de24eb059a6d64a89cdf5 + languageName: node + linkType: hard + +"lodash.union@npm:~4.6.0": + version: 4.6.0 + resolution: "lodash.union@npm:4.6.0" + checksum: 6da7f72d1facd472f6090b49eefff984c9f9179e13172039c0debca6851d21d37d83c7ad5c43af23bd220f184cd80e6897e8e3206509fae491f9068b02ae6319 + languageName: node + linkType: hard + +"lodash.uniq@npm:4.5.0, lodash.uniq@npm:^4.5.0, lodash.uniq@npm:~4.5.0": + version: 4.5.0 + resolution: "lodash.uniq@npm:4.5.0" + checksum: 262d400bb0952f112162a320cc4a75dea4f66078b9e7e3075ffbc9c6aa30b3e9df3cf20e7da7d566105e1ccf7804e4fbd7d804eee0b53de05d83f16ffbf41c5e + languageName: node + linkType: hard + +"lodash.without@npm:~4.4.0": + version: 4.4.0 + resolution: "lodash.without@npm:4.4.0" + checksum: 8d87b5dea6ad0eb63a2b1e09db152fef1a5f7177ab4732f345f4623b1d7d0533e0cbe57bf47d9c4aae40a2c1af3f264b30da88d3e166ab28fcaecf0376d33d80 + languageName: node + linkType: hard + +"lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4, lodash@npm:~4.17.21": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c + languageName: node + linkType: hard + +"log-symbols@npm:^4.0.0": + version: 4.1.0 + resolution: "log-symbols@npm:4.1.0" + dependencies: + chalk: ^4.1.0 + is-unicode-supported: ^0.1.0 + checksum: 67f445a9ffa76db1989d0fa98586e5bc2fd5247260dafb8ad93d9f0ccd5896d53fb830b0e54dade5ad838b9de2006c826831a3c528913093af20dff8bd24aca6 + languageName: node + linkType: hard + +"log-update@npm:^4.0.0": + version: 4.0.0 + resolution: "log-update@npm:4.0.0" + dependencies: + ansi-escapes: ^4.3.0 + cli-cursor: ^3.1.0 + slice-ansi: ^4.0.0 + wrap-ansi: ^6.2.0 + checksum: 18b299e230432a156f2535660776406d15ba8bb7817dd3eaadd58004b363756d4ecaabcd658f9949f90b62ea7d3354423be3fdeb7a201ab951ec0e8d6139af86 + languageName: node + linkType: hard + +"logform@npm:^2.3.2, logform@npm:^2.4.0": + version: 2.4.2 + resolution: "logform@npm:2.4.2" + dependencies: + "@colors/colors": 1.5.0 + fecha: ^4.2.0 + ms: ^2.1.1 + safe-stable-stringify: ^2.3.1 + triple-beam: ^1.3.0 + checksum: a41337eea9d4a400c23e5c65bd8af7cc77eebd224f0b1d441e90ec6a7c0ec60ca9f6408797428c835ee56dc30863e4aef0a4f30c5592b06dfd415eacf7e06144 + languageName: node + linkType: hard + +"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.2.0, loose-envify@npm:^1.3.1, loose-envify@npm:^1.4.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: ^3.0.0 || ^4.0.0 + bin: + loose-envify: cli.js + checksum: 655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e + languageName: node + linkType: hard + +"loud-rejection@npm:^1.0.0": + version: 1.6.0 + resolution: "loud-rejection@npm:1.6.0" + dependencies: + currently-unhandled: ^0.4.1 + signal-exit: ^3.0.0 + checksum: aa060b3fe55ad96b97890f1b0a24bf81a2d612e397d6cc0374ce1cf7e021cd0247f0ddb68134499882d0843c2776371d5221b80b0b3beeca5133a6e7f27a3845 + languageName: node + linkType: hard + +"lower-case@npm:^2.0.2": + version: 2.0.2 + resolution: "lower-case@npm:2.0.2" + dependencies: + tslib: ^2.0.3 + checksum: 3d925e090315cf7dc1caa358e0477e186ffa23947740e4314a7429b6e62d72742e0bbe7536a5ae56d19d7618ce998aba05caca53c2902bd5742fdca5fc57fd7b + languageName: node + linkType: hard + +"lowercase-keys@npm:^1.0.0": + version: 1.0.1 + resolution: "lowercase-keys@npm:1.0.1" + checksum: 56776a8e1ef1aca98ecf6c19b30352ae1cf257b65b8ac858b7d8a0e8b348774d12a9b41aa7f59bfea51bff44bc7a198ab63ba4406bfba60dba008799618bef66 + languageName: node + linkType: hard + +"lru-cache@npm:^4.0.1": + version: 4.1.5 + resolution: "lru-cache@npm:4.1.5" + dependencies: + pseudomap: ^1.0.2 + yallist: ^2.1.2 + checksum: 1ca5306814e5add9ec63556d6fd9b24a4ecdeaef8e9cea52cbf30301e6b88c8d8ddc7cab45b59b56eb763e6c45af911585dc89925a074ab65e1502e3fe8103cf + languageName: node + linkType: hard + +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: ^3.0.2 + checksum: 89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 + languageName: node + linkType: hard + +"lru-cache@npm:^6.0.0": + version: 6.0.0 + resolution: "lru-cache@npm:6.0.0" + dependencies: + yallist: ^4.0.0 + checksum: cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 + languageName: node + linkType: hard + +"lru-cache@npm:^7.7.1": + version: 7.14.1 + resolution: "lru-cache@npm:7.14.1" + checksum: d54e01ae3bedbd7bb0562fe48e61e509c86102bcc2c8babba1ba5498a6859a796adc697fa917459a455969a45c7d8645b29d938c0142b97fa3b5fb9c234d2390 + languageName: node + linkType: hard + +"luxon@npm:^1.24.1": + version: 1.28.1 + resolution: "luxon@npm:1.28.1" + checksum: 5c561ce4364bb2301ca5811c74d11a9e087f82164109c7997dc8f0959e64d51259d8e630914dca2edc6702525ce5ab066a4b85caa19d04be71f10e79ffe2bc84 + languageName: node + linkType: hard + +"lz-string@npm:^1.4.4": + version: 1.4.4 + resolution: "lz-string@npm:1.4.4" + bin: + lz-string: bin/bin.js + checksum: 683d2d01607444605bee9902b05851415ae54e4de75ff14971c7e070d0fab53a7f1f82e659f24e6ccdc63080832b937418e278a611ed4a354bf2e7ad6f0b874b + languageName: node + linkType: hard + +"magic-string@npm:^0.25.0, magic-string@npm:^0.25.7": + version: 0.25.9 + resolution: "magic-string@npm:0.25.9" + dependencies: + sourcemap-codec: ^1.4.8 + checksum: 37f5e01a7e8b19a072091f0b45ff127cda676232d373ce2c551a162dd4053c575ec048b9cbb4587a1f03adb6c5d0fd0dd49e8ab070cd2c83a4992b2182d9cb56 + languageName: node + linkType: hard + +"make-dir@npm:^1.0.0": + version: 1.3.0 + resolution: "make-dir@npm:1.3.0" + dependencies: + pify: ^3.0.0 + checksum: 5eb94f47d7ef41d89d1b8eef6539b8950d5bd99eeba093a942bfd327faa37d2d62227526b88b73633243a2ec7972d21eb0f4e5d62ae4e02a79e389f4a7bb3022 + languageName: node + linkType: hard + +"make-dir@npm:^2.0.0, make-dir@npm:^2.1.0": + version: 2.1.0 + resolution: "make-dir@npm:2.1.0" + dependencies: + pify: ^4.0.1 + semver: ^5.6.0 + checksum: ada869944d866229819735bee5548944caef560d7a8536ecbc6536edca28c72add47cc4f6fc39c54fb25d06b58da1f8994cf7d9df7dadea047064749efc085d8 + languageName: node + linkType: hard + +"make-dir@npm:^3.0.0, make-dir@npm:^3.0.2, make-dir@npm:^3.1.0": + version: 3.1.0 + resolution: "make-dir@npm:3.1.0" + dependencies: + semver: ^6.0.0 + checksum: 56aaafefc49c2dfef02c5c95f9b196c4eb6988040cf2c712185c7fe5c99b4091591a7fc4d4eafaaefa70ff763a26f6ab8c3ff60b9e75ea19876f49b18667ecaa + languageName: node + linkType: hard + +"make-error@npm:1.x, make-error@npm:^1.1.1": + version: 1.3.6 + resolution: "make-error@npm:1.3.6" + checksum: 171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f + languageName: node + linkType: hard + +"make-fetch-happen@npm:^10.0.3": + version: 10.2.1 + resolution: "make-fetch-happen@npm:10.2.1" + dependencies: + agentkeepalive: ^4.2.1 + cacache: ^16.1.0 + http-cache-semantics: ^4.1.0 + http-proxy-agent: ^5.0.0 + https-proxy-agent: ^5.0.0 + is-lambda: ^1.0.1 + lru-cache: ^7.7.1 + minipass: ^3.1.6 + minipass-collect: ^1.0.2 + minipass-fetch: ^2.0.3 + minipass-flush: ^1.0.5 + minipass-pipeline: ^1.2.4 + negotiator: ^0.6.3 + promise-retry: ^2.0.1 + socks-proxy-agent: ^7.0.0 + ssri: ^9.0.0 + checksum: 28ec392f63ab93511f400839dcee83107eeecfaad737d1e8487ea08b4332cd89a8f3319584222edd9f6f1d0833cf516691469496d46491863f9e88c658013949 + languageName: node + linkType: hard + +"make-fetch-happen@npm:^5.0.0": + version: 5.0.2 + resolution: "make-fetch-happen@npm:5.0.2" + dependencies: + agentkeepalive: ^3.4.1 + cacache: ^12.0.0 + http-cache-semantics: ^3.8.1 + http-proxy-agent: ^2.1.0 + https-proxy-agent: ^2.2.3 + lru-cache: ^5.1.1 + mississippi: ^3.0.0 + node-fetch-npm: ^2.0.2 + promise-retry: ^1.1.1 + socks-proxy-agent: ^4.0.0 + ssri: ^6.0.0 + checksum: 8c3b3b3614b976df6af31de27c9573dd7292236450ecb5911c4fef7dfe7847385e878972878dbd9ecd2143a60868d6fa5911b108128667efce6d93fc7c93c568 + languageName: node + linkType: hard + +"makeerror@npm:1.0.12": + version: 1.0.12 + resolution: "makeerror@npm:1.0.12" + dependencies: + tmpl: 1.0.5 + checksum: b0e6e599780ce6bab49cc413eba822f7d1f0dfebd1c103eaa3785c59e43e22c59018323cf9e1708f0ef5329e94a745d163fcbb6bff8e4c6742f9be9e86f3500c + languageName: node + linkType: hard + +"map-age-cleaner@npm:^0.1.3": + version: 0.1.3 + resolution: "map-age-cleaner@npm:0.1.3" + dependencies: + p-defer: ^1.0.0 + checksum: 7495236c7b0950956c144fd8b4bc6399d4e78072a8840a4232fe1c4faccbb5eb5d842e5c0a56a60afc36d723f315c1c672325ca03c1b328650f7fcc478f385fd + languageName: node + linkType: hard + +"map-cache@npm:^0.2.2": + version: 0.2.2 + resolution: "map-cache@npm:0.2.2" + checksum: 05e3eb005c1b80b9f949ca007687640e8c5d0fc88dc45c3c3ab4902a3bec79d66a58f3e3b04d6985d90cd267c629c7b46c977e9c34433e8c11ecfcbb9f0fa290 + languageName: node + linkType: hard + +"map-obj@npm:^1.0.0, map-obj@npm:^1.0.1": + version: 1.0.1 + resolution: "map-obj@npm:1.0.1" + checksum: ccca88395e7d38671ed9f5652ecf471ecd546924be2fb900836b9da35e068a96687d96a5f93dcdfa94d9a27d649d2f10a84595590f89a347fb4dda47629dcc52 + languageName: node + linkType: hard + +"map-or-similar@npm:^1.5.0": + version: 1.5.0 + resolution: "map-or-similar@npm:1.5.0" + checksum: 33c6ccfdc272992e33e4e99a69541a3e7faed9de3ac5bc732feb2500a9ee71d3f9d098980a70b7746e7eeb7f859ff7dfb8aa9b5ecc4e34170a32ab78cfb18def + languageName: node + linkType: hard + +"map-visit@npm:^1.0.0": + version: 1.0.0 + resolution: "map-visit@npm:1.0.0" + dependencies: + object-visit: ^1.0.0 + checksum: fb3475e5311939a6147e339999113db607adc11c7c3cd3103e5e9dbf502898416ecba6b1c7c649c6d4d12941de00cee58b939756bdf20a9efe7d4fa5a5738b73 + languageName: node + linkType: hard + +"markdown-escapes@npm:^1.0.0": + version: 1.0.4 + resolution: "markdown-escapes@npm:1.0.4" + checksum: cf3f2231191d9df61cd1d02a50a55a5c89ab9cebfe75572950f4844b93a41d561eed2d82e42732d55f2c55fa0d426b51df3a7f378b4068ae1e2923bb758a9cc8 + languageName: node + linkType: hard + +"md5-file@npm:5": + version: 5.0.0 + resolution: "md5-file@npm:5.0.0" + bin: + md5-file: cli.js + checksum: ef69092bcf28efcd82f3c5b40831eb6b23bae3133d5d63b58d2b77d1bc5f9a8a0e46d29ddb5167b7c939d6ae1ea7447332e722693ea08cc19cd36d306122f43a + languageName: node + linkType: hard + +"md5.js@npm:^1.3.4": + version: 1.3.5 + resolution: "md5.js@npm:1.3.5" + dependencies: + hash-base: ^3.0.0 + inherits: ^2.0.1 + safe-buffer: ^5.1.2 + checksum: b7bd75077f419c8e013fc4d4dada48be71882e37d69a44af65a2f2804b91e253441eb43a0614423a1c91bb830b8140b0dc906bc797245e2e275759584f4efcc5 + languageName: node + linkType: hard + +"mdast-squeeze-paragraphs@npm:^4.0.0": + version: 4.0.0 + resolution: "mdast-squeeze-paragraphs@npm:4.0.0" + dependencies: + unist-util-remove: ^2.0.0 + checksum: 0b44a85d7e6d98772b1dbb28a46a35c74c2791c6cf057bfd2e590a4e011d626627e5bf82d4497706f0dae03da02a63a9279aca17c4c23a9c7173792adba8e6fc + languageName: node + linkType: hard + +"mdast-util-definitions@npm:^4.0.0": + version: 4.0.0 + resolution: "mdast-util-definitions@npm:4.0.0" + dependencies: + unist-util-visit: ^2.0.0 + checksum: d81bb0b702f99878c8e8e4f66dd7f6f673ab341f061b3d9487ba47dad28b584e02f16b4c42df23714eaac8a7dd8544ba7d77308fad8d4a9fd0ac92e2a7f56be9 + languageName: node + linkType: hard + +"mdast-util-to-hast@npm:10.0.1": + version: 10.0.1 + resolution: "mdast-util-to-hast@npm:10.0.1" + dependencies: + "@types/mdast": ^3.0.0 + "@types/unist": ^2.0.0 + mdast-util-definitions: ^4.0.0 + mdurl: ^1.0.0 + unist-builder: ^2.0.0 + unist-util-generated: ^1.0.0 + unist-util-position: ^3.0.0 + unist-util-visit: ^2.0.0 + checksum: 08d0977c60ee951cb5e2e84bc821a842da463c37f7bbb79abf0be0894120ed5e2fc1d003d072d3bb968d8e813a916e132a094166d5562deb424acc45e1c661f4 + languageName: node + linkType: hard + +"mdast-util-to-string@npm:^1.0.0": + version: 1.1.0 + resolution: "mdast-util-to-string@npm:1.1.0" + checksum: 5dad9746ec0839792a8a35f504564e8d2b8c30013652410306c111963d33f1ee7b5477aa64ed77b64e13216363a29395809875ffd80e2031a08614657628a121 + languageName: node + linkType: hard + +"mdn-data@npm:2.0.14": + version: 2.0.14 + resolution: "mdn-data@npm:2.0.14" + checksum: 67241f8708c1e665a061d2b042d2d243366e93e5bf1f917693007f6d55111588b952dcbfd3ea9c2d0969fb754aad81b30fdcfdcc24546495fc3b24336b28d4bd + languageName: node + linkType: hard + +"mdurl@npm:^1.0.0": + version: 1.0.1 + resolution: "mdurl@npm:1.0.1" + checksum: ea8534341eb002aaa532a722daef6074cd8ca66202e10a2b4cda46722c1ebdb1da92197ac300bc953d3ef1bf41cd6561ef2cc69d82d5d0237dae00d4a61a4eee + languageName: node + linkType: hard + +"meant@npm:^1.0.3": + version: 1.0.3 + resolution: "meant@npm:1.0.3" + checksum: ca35218688a84c3d98f49bfe32058cd84a9d078fa91ad347f03e5a0d1fccad2e78ccfc54487617baa1ebb8a560986e5abc60873576e4b8e246f75e5d626e4283 + languageName: node + linkType: hard + +"media-typer@npm:0.3.0": + version: 0.3.0 + resolution: "media-typer@npm:0.3.0" + checksum: d160f31246907e79fed398470285f21bafb45a62869dc469b1c8877f3f064f5eabc4bcc122f9479b8b605bc5c76187d7871cf84c4ee3ecd3e487da1993279928 + languageName: node + linkType: hard + +"mem@npm:^8.1.1": + version: 8.1.1 + resolution: "mem@npm:8.1.1" + dependencies: + map-age-cleaner: ^0.1.3 + mimic-fn: ^3.1.0 + checksum: 5829c404d024c1accaf76ebacbc7eae9b59e5ce5722d184aa24e8387a8097a499f6aa7e181021003c51eb87b2dcdc9a2270050c58753cce761de206643cba91c + languageName: node + linkType: hard + +"memfs@npm:^3.1.2, memfs@npm:^3.2.2, memfs@npm:^3.4.3": + version: 3.4.13 + resolution: "memfs@npm:3.4.13" + dependencies: + fs-monkey: ^1.0.3 + checksum: f14ab3ff938eacf688577d1b0f7bf77ca3a05d4df9c335b024ed6790e6b224b569cc4b61c1de604c0420a0fac6b3fbf3f283c72fd2be9ce395534539599ac63b + languageName: node + linkType: hard + +"memoize-one@npm:^6.0.0": + version: 6.0.0 + resolution: "memoize-one@npm:6.0.0" + checksum: 45c88e064fd715166619af72e8cf8a7a17224d6edf61f7a8633d740ed8c8c0558a4373876c9b8ffc5518c2b65a960266adf403cc215cb1e90f7e262b58991f54 + languageName: node + linkType: hard + +"memoizerific@npm:^1.11.3": + version: 1.11.3 + resolution: "memoizerific@npm:1.11.3" + dependencies: + map-or-similar: ^1.5.0 + checksum: 661bf69b7afbfad57f0208f0c63324f4c96087b480708115b78ee3f0237d86c7f91347f6db31528740b2776c2e34c709bcb034e1e910edee2270c9603a0a469e + languageName: node + linkType: hard + +"memory-fs@npm:^0.4.1": + version: 0.4.1 + resolution: "memory-fs@npm:0.4.1" + dependencies: + errno: ^0.1.3 + readable-stream: ^2.0.1 + checksum: f114c44ad8285103cb0e71420cf5bb628d3eb6cbd918197f5951590ff56ba2072f4a97924949c170320cdf180d2da4e8d16a0edd92ba0ca2d2de51dc932841e2 + languageName: node + linkType: hard + +"memory-fs@npm:^0.5.0": + version: 0.5.0 + resolution: "memory-fs@npm:0.5.0" + dependencies: + errno: ^0.1.3 + readable-stream: ^2.0.1 + checksum: 2737a27b14a9e8b8cd757be2ad99e8cc504b78a78aba9d6aa18ff1ef528e2223a433413d2df6ab5332997a5a8ccf075e6c6e90e31ab732a55455ca620e4a720b + languageName: node + linkType: hard + +"meow@npm:^3.1.0": + version: 3.7.0 + resolution: "meow@npm:3.7.0" + dependencies: + camelcase-keys: ^2.0.0 + decamelize: ^1.1.2 + loud-rejection: ^1.0.0 + map-obj: ^1.0.1 + minimist: ^1.1.3 + normalize-package-data: ^2.3.4 + object-assign: ^4.0.1 + read-pkg-up: ^1.0.1 + redent: ^1.0.0 + trim-newlines: ^1.0.0 + checksum: e5ba4632b6558006b5f4df64b5a35e777d75629ab08d84f7bbc967e7603a396e16baa8f67aae26c7833a6a117e4857afef393e0b9aee21f52320e54812d9ae09 + languageName: node + linkType: hard + +"merge-descriptors@npm:1.0.1": + version: 1.0.1 + resolution: "merge-descriptors@npm:1.0.1" + checksum: b67d07bd44cfc45cebdec349bb6e1f7b077ee2fd5beb15d1f7af073849208cb6f144fe403e29a36571baf3f4e86469ac39acf13c318381e958e186b2766f54ec + languageName: node + linkType: hard + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 + languageName: node + linkType: hard + +"merge2@npm:^1.2.3, merge2@npm:^1.3.0, merge2@npm:^1.4.1": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb + languageName: node + linkType: hard + +"methods@npm:~1.1.2": + version: 1.1.2 + resolution: "methods@npm:1.1.2" + checksum: bdf7cc72ff0a33e3eede03708c08983c4d7a173f91348b4b1e4f47d4cdbf734433ad971e7d1e8c77247d9e5cd8adb81ea4c67b0a2db526b758b2233d7814b8b2 + languageName: node + linkType: hard + +"microevent.ts@npm:~0.1.1": + version: 0.1.1 + resolution: "microevent.ts@npm:0.1.1" + checksum: 1f18f23ebebf155d3f480b1414cec7667a477a09ced2c60705b204cfaba82cbecc76169d890b9a675f237cb1a5497ba744ca8619a65802ac6765148a05bb6bf4 + languageName: node + linkType: hard + +"micromatch@npm:4, micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5": + version: 4.0.5 + resolution: "micromatch@npm:4.0.5" + dependencies: + braces: ^3.0.2 + picomatch: ^2.3.1 + checksum: 3d6505b20f9fa804af5d8c596cb1c5e475b9b0cd05f652c5b56141cf941bd72adaeb7a436fda344235cef93a7f29b7472efc779fcdb83b478eab0867b95cdeff + languageName: node + linkType: hard + +"micromatch@npm:^3.1.10, micromatch@npm:^3.1.4": + version: 3.1.10 + resolution: "micromatch@npm:3.1.10" + dependencies: + arr-diff: ^4.0.0 + array-unique: ^0.3.2 + braces: ^2.3.1 + define-property: ^2.0.2 + extend-shallow: ^3.0.2 + extglob: ^2.0.4 + fragment-cache: ^0.2.1 + kind-of: ^6.0.2 + nanomatch: ^1.2.9 + object.pick: ^1.3.0 + regex-not: ^1.0.0 + snapdragon: ^0.8.1 + to-regex: ^3.0.2 + checksum: 531a32e7ac92bef60657820202be71b63d0f945c08a69cc4c239c0b19372b751483d464a850a2e3a5ff6cc9060641e43d44c303af104c1a27493d137d8af017f + languageName: node + linkType: hard + +"miller-rabin@npm:^4.0.0": + version: 4.0.1 + resolution: "miller-rabin@npm:4.0.1" + dependencies: + bn.js: ^4.0.0 + brorand: ^1.0.1 + bin: + miller-rabin: bin/miller-rabin + checksum: 26b2b96f6e49dbcff7faebb78708ed2f5f9ae27ac8cbbf1d7c08f83cf39bed3d418c0c11034dce997da70d135cc0ff6f3a4c15dc452f8e114c11986388a64346 + languageName: node + linkType: hard + +"mime-db@npm:1.52.0, mime-db@npm:>= 1.43.0 < 2": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-types@npm:2, mime-types@npm:^2.1.12, mime-types@npm:^2.1.14, mime-types@npm:^2.1.27, mime-types@npm:^2.1.30, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: 1.52.0 + checksum: 82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + +"mime@npm:1.6.0": + version: 1.6.0 + resolution: "mime@npm:1.6.0" + bin: + mime: cli.js + checksum: b92cd0adc44888c7135a185bfd0dddc42c32606401c72896a842ae15da71eb88858f17669af41e498b463cd7eb998f7b48939a25b08374c7924a9c8a6f8a81b0 + languageName: node + linkType: hard + +"mime@npm:^2.4.4": + version: 2.6.0 + resolution: "mime@npm:2.6.0" + bin: + mime: cli.js + checksum: a7f2589900d9c16e3bdf7672d16a6274df903da958c1643c9c45771f0478f3846dcb1097f31eb9178452570271361e2149310931ec705c037210fc69639c8e6c + languageName: node + linkType: hard + +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 + languageName: node + linkType: hard + +"mimic-fn@npm:^3.1.0": + version: 3.1.0 + resolution: "mimic-fn@npm:3.1.0" + checksum: a07cdd8ed6490c2dff5b11f889b245d9556b80f5a653a552a651d17cff5a2d156e632d235106c2369f00cccef4071704589574cf3601bc1b1400a1f620dff067 + languageName: node + linkType: hard + +"min-document@npm:^2.19.0": + version: 2.19.0 + resolution: "min-document@npm:2.19.0" + dependencies: + dom-walk: ^0.1.0 + checksum: 783724da716fc73a51c171865d7b29bf2b855518573f82ef61c40d214f6898d7b91b5c5419e4d22693cdb78d4615873ebc3b37d7639d3dd00ca283e5a07c7af9 + languageName: node + linkType: hard + +"min-indent@npm:^1.0.0": + version: 1.0.1 + resolution: "min-indent@npm:1.0.1" + checksum: 7e207bd5c20401b292de291f02913230cb1163abca162044f7db1d951fa245b174dc00869d40dd9a9f32a885ad6a5f3e767ee104cf278f399cb4e92d3f582d5c + languageName: node + linkType: hard + +"mini-css-extract-plugin@npm:^2.6.0": + version: 2.7.2 + resolution: "mini-css-extract-plugin@npm:2.7.2" + dependencies: + schema-utils: ^4.0.0 + peerDependencies: + webpack: ^5.0.0 + checksum: e9115811c81a8f875c59c5d37b4e69cc66560751d4faa8f596dc92bba1d394fd1c43173274027e8d59303d1df97c43a8685dac33e4e8b1fc031193356044193f + languageName: node + linkType: hard + +"minimalistic-assert@npm:^1.0.0, minimalistic-assert@npm:^1.0.1": + version: 1.0.1 + resolution: "minimalistic-assert@npm:1.0.1" + checksum: 96730e5601cd31457f81a296f521eb56036e6f69133c0b18c13fe941109d53ad23a4204d946a0d638d7f3099482a0cec8c9bb6d642604612ce43ee536be3dddd + languageName: node + linkType: hard + +"minimalistic-crypto-utils@npm:^1.0.1": + version: 1.0.1 + resolution: "minimalistic-crypto-utils@npm:1.0.1" + checksum: 790ecec8c5c73973a4fbf2c663d911033e8494d5fb0960a4500634766ab05d6107d20af896ca2132e7031741f19888154d44b2408ada0852446705441383e9f8 + languageName: node + linkType: hard + +"minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: ^1.1.7 + checksum: 0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 + languageName: node + linkType: hard + +"minimatch@npm:^5.0.1": + version: 5.1.6 + resolution: "minimatch@npm:5.1.6" + dependencies: + brace-expansion: ^2.0.1 + checksum: 3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3 + languageName: node + linkType: hard + +"minimatch@npm:~3.0.2": + version: 3.0.8 + resolution: "minimatch@npm:3.0.8" + dependencies: + brace-expansion: ^1.1.7 + checksum: 72b226f452dcfb5075255f53534cb83fc25565b909e79b9be4fad463d735cb1084827f7013ff41d050e77ee6e474408c6073473edd2fb72c2fd630cfb0acc6ad + languageName: node + linkType: hard + +"minimist@npm:^1.1.1, minimist@npm:^1.1.3, minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6": + version: 1.2.7 + resolution: "minimist@npm:1.2.7" + checksum: 8808da67ca50ee19ab2d69051d77ee78572e67297fd8a1635ecc757a15106ccdfb5b8c4d11d84750120142f1684e5329a141295728c755e5d149eedd73cc6572 + languageName: node + linkType: hard + +"minio@npm:^7.0.18": + version: 7.0.32 + resolution: "minio@npm:7.0.32" + dependencies: + async: ^3.1.0 + block-stream2: ^2.0.0 + browser-or-node: ^1.3.0 + buffer-crc32: ^0.2.13 + crypto-browserify: ^3.12.0 + es6-error: ^4.1.1 + fast-xml-parser: ^3.17.5 + ipaddr.js: ^2.0.1 + json-stream: ^1.0.0 + lodash: ^4.17.21 + mime-types: ^2.1.14 + mkdirp: ^0.5.1 + query-string: ^7.1.1 + through2: ^3.0.1 + web-encoding: ^1.1.5 + xml: ^1.0.0 + xml2js: ^0.4.15 + checksum: 6b6eb660003db751f9a19c514950761af4f9ae403b9ec14b388eae87e1a2a70bb6808f6d107977081a1f8ab86a4a034cf12a9bbb5ee890e6e3102569d85b9f4f + languageName: node + linkType: hard + +"minipass-collect@npm:^1.0.2": + version: 1.0.2 + resolution: "minipass-collect@npm:1.0.2" + dependencies: + minipass: ^3.0.0 + checksum: 8f82bd1f3095b24f53a991b04b67f4c710c894e518b813f0864a31de5570441a509be1ca17e0bb92b047591a8fdbeb886f502764fefb00d2f144f4011791e898 + languageName: node + linkType: hard + +"minipass-fetch@npm:^2.0.3": + version: 2.1.2 + resolution: "minipass-fetch@npm:2.1.2" + dependencies: + encoding: ^0.1.13 + minipass: ^3.1.6 + minipass-sized: ^1.0.3 + minizlib: ^2.1.2 + dependenciesMeta: + encoding: + optional: true + checksum: 33ab2c5bdb3d91b9cb8bc6ae42d7418f4f00f7f7beae14b3bb21ea18f9224e792f560a6e17b6f1be12bbeb70dbe99a269f4204c60e5d99130a0777b153505c43 + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: ^3.0.0 + checksum: 2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.2, minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: ^3.0.0 + checksum: cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: ^3.0.0 + checksum: 298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb + languageName: node + linkType: hard + +"minipass@npm:^2.3.5, minipass@npm:^2.6.0, minipass@npm:^2.9.0": + version: 2.9.0 + resolution: "minipass@npm:2.9.0" + dependencies: + safe-buffer: ^5.1.2 + yallist: ^3.0.0 + checksum: 307d8765ac3db9fcd6b486367e6f6c3e460f3a3e198d95d6c0005a2d95804c40c72959261cdebde3c8237cda0b03d4c01975e4581fe11abcf201f5005caafd2a + languageName: node + linkType: hard + +"minipass@npm:^3.0.0, minipass@npm:^3.1.1, minipass@npm:^3.1.6": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: ^4.0.0 + checksum: a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^4.0.0": + version: 4.0.0 + resolution: "minipass@npm:4.0.0" + dependencies: + yallist: ^4.0.0 + checksum: ea9a3eee82a33899693db3c25333ce15d1a900bb7ef47aa97d44db91d293bcbd9a3c5127da617b92844411fcaf6270c37f79755bbcbe11e5d329f60bad6d7229 + languageName: node + linkType: hard + +"minizlib@npm:^1.3.3": + version: 1.3.3 + resolution: "minizlib@npm:1.3.3" + dependencies: + minipass: ^2.9.0 + checksum: 79798032bbaa6594fa517e5b7ff9977951984fc9548a421b28d3fb0add8ed7e98a33e41e262af53b944f9d860c1e00fc778b477ef692e7b38b1ba12b390ffb17 + languageName: node + linkType: hard + +"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": + version: 2.1.2 + resolution: "minizlib@npm:2.1.2" + dependencies: + minipass: ^3.0.0 + yallist: ^4.0.0 + checksum: 64fae024e1a7d0346a1102bb670085b17b7f95bf6cfdf5b128772ec8faf9ea211464ea4add406a3a6384a7d87a0cd1a96263692134323477b4fb43659a6cab78 + languageName: node + linkType: hard + +"mississippi@npm:^3.0.0": + version: 3.0.0 + resolution: "mississippi@npm:3.0.0" + dependencies: + concat-stream: ^1.5.0 + duplexify: ^3.4.2 + end-of-stream: ^1.1.0 + flush-write-stream: ^1.0.0 + from2: ^2.1.0 + parallel-transform: ^1.1.0 + pump: ^3.0.0 + pumpify: ^1.3.3 + stream-each: ^1.1.0 + through2: ^2.0.0 + checksum: 97424a331ce1b9f789a0d3fa47d725dad9adfe5e0ead8bc458ba9fb51c4d2630df6b0966ca9dcbb4c90db48737d58126cbf0e3c170697bf41c265606efa91103 + languageName: node + linkType: hard + +"mixin-deep@npm:^1.2.0": + version: 1.3.2 + resolution: "mixin-deep@npm:1.3.2" + dependencies: + for-in: ^1.0.2 + is-extendable: ^1.0.1 + checksum: cb39ffb73c377222391af788b4c83d1a6cecb2d9fceb7015384f8deb46e151a9b030c21ef59a79cb524d4557e3f74c7248ab948a62a6e7e296b42644863d183b + languageName: node + linkType: hard + +"mkdirp-infer-owner@npm:^1.0.2": + version: 1.0.2 + resolution: "mkdirp-infer-owner@npm:1.0.2" + dependencies: + chownr: ^1.1.3 + infer-owner: ^1.0.4 + mkdirp: ^1.0.3 + checksum: 3ad67d5c66348e8f390448c19926e92d515e0452533a784467ce9f471228149f5b4b36014bda268c5fbc4df715e80f984bafc8e25605bd8cbbdca8656b9505eb + languageName: node + linkType: hard + +"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.3, mkdirp@npm:^0.5.5, mkdirp@npm:^0.5.6, mkdirp@npm:~0.5.0": + version: 0.5.6 + resolution: "mkdirp@npm:0.5.6" + dependencies: + minimist: ^1.2.6 + bin: + mkdirp: bin/cmd.js + checksum: e2e2be789218807b58abced04e7b49851d9e46e88a2f9539242cc8a92c9b5c3a0b9bab360bd3014e02a140fc4fbc58e31176c408b493f8a2a6f4986bd7527b01 + languageName: node + linkType: hard + +"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": + version: 1.0.4 + resolution: "mkdirp@npm:1.0.4" + bin: + mkdirp: bin/cmd.js + checksum: 46ea0f3ffa8bc6a5bc0c7081ffc3907777f0ed6516888d40a518c5111f8366d97d2678911ad1a6882bf592fa9de6c784fea32e1687bb94e1f4944170af48a5cf + languageName: node + linkType: hard + +"mobx-react-lite@npm:^3.1.6": + version: 3.4.0 + resolution: "mobx-react-lite@npm:3.4.0" + peerDependencies: + mobx: ^6.1.0 + react: ^16.8.0 || ^17 || ^18 + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + checksum: 7aa64fb8740d48de23c370385331063f0a0821a87ed7a1eb0324305e8ef2f5173806c3e5ff2bfb7696e5b80faf19de11e2d3c0b64268df21895d4eec4816e325 + languageName: node + linkType: hard + +"mobx@npm:^6.3.8": + version: 6.7.0 + resolution: "mobx@npm:6.7.0" + checksum: 91c09333073a6103d62cfcce133817402723eead9500a85ace8f39404b86ffdba19e9fc792fc626a9c872eeeae8437c445f80eedf6914f7f950b83d44f795b03 + languageName: node + linkType: hard + +"moment-locales-webpack-plugin@npm:^1.2.0": + version: 1.2.0 + resolution: "moment-locales-webpack-plugin@npm:1.2.0" + dependencies: + lodash.difference: ^4.5.0 + peerDependencies: + moment: ^2.8.0 + webpack: ^1 || ^2 || ^3 || ^4 || ^5 + checksum: 610c41c77d09e2d1bd8dd412493a3396c3e151080117939357a13b91a71e7782c44ee6405739a11d6ad7dfaf41179ca5eb770d79e3fbf2bf091f10088f0557a7 + languageName: node + linkType: hard + +"moment-range@npm:^4.0.2": + version: 4.0.2 + resolution: "moment-range@npm:4.0.2" + dependencies: + es6-symbol: ^3.1.0 + peerDependencies: + moment: ">= 2" + checksum: 9a847b81d44f14f26f607c3320a59e825ac7757be900951654c02db8aedb748e1e0c2b23aa5c896dbea94e0c696959cca0c16f02d68ec56a7b57e2378dac6bfa + languageName: node + linkType: hard + +"moment@npm:2.x, moment@npm:^2.29.4": + version: 2.29.4 + resolution: "moment@npm:2.29.4" + checksum: 844c6f3ce42862ac9467c8ca4f5e48a00750078682cc5bda1bc0e50cc7ca88e2115a0f932d65a06e4a90e26cb78892be9b3ca3dd6546ca2c4d994cebb787fc2b + languageName: node + linkType: hard + +"move-concurrently@npm:^1.0.1": + version: 1.0.1 + resolution: "move-concurrently@npm:1.0.1" + dependencies: + aproba: ^1.1.1 + copy-concurrently: ^1.0.0 + fs-write-stream-atomic: ^1.0.8 + mkdirp: ^0.5.1 + rimraf: ^2.5.4 + run-queue: ^1.0.3 + checksum: 0fe81acf3bbbc322013c2f4ee4a48cf8d180a7d925fb9284c0f1f444e862d7eb0421ee074b68d35357a12f0d5e94a322049dc9da480672331b5b8895743eb66a + languageName: node + linkType: hard + +"mrmime@npm:^1.0.0": + version: 1.0.1 + resolution: "mrmime@npm:1.0.1" + checksum: ab071441da76fd23b3b0d1823d77aacf8679d379a4a94cacd83e487d3d906763b277f3203a594c613602e31ab5209c26a8119b0477c4541ef8555b293a9db6d3 + languageName: node + linkType: hard + +"ms@npm:2.0.0": + version: 2.0.0 + resolution: "ms@npm:2.0.0" + checksum: f8fda810b39fd7255bbdc451c46286e549794fcc700dc9cd1d25658bbc4dc2563a5de6fe7c60f798a16a60c6ceb53f033cb353f493f0cf63e5199b702943159d + languageName: node + linkType: hard + +"ms@npm:2.1.1": + version: 2.1.1 + resolution: "ms@npm:2.1.1" + checksum: 056140c631e740369fa21142417aba1bd629ab912334715216c666eb681c8f015c622dd4e38bc1d836b30852b05641331661703af13a0397eb0ca420fc1e75d9 + languageName: node + linkType: hard + +"ms@npm:2.1.2": + version: 2.1.2 + resolution: "ms@npm:2.1.2" + checksum: a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc + languageName: node + linkType: hard + +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"multicast-dns@npm:^7.2.5": + version: 7.2.5 + resolution: "multicast-dns@npm:7.2.5" + dependencies: + dns-packet: ^5.2.2 + thunky: ^1.0.2 + bin: + multicast-dns: cli.js + checksum: 5120171d4bdb1577764c5afa96e413353bff530d1b37081cb29cccc747f989eb1baf40574fe8e27060fc1aef72b59c042f72b9b208413de33bcf411343c69057 + languageName: node + linkType: hard + +"mute-stream@npm:~0.0.4": + version: 0.0.8 + resolution: "mute-stream@npm:0.0.8" + checksum: 18d06d92e5d6d45e2b63c0e1b8f25376af71748ac36f53c059baa8b76ffac31c5ab225480494e7d35d30215ecdb18fed26ec23cafcd2f7733f2f14406bcd19e2 + languageName: node + linkType: hard + +"nan@npm:^2.12.1": + version: 2.17.0 + resolution: "nan@npm:2.17.0" + dependencies: + node-gyp: latest + checksum: 4a231a62dba025f4c4fa814c1e6ffeb450c5cd0852b780f19fe4ea22b86ba0f1f394406dfd628c67fb7f0987e982fa230da1fbd3632258f927b8defd7046c1ad + languageName: node + linkType: hard + +"nanoid@npm:^3.3.1, nanoid@npm:^3.3.4": + version: 3.3.4 + resolution: "nanoid@npm:3.3.4" + bin: + nanoid: bin/nanoid.cjs + checksum: a0747d5c6021828fe8d38334e5afb05d3268d7d4b06024058ec894ccc47070e4e81d268a6b75488d2ff3485fa79a75c251d4b7c6f31051bb54bb662b6fd2a27d + languageName: node + linkType: hard + +"nanomatch@npm:^1.2.9": + version: 1.2.13 + resolution: "nanomatch@npm:1.2.13" + dependencies: + arr-diff: ^4.0.0 + array-unique: ^0.3.2 + define-property: ^2.0.2 + extend-shallow: ^3.0.2 + fragment-cache: ^0.2.1 + is-windows: ^1.0.2 + kind-of: ^6.0.2 + object.pick: ^1.3.0 + regex-not: ^1.0.0 + snapdragon: ^0.8.1 + to-regex: ^3.0.1 + checksum: 0f5cefa755ca2e20c86332821995effb24acb79551ddaf51c1b9112628cad234a0d8fd9ac6aa56ad1f8bfad6ff6ae86e851acb960943249d9fa44b091479953a + languageName: node + linkType: hard + +"natural-compare-lite@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare-lite@npm:1.4.0" + checksum: f6cef26f5044515754802c0fc475d81426f3b90fe88c20fabe08771ce1f736ce46e0397c10acb569a4dd0acb84c7f1ee70676122f95d5bfdd747af3a6c6bbaa8 + languageName: node + linkType: hard + +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 + languageName: node + linkType: hard + +"negotiator@npm:0.6.3, negotiator@npm:^0.6.3": + version: 0.6.3 + resolution: "negotiator@npm:0.6.3" + checksum: 3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 + languageName: node + linkType: hard + +"neo-async@npm:^2.5.0, neo-async@npm:^2.6.0, neo-async@npm:^2.6.1, neo-async@npm:^2.6.2": + version: 2.6.2 + resolution: "neo-async@npm:2.6.2" + checksum: c2f5a604a54a8ec5438a342e1f356dff4bc33ccccdb6dc668d94fe8e5eccfc9d2c2eea6064b0967a767ba63b33763f51ccf2cd2441b461a7322656c1f06b3f5d + languageName: node + linkType: hard + +"nested-error-stacks@npm:^2.0.0, nested-error-stacks@npm:^2.1.0": + version: 2.1.1 + resolution: "nested-error-stacks@npm:2.1.1" + checksum: feec00417e4778661cfbbe657e6add6ca9918dcc026cd697ac330b4a56a79e4882b36dde8abc138167566b1ce4c5baa17d2d4df727a96f8b96aebace1c3ffca7 + languageName: node + linkType: hard + +"next-tick@npm:^1.1.0": + version: 1.1.0 + resolution: "next-tick@npm:1.1.0" + checksum: 3ba80dd805fcb336b4f52e010992f3e6175869c8d88bf4ff0a81d5d66e6049f89993463b28211613e58a6b7fe93ff5ccbba0da18d4fa574b96289e8f0b577f28 + languageName: node + linkType: hard + +"nice-try@npm:^1.0.4": + version: 1.0.5 + resolution: "nice-try@npm:1.0.5" + checksum: 95568c1b73e1d0d4069a3e3061a2102d854513d37bcfda73300015b7ba4868d3b27c198d1dbbd8ebdef4112fc2ed9e895d4a0f2e1cce0bd334f2a1346dc9205f + languageName: node + linkType: hard + +"no-case@npm:^3.0.4": + version: 3.0.4 + resolution: "no-case@npm:3.0.4" + dependencies: + lower-case: ^2.0.2 + tslib: ^2.0.3 + checksum: 8ef545f0b3f8677c848f86ecbd42ca0ff3cd9dd71c158527b344c69ba14710d816d8489c746b6ca225e7b615108938a0bda0a54706f8c255933703ac1cf8e703 + languageName: node + linkType: hard + +"node-addon-api@npm:^1.7.1": + version: 1.7.2 + resolution: "node-addon-api@npm:1.7.2" + dependencies: + node-gyp: latest + checksum: bcf526f2ce788182730d3c3df5206585873d1e837a6e1378ff84abccf2f19cf3f93a8274f9c1245af0de63a0dbd1bb95ca2f767ecf5c678d6930326aaf396c4e + languageName: node + linkType: hard + +"node-dir@npm:^0.1.10": + version: 0.1.17 + resolution: "node-dir@npm:0.1.17" + dependencies: + minimatch: ^3.0.2 + checksum: 16222e871708c405079ff8122d4a7e1d522c5b90fc8f12b3112140af871cfc70128c376e845dcd0044c625db0d2efebd2d852414599d240564db61d53402b4c1 + languageName: node + linkType: hard + +"node-environment-flags@npm:^1.0.5": + version: 1.0.6 + resolution: "node-environment-flags@npm:1.0.6" + dependencies: + object.getownpropertydescriptors: ^2.0.3 + semver: ^5.7.0 + checksum: 8be86f294f8b065a1e126e9ceb7a4b38b75eb7ec6391060e6e093ab9649e5c1fa977f2a5fe799b6ada862d65ce8259d1b7eabf2057774d641306e467d58cb96b + languageName: node + linkType: hard + +"node-fetch-npm@npm:^2.0.2": + version: 2.0.4 + resolution: "node-fetch-npm@npm:2.0.4" + dependencies: + encoding: ^0.1.11 + json-parse-better-errors: ^1.0.0 + safe-buffer: ^5.1.1 + checksum: f8188ad6f2c4de5b105aa8428c4fb4189616a38b68b4abe1f08b27fe2857238f7554c2eb56ff8404a2179576b0cdd9a5ab3129025a05806b02531bdc7173fc20 + languageName: node + linkType: hard + +"node-fetch@npm:2.6.7": + version: 2.6.7 + resolution: "node-fetch@npm:2.6.7" + dependencies: + whatwg-url: ^5.0.0 + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: fcae80f5ac52fbf5012f5e19df2bd3915e67d3b3ad51cb5942943df2238d32ba15890fecabd0e166876a9f98a581ab50f3f10eb942b09405c49ef8da36b826c7 + languageName: node + linkType: hard + +"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7": + version: 2.6.8 + resolution: "node-fetch@npm:2.6.8" + dependencies: + whatwg-url: ^5.0.0 + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 4e3cfdeb3e5e0e6d1192be08942d6530d74b0ac4fe6d8fc1cbcc3e7f274807b4f662ff1218393ba9e5e0e0c15c66365128726ed5400585feda707c31234d1f16 + languageName: node + linkType: hard + +"node-forge@npm:^1": + version: 1.3.1 + resolution: "node-forge@npm:1.3.1" + checksum: e882819b251a4321f9fc1d67c85d1501d3004b4ee889af822fd07f64de3d1a8e272ff00b689570af0465d65d6bf5074df9c76e900e0aff23e60b847f2a46fbe8 + languageName: node + linkType: hard + +"node-gyp@npm:^5.0.2, node-gyp@npm:^5.1.1": + version: 5.1.1 + resolution: "node-gyp@npm:5.1.1" + dependencies: + env-paths: ^2.2.0 + glob: ^7.1.4 + graceful-fs: ^4.2.2 + mkdirp: ^0.5.1 + nopt: ^4.0.1 + npmlog: ^4.1.2 + request: ^2.88.0 + rimraf: ^2.6.3 + semver: ^5.7.1 + tar: ^4.4.12 + which: ^1.3.1 + bin: + node-gyp: bin/node-gyp.js + checksum: b8e68a8fd241e2d2177b28e40c3cec8153b3ff58df8cd5cd8a5ee66070431ac3d87ddbe3c265097e1f27513f48b186801be18e7de0d979ec7faf492909425b9a + languageName: node + linkType: hard + +"node-gyp@npm:^9.0.0, node-gyp@npm:latest": + version: 9.3.1 + resolution: "node-gyp@npm:9.3.1" + dependencies: + env-paths: ^2.2.0 + glob: ^7.1.4 + graceful-fs: ^4.2.6 + make-fetch-happen: ^10.0.3 + nopt: ^6.0.0 + npmlog: ^6.0.0 + rimraf: ^3.0.2 + semver: ^7.3.5 + tar: ^6.1.2 + which: ^2.0.2 + bin: + node-gyp: bin/node-gyp.js + checksum: 3285c110768eb65aadd9aa1d056f917e594ea22611d21fd535ab3677ea433d0a281e7f09bc73d53e64b02214f4379dbca476dc33faffe455b0ac1d5ba92802f4 + languageName: node + linkType: hard + +"node-int64@npm:^0.4.0": + version: 0.4.0 + resolution: "node-int64@npm:0.4.0" + checksum: a6a4d8369e2f2720e9c645255ffde909c0fbd41c92ea92a5607fc17055955daac99c1ff589d421eee12a0d24e99f7bfc2aabfeb1a4c14742f6c099a51863f31a + languageName: node + linkType: hard + +"node-libs-browser@npm:^2.2.1": + version: 2.2.1 + resolution: "node-libs-browser@npm:2.2.1" + dependencies: + assert: ^1.1.1 + browserify-zlib: ^0.2.0 + buffer: ^4.3.0 + console-browserify: ^1.1.0 + constants-browserify: ^1.0.0 + crypto-browserify: ^3.11.0 + domain-browser: ^1.1.1 + events: ^3.0.0 + https-browserify: ^1.0.0 + os-browserify: ^0.3.0 + path-browserify: 0.0.1 + process: ^0.11.10 + punycode: ^1.2.4 + querystring-es3: ^0.2.0 + readable-stream: ^2.3.3 + stream-browserify: ^2.0.1 + stream-http: ^2.7.2 + string_decoder: ^1.0.0 + timers-browserify: ^2.0.4 + tty-browserify: 0.0.0 + url: ^0.11.0 + util: ^0.11.0 + vm-browserify: ^1.0.1 + checksum: 0e05321a6396408903ed642231d2bca7dd96492d074c7af161ba06a63c95378bd3de50b4105eccbbc02d93ba3da69f0ff5e624bc2a8c92ca462ceb6a403e7986 + languageName: node + linkType: hard + +"node-releases@npm:^2.0.6": + version: 2.0.8 + resolution: "node-releases@npm:2.0.8" + checksum: 4b58f44de428b630fae7e8bcd7bf8e4030dab628122acff3ce2df6bb480c00b97319bb9408e3be23b258329350c31e1cb27a232ba548c35f3bce2d647717be58 + languageName: node + linkType: hard + +"nopt@npm:^4.0.1, nopt@npm:^4.0.3": + version: 4.0.3 + resolution: "nopt@npm:4.0.3" + dependencies: + abbrev: 1 + osenv: ^0.1.4 + bin: + nopt: bin/nopt.js + checksum: 03e54cdf8c9b46924cfadf333b2b86fc180410d74d51f9c72fec5ef9c6f1a19ec533f647c05e40d49ef7491af59664c5d0baace808d6ccfe3ff064ae630a61b4 + languageName: node + linkType: hard + +"nopt@npm:^6.0.0": + version: 6.0.0 + resolution: "nopt@npm:6.0.0" + dependencies: + abbrev: ^1.0.0 + bin: + nopt: bin/nopt.js + checksum: 837b52c330df16fcaad816b1f54fec6b2854ab1aa771d935c1603fbcf9b023bb073f1466b1b67f48ea4dce127ae675b85b9d9355700e9b109de39db490919786 + languageName: node + linkType: hard + +"normalize-package-data@npm:^2.0.0, normalize-package-data@npm:^2.3.2, normalize-package-data@npm:^2.3.4, normalize-package-data@npm:^2.4.0, normalize-package-data@npm:^2.5.0": + version: 2.5.0 + resolution: "normalize-package-data@npm:2.5.0" + dependencies: + hosted-git-info: ^2.1.4 + resolve: ^1.10.0 + semver: 2 || 3 || 4 || 5 + validate-npm-package-license: ^3.0.1 + checksum: 357cb1646deb42f8eb4c7d42c4edf0eec312f3628c2ef98501963cc4bbe7277021b2b1d977f982b2edce78f5a1014613ce9cf38085c3df2d76730481357ca504 + languageName: node + linkType: hard + +"normalize-path@npm:^2.1.1": + version: 2.1.1 + resolution: "normalize-path@npm:2.1.1" + dependencies: + remove-trailing-separator: ^1.0.1 + checksum: db814326ff88057437233361b4c7e9cac7b54815b051b57f2d341ce89b1d8ec8cbd43e7fa95d7652b3b69ea8fcc294b89b8530d556a84d1bdace94229e1e9a8b + languageName: node + linkType: hard + +"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 + languageName: node + linkType: hard + +"normalize-range@npm:^0.1.2": + version: 0.1.2 + resolution: "normalize-range@npm:0.1.2" + checksum: bf39b73a63e0a42ad1a48c2bd1bda5a07ede64a7e2567307a407674e595bcff0fa0d57e8e5f1e7fa5e91000797c7615e13613227aaaa4d6d6e87f5bd5cc95de6 + languageName: node + linkType: hard + +"normalize-url@npm:^6.0.1": + version: 6.1.0 + resolution: "normalize-url@npm:6.1.0" + checksum: 95d948f9bdd2cfde91aa786d1816ae40f8262946e13700bf6628105994fe0ff361662c20af3961161c38a119dc977adeb41fc0b41b1745eb77edaaf9cb22db23 + languageName: node + linkType: hard + +"npm-audit-report@npm:^1.3.3": + version: 1.3.3 + resolution: "npm-audit-report@npm:1.3.3" + dependencies: + cli-table3: ^0.5.0 + console-control-strings: ^1.1.0 + checksum: cd4450a077fafb65985042cfdf48b1be8c2b2caa2012b413e52153185f5e30c38dbbdf521be884c064c68ee1f70ba7204898c680b4aea3742c989a0f7088d681 + languageName: node + linkType: hard + +"npm-bundled@npm:^1.0.1": + version: 1.1.2 + resolution: "npm-bundled@npm:1.1.2" + dependencies: + npm-normalize-package-bin: ^1.0.1 + checksum: 3f2337789afc8cb608a0dd71cefe459531053d48a5497db14b07b985c4cab15afcae88600db9f92eae072c89b982eeeec8e4463e1d77bc03a7e90f5dacf29769 + languageName: node + linkType: hard + +"npm-cache-filename@npm:~1.0.2": + version: 1.0.2 + resolution: "npm-cache-filename@npm:1.0.2" + checksum: 46910566dee0e1ec442d8c143dc14cd17aa352e1edf79f8b46c6c09616a6150109ba53f9f911ce1b0c53df08ae09421a1539df911dc3249334c75ccb179d0977 + languageName: node + linkType: hard + +"npm-install-checks@npm:^3.0.2": + version: 3.0.2 + resolution: "npm-install-checks@npm:3.0.2" + dependencies: + semver: ^2.3.0 || 3.x || 4 || 5 + checksum: 823c7ce77fce10bf43d0af6473dc4116aee60700203047f9959bceadd69274fe8ebbe9c6e635e558dcec6a169fdb7881cc854e87dcfc1a9c2c162efa1e7ec056 + languageName: node + linkType: hard + +"npm-install-peers@npm:^1.2.2": + version: 1.2.2 + resolution: "npm-install-peers@npm:1.2.2" + dependencies: + chalk: ^1.1.3 + npm: < 7.0 + bin: + npm-install-peers: bin/npm-install-peers.js + checksum: 03f2e7480e0b21b16c768bae6518c7252f8f40e5c4ab8ca885db3bc2e592c0aeb0c0bb98e20339e40584b3f998ad21ad93ec0ce617a8ccc23ae3c8b467c2009f + languageName: node + linkType: hard + +"npm-lifecycle@npm:^3.0.0, npm-lifecycle@npm:^3.1.5": + version: 3.1.5 + resolution: "npm-lifecycle@npm:3.1.5" + dependencies: + byline: ^5.0.0 + graceful-fs: ^4.1.15 + node-gyp: ^5.0.2 + resolve-from: ^4.0.0 + slide: ^1.1.6 + uid-number: 0.0.6 + umask: ^1.1.0 + which: ^1.3.1 + checksum: 73ac1a606298b68994ee45415065f7248358018ff03009a0b43709d9adda126e1bbd1f76e92a39fda14fb20b45cd146f4f67ab9a07dba973cf831f2fea035783 + languageName: node + linkType: hard + +"npm-logical-tree@npm:^1.2.1": + version: 1.2.1 + resolution: "npm-logical-tree@npm:1.2.1" + checksum: 1e909ce7c7bf3da6d5358c4d9b42ec0e029cd601ac4424b5072faff2d237de0485fcacdc32fd0069de1329418875a391cc09f0c4c23fe3ae9a2e8a2724f4d53d + languageName: node + linkType: hard + +"npm-normalize-package-bin@npm:^1.0.0, npm-normalize-package-bin@npm:^1.0.1": + version: 1.0.1 + resolution: "npm-normalize-package-bin@npm:1.0.1" + checksum: b0c8c05fe419a122e0ff970ccbe7874ae24b4b4b08941a24d18097fe6e1f4b93e3f6abfb5512f9c5488827a5592f2fb3ce2431c41d338802aed24b9a0c160551 + languageName: node + linkType: hard + +"npm-package-arg@npm:^4.0.0 || ^5.0.0 || ^6.0.0, npm-package-arg@npm:^6.0.0, npm-package-arg@npm:^6.1.0, npm-package-arg@npm:^6.1.1": + version: 6.1.1 + resolution: "npm-package-arg@npm:6.1.1" + dependencies: + hosted-git-info: ^2.7.1 + osenv: ^0.1.5 + semver: ^5.6.0 + validate-npm-package-name: ^3.0.0 + checksum: a653531d9136d7f8049f92a89d6806ebedb467fe859ea7f37ff0c17bf8d90c9aade6ca9d823baaa963795c49eef66d423be69b511fbe762aff94e47424057082 + languageName: node + linkType: hard + +"npm-packlist@npm:^1.1.12, npm-packlist@npm:^1.4.8": + version: 1.4.8 + resolution: "npm-packlist@npm:1.4.8" + dependencies: + ignore-walk: ^3.0.1 + npm-bundled: ^1.0.1 + npm-normalize-package-bin: ^1.0.1 + checksum: 3b6dd1d0f677a3c1ad8e5f59362f4249459ad9fbb31c8a9306c0cf2af74016078d17a37fffee66b5437e76aba33c7ceb008905bccbadb23ea4776171d4b22b92 + languageName: node + linkType: hard + +"npm-pick-manifest@npm:^3.0.0, npm-pick-manifest@npm:^3.0.2": + version: 3.0.2 + resolution: "npm-pick-manifest@npm:3.0.2" + dependencies: + figgy-pudding: ^3.5.1 + npm-package-arg: ^6.0.0 + semver: ^5.4.1 + checksum: f17fc70a187888ca25f7e3edeb3330e1adce7364bf6d1329bffb614f6b05825bbb8002ccc25983486126647eb2a907b237eb5b8b9cdb9782369ce5e065e51e2c + languageName: node + linkType: hard + +"npm-profile@npm:^4.0.2, npm-profile@npm:^4.0.4": + version: 4.0.4 + resolution: "npm-profile@npm:4.0.4" + dependencies: + aproba: ^1.1.2 || 2 + figgy-pudding: ^3.4.1 + npm-registry-fetch: ^4.0.0 + checksum: 48c5aa27880bbdf2c5561fa209c5e34566dc9a9856872a164a90ba3e4187567b3bb227515fdd0ec219a92215f1739bccf58d48306cefe223a15408d143eb3e31 + languageName: node + linkType: hard + +"npm-registry-fetch@npm:^4.0.0, npm-registry-fetch@npm:^4.0.7": + version: 4.0.7 + resolution: "npm-registry-fetch@npm:4.0.7" + dependencies: + JSONStream: ^1.3.4 + bluebird: ^3.5.1 + figgy-pudding: ^3.4.1 + lru-cache: ^5.1.1 + make-fetch-happen: ^5.0.0 + npm-package-arg: ^6.1.0 + safe-buffer: ^5.2.0 + checksum: dc7e7195567797b7b10c6d7605338b2a5f97301e738ab696d95942f2b0da2d4135cba816e40691ff82fa14192ca003487f44415f75809f7be737d7b996629462 + languageName: node + linkType: hard + +"npm-run-path@npm:^2.0.0": + version: 2.0.2 + resolution: "npm-run-path@npm:2.0.2" + dependencies: + path-key: ^2.0.0 + checksum: 95549a477886f48346568c97b08c4fda9cdbf7ce8a4fbc2213f36896d0d19249e32d68d7451bdcbca8041b5fba04a6b2c4a618beaf19849505c05b700740f1de + languageName: node + linkType: hard + +"npm-run-path@npm:^4.0.0, npm-run-path@npm:^4.0.1": + version: 4.0.1 + resolution: "npm-run-path@npm:4.0.1" + dependencies: + path-key: ^3.0.0 + checksum: 6f9353a95288f8455cf64cbeb707b28826a7f29690244c1e4bb61ec573256e021b6ad6651b394eb1ccfd00d6ec50147253aba2c5fe58a57ceb111fad62c519ac + languageName: node + linkType: hard + +"npm-user-validate@npm:^1.0.1": + version: 1.0.1 + resolution: "npm-user-validate@npm:1.0.1" + checksum: b6533da7df07c4495e8e209eba7191846683443503897e10e0acfb52fedefde34028f221b7ee5ae45b79ada13748a8e881a20392cd0fb93d190b1bf54ef1ee42 + languageName: node + linkType: hard + +"npm@npm:< 7.0": + version: 6.14.18 + resolution: "npm@npm:6.14.18" + dependencies: + JSONStream: ^1.3.5 + abbrev: ~1.1.1 + ansicolors: ~0.3.2 + ansistyles: ~0.1.3 + aproba: ^2.0.0 + archy: ~1.0.0 + bin-links: ^1.1.8 + bluebird: ^3.7.2 + byte-size: ^5.0.1 + cacache: ^12.0.4 + call-limit: ^1.1.1 + chownr: ^1.1.4 + ci-info: ^2.0.0 + cli-columns: ^3.1.2 + cli-table3: ^0.5.1 + cmd-shim: ^3.0.3 + columnify: ~1.5.4 + config-chain: ^1.1.13 + debuglog: "*" + detect-indent: ~5.0.0 + detect-newline: ^2.1.0 + dezalgo: ^1.0.4 + editor: ~1.0.0 + figgy-pudding: ^3.5.2 + find-npm-prefix: ^1.0.2 + fs-vacuum: ~1.2.10 + fs-write-stream-atomic: ~1.0.10 + gentle-fs: ^2.3.1 + glob: ^7.2.3 + graceful-fs: ^4.2.10 + has-unicode: ~2.0.1 + hosted-git-info: ^2.8.9 + iferr: ^1.0.2 + imurmurhash: "*" + infer-owner: ^1.0.4 + inflight: ~1.0.6 + inherits: ^2.0.4 + ini: ^1.3.8 + init-package-json: ^1.10.3 + is-cidr: ^3.1.1 + json-parse-better-errors: ^1.0.2 + lazy-property: ~1.0.0 + libcipm: ^4.0.8 + libnpm: ^3.0.1 + libnpmaccess: ^3.0.2 + libnpmhook: ^5.0.3 + libnpmorg: ^1.0.1 + libnpmsearch: ^2.0.2 + libnpmteam: ^1.0.2 + libnpx: ^10.2.4 + lock-verify: ^2.2.2 + lockfile: ^1.0.4 + lodash._baseindexof: "*" + lodash._baseuniq: ~4.6.0 + lodash._bindcallback: "*" + lodash._cacheindexof: "*" + lodash._createcache: "*" + lodash._getnative: "*" + lodash.clonedeep: ~4.5.0 + lodash.restparam: "*" + lodash.union: ~4.6.0 + lodash.uniq: ~4.5.0 + lodash.without: ~4.4.0 + lru-cache: ^5.1.1 + meant: ^1.0.3 + mississippi: ^3.0.0 + mkdirp: ^0.5.6 + move-concurrently: ^1.0.1 + node-gyp: ^5.1.1 + nopt: ^4.0.3 + normalize-package-data: ^2.5.0 + npm-audit-report: ^1.3.3 + npm-cache-filename: ~1.0.2 + npm-install-checks: ^3.0.2 + npm-lifecycle: ^3.1.5 + npm-package-arg: ^6.1.1 + npm-packlist: ^1.4.8 + npm-pick-manifest: ^3.0.2 + npm-profile: ^4.0.4 + npm-registry-fetch: ^4.0.7 + npm-user-validate: ^1.0.1 + npmlog: ~4.1.2 + once: ~1.4.0 + opener: ^1.5.2 + osenv: ^0.1.5 + pacote: ^9.5.12 + path-is-inside: ~1.0.2 + promise-inflight: ~1.0.1 + qrcode-terminal: ^0.12.0 + query-string: ^6.14.1 + qw: ^1.0.2 + read: ~1.0.7 + read-cmd-shim: ^1.0.5 + read-installed: ~4.0.3 + read-package-json: ^2.1.2 + read-package-tree: ^5.3.1 + readable-stream: ^3.6.0 + readdir-scoped-modules: ^1.1.0 + request: ^2.88.2 + retry: ^0.12.0 + rimraf: ^2.7.1 + safe-buffer: ^5.2.1 + semver: ^5.7.1 + sha: ^3.0.0 + slide: ~1.1.6 + sorted-object: ~2.0.1 + sorted-union-stream: ~2.1.3 + ssri: ^6.0.2 + stringify-package: ^1.0.1 + tar: ^4.4.19 + text-table: ~0.2.0 + tiny-relative-date: ^1.3.0 + uid-number: 0.0.6 + umask: ~1.1.0 + unique-filename: ^1.1.1 + unpipe: ~1.0.0 + update-notifier: ^2.5.0 + uuid: ^3.4.0 + validate-npm-package-license: ^3.0.4 + validate-npm-package-name: ~3.0.0 + which: ^1.3.1 + worker-farm: ^1.7.0 + write-file-atomic: ^2.4.3 + bin: + npm: bin/npm-cli.js + npx: bin/npx-cli.js + checksum: 12c7280971f84f25e0e229af7d130f6601efb22193814710749bb33e35c20071b9d2db44002f3215dee6cbd2021c01c58e2e49a992a669e64fe38d0a7868bde9 + languageName: node + linkType: hard + +"npmlog@npm:^4.1.2, npmlog@npm:~4.1.2": + version: 4.1.2 + resolution: "npmlog@npm:4.1.2" + dependencies: + are-we-there-yet: ~1.1.2 + console-control-strings: ~1.1.0 + gauge: ~2.7.3 + set-blocking: ~2.0.0 + checksum: d6a26cb362277c65e24a70ebdaff31f81184ceb5415fd748abaaf26417bf0794a17ba849116e4f454a0370b9067ae320834cc78d74527dbeadf6e9d19a959046 + languageName: node + linkType: hard + +"npmlog@npm:^5.0.1": + version: 5.0.1 + resolution: "npmlog@npm:5.0.1" + dependencies: + are-we-there-yet: ^2.0.0 + console-control-strings: ^1.1.0 + gauge: ^3.0.0 + set-blocking: ^2.0.0 + checksum: 489ba519031013001135c463406f55491a17fc7da295c18a04937fe3a4d523fd65e88dd418a28b967ab743d913fdeba1e29838ce0ad8c75557057c481f7d49fa + languageName: node + linkType: hard + +"npmlog@npm:^6.0.0": + version: 6.0.2 + resolution: "npmlog@npm:6.0.2" + dependencies: + are-we-there-yet: ^3.0.0 + console-control-strings: ^1.1.0 + gauge: ^4.0.3 + set-blocking: ^2.0.0 + checksum: 0cacedfbc2f6139c746d9cd4a85f62718435ad0ca4a2d6459cd331dd33ae58206e91a0742c1558634efcde3f33f8e8e7fd3adf1bfe7978310cf00bd55cccf890 + languageName: node + linkType: hard + +"nth-check@npm:^2.0.1": + version: 2.1.1 + resolution: "nth-check@npm:2.1.1" + dependencies: + boolbase: ^1.0.0 + checksum: 5fee7ff309727763689cfad844d979aedd2204a817fbaaf0e1603794a7c20db28548d7b024692f953557df6ce4a0ee4ae46cd8ebd9b36cfb300b9226b567c479 + languageName: node + linkType: hard + +"num2fraction@npm:^1.2.2": + version: 1.2.2 + resolution: "num2fraction@npm:1.2.2" + checksum: 3bf17b44af00508a2b0370146629710645c3e3ff3c052893680efe3f4a6ff5c953ce9e54734013b02b35744a49352d54fbc5d8b455fac979047ef17dd8ec74bd + languageName: node + linkType: hard + +"number-is-nan@npm:^1.0.0": + version: 1.0.1 + resolution: "number-is-nan@npm:1.0.1" + checksum: cb97149006acc5cd512c13c1838223abdf202e76ddfa059c5e8e7507aff2c3a78cd19057516885a2f6f5b576543dc4f7b6f3c997cc7df53ae26c260855466df5 + languageName: node + linkType: hard + +"oauth-sign@npm:~0.9.0": + version: 0.9.0 + resolution: "oauth-sign@npm:0.9.0" + checksum: fc92a516f6ddbb2699089a2748b04f55c47b6ead55a77cd3a2cbbce5f7af86164cb9425f9ae19acfd066f1ad7d3a96a67b8928c6ea946426f6d6c29e448497c2 + languageName: node + linkType: hard + +"object-assign@npm:4.x, object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + +"object-assign@npm:^3.0.0": + version: 3.0.0 + resolution: "object-assign@npm:3.0.0" + checksum: 88d1b93de35a2073df309239486829b8b3252ef86196a0d5e06fae20f9e6e5830a33ccaa1c675bd63191eec4310961797ff577fa586583a92a09ece53e668416 + languageName: node + linkType: hard + +"object-copy@npm:^0.1.0": + version: 0.1.0 + resolution: "object-copy@npm:0.1.0" + dependencies: + copy-descriptor: ^0.1.0 + define-property: ^0.2.5 + kind-of: ^3.0.3 + checksum: 79314b05e9d626159a04f1d913f4c4aba9eae8848511cf5f4c8e3b04bb3cc313b65f60357f86462c959a14c2d58380fedf89b6b32ecec237c452a5ef3900a293 + languageName: node + linkType: hard + +"object-hash@npm:^3.0.0": + version: 3.0.0 + resolution: "object-hash@npm:3.0.0" + checksum: a06844537107b960c1c8b96cd2ac8592a265186bfa0f6ccafe0d34eabdb526f6fa81da1f37c43df7ed13b12a4ae3457a16071603bcd39d8beddb5f08c37b0f47 + languageName: node + linkType: hard + +"object-inspect@npm:^1.12.2, object-inspect@npm:^1.9.0": + version: 1.12.3 + resolution: "object-inspect@npm:1.12.3" + checksum: 752bb5f4dc595e214157ea8f442adb77bdb850ace762b078d151d8b6486331ab12364997a89ee6509be1023b15adf2b3774437a7105f8a5043dfda11ed622411 + languageName: node + linkType: hard + +"object-is@npm:^1.1.5": + version: 1.1.5 + resolution: "object-is@npm:1.1.5" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.3 + checksum: 8c263fb03fc28f1ffb54b44b9147235c5e233dc1ca23768e7d2569740b5d860154d7cc29a30220fe28ed6d8008e2422aefdebfe987c103e1c5d190cf02d9d886 + languageName: node + linkType: hard + +"object-keys@npm:^1.1.1": + version: 1.1.1 + resolution: "object-keys@npm:1.1.1" + checksum: b11f7ccdbc6d406d1f186cdadb9d54738e347b2692a14439ca5ac70c225fa6db46db809711b78589866d47b25fc3e8dee0b4c722ac751e11180f9380e3d8601d + languageName: node + linkType: hard + +"object-visit@npm:^1.0.0": + version: 1.0.1 + resolution: "object-visit@npm:1.0.1" + dependencies: + isobject: ^3.0.0 + checksum: 086b475bda24abd2318d2b187c3e928959b89f5cb5883d6fe5a42d03719b61fc18e765f658de9ac8730e67ba9ff26d61e73d991215948ff9ecefe771e0071029 + languageName: node + linkType: hard + +"object.assign@npm:^4.1.3, object.assign@npm:^4.1.4": + version: 4.1.4 + resolution: "object.assign@npm:4.1.4" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + has-symbols: ^1.0.3 + object-keys: ^1.1.1 + checksum: 2f286118c023e557757620e647b02e7c88d3d417e0c568fca0820de8ec9cca68928304854d5b03e99763eddad6e78a6716e2930f7e6372e4b9b843f3fd3056f3 + languageName: node + linkType: hard + +"object.entries@npm:^1.1.0, object.entries@npm:^1.1.6": + version: 1.1.6 + resolution: "object.entries@npm:1.1.6" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: 8782c71db3a068ccbae9e0541e6b4ac2c25dc67c63f97b7e6ad3c88271d7820197e7398e37747f96542ed47c27f0b81148cdf14c42df15dc22f64818ae7bb5bf + languageName: node + linkType: hard + +"object.fromentries@npm:^2.0.0 || ^1.0.0, object.fromentries@npm:^2.0.6": + version: 2.0.6 + resolution: "object.fromentries@npm:2.0.6" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: db6759ea68131cbdb70b1152f9984b49db03e81de4f6de079b39929bebd8b45501e5333ca2351991e07ee56f4651606c023396644e8f25c0806fa39a26c4c6e6 + languageName: node + linkType: hard + +"object.getownpropertydescriptors@npm:^2.0.3, object.getownpropertydescriptors@npm:^2.1.2": + version: 2.1.5 + resolution: "object.getownpropertydescriptors@npm:2.1.5" + dependencies: + array.prototype.reduce: ^1.0.5 + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: 2a27afa5628bc200ddb511c5d030be867e049246388c100fcd81371c4d538c186014cf17f415f3e1430231ba75bbb021dff287727221c88156ab483cae02ab0a + languageName: node + linkType: hard + +"object.hasown@npm:^1.1.2": + version: 1.1.2 + resolution: "object.hasown@npm:1.1.2" + dependencies: + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: 419fc1c74a2aea7ebb4d49b79d5b1599a010b26c18eae35bd061ccdd013ccb749c499d8dd6ee21a91e6d7264ccc592573d0f13562970f76e25fc844d8c1b02ce + languageName: node + linkType: hard + +"object.pick@npm:^1.3.0": + version: 1.3.0 + resolution: "object.pick@npm:1.3.0" + dependencies: + isobject: ^3.0.1 + checksum: cd316ec986e49895a28f2df9182de9cdeee57cd2a952c122aacc86344c28624fe002d9affc4f48b5014ec7c033da9942b08821ddb44db8c5bac5b3ec54bdc31e + languageName: node + linkType: hard + +"object.values@npm:^1.1.0, object.values@npm:^1.1.6": + version: 1.1.6 + resolution: "object.values@npm:1.1.6" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: 3381204390f10c9f653a4875a50d221c67b5c16cb80a6ac06c706fc82a7cad8400857d4c7a0731193b0abb56b84fe803eabcf7addcf32de76397bbf207e68c66 + languageName: node + linkType: hard + +"objectorarray@npm:^1.0.5": + version: 1.0.5 + resolution: "objectorarray@npm:1.0.5" + checksum: 3d3db66e2052df85617ac31b98f8e51a7a883ebce24123018dacf286712aa513a0a84e82b4a6bef68889d5fc39cf08e630ee78df013023fc5161e1fdf3eaaa5a + languageName: node + linkType: hard + +"obuf@npm:^1.0.0, obuf@npm:^1.1.2": + version: 1.1.2 + resolution: "obuf@npm:1.1.2" + checksum: 520aaac7ea701618eacf000fc96ae458e20e13b0569845800fc582f81b386731ab22d55354b4915d58171db00e79cfcd09c1638c02f89577ef092b38c65b7d81 + languageName: node + linkType: hard + +"on-finished@npm:2.4.1": + version: 2.4.1 + resolution: "on-finished@npm:2.4.1" + dependencies: + ee-first: 1.1.1 + checksum: 46fb11b9063782f2d9968863d9cbba33d77aa13c17f895f56129c274318b86500b22af3a160fe9995aa41317efcd22941b6eba747f718ced08d9a73afdb087b4 + languageName: node + linkType: hard + +"on-headers@npm:~1.0.2": + version: 1.0.2 + resolution: "on-headers@npm:1.0.2" + checksum: f649e65c197bf31505a4c0444875db0258e198292f34b884d73c2f751e91792ef96bb5cf89aa0f4fecc2e4dc662461dda606b1274b0e564f539cae5d2f5fc32f + languageName: node + linkType: hard + +"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0, once@npm:~1.4.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: 1 + checksum: 5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 + languageName: node + linkType: hard + +"one-time@npm:^1.0.0": + version: 1.0.0 + resolution: "one-time@npm:1.0.0" + dependencies: + fn.name: 1.x.x + checksum: 6e4887b331edbb954f4e915831cbec0a7b9956c36f4feb5f6de98c448ac02ff881fd8d9b55a6b1b55030af184c6b648f340a76eb211812f4ad8c9b4b8692fdaa + languageName: node + linkType: hard + +"onetime@npm:^5.1.0, onetime@npm:^5.1.2": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: ^2.1.0 + checksum: ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f + languageName: node + linkType: hard + +"open@npm:^7.0.3": + version: 7.4.2 + resolution: "open@npm:7.4.2" + dependencies: + is-docker: ^2.0.0 + is-wsl: ^2.1.1 + checksum: 77573a6a68f7364f3a19a4c80492712720746b63680ee304555112605ead196afe91052bd3c3d165efdf4e9d04d255e87de0d0a77acec11ef47fd5261251813f + languageName: node + linkType: hard + +"open@npm:^8.0.9, open@npm:^8.4.0": + version: 8.4.0 + resolution: "open@npm:8.4.0" + dependencies: + define-lazy-prop: ^2.0.0 + is-docker: ^2.1.1 + is-wsl: ^2.2.0 + checksum: 585596580226cbeb7262f36b5acc7eed05211dc26980020a2527f829336b8b07fd79cdc4240f4d995b5615f635e0a59ebb0261c4419fef91edd5d4604c463f18 + languageName: node + linkType: hard + +"opener@npm:^1.5.2": + version: 1.5.2 + resolution: "opener@npm:1.5.2" + bin: + opener: bin/opener-bin.js + checksum: dd56256ab0cf796585617bc28e06e058adf09211781e70b264c76a1dbe16e90f868c974e5bf5309c93469157c7d14b89c35dc53fe7293b0e40b4d2f92073bc79 + languageName: node + linkType: hard + +"openreplay@workspace:.": + version: 0.0.0-use.local + resolution: "openreplay@workspace:." + dependencies: + "@babel/cli": ^7.10.5 + "@babel/core": ^7.17.12 + "@babel/node": ^7.16.8 + "@babel/plugin-proposal-class-properties": ^7.17.12 + "@babel/plugin-proposal-decorators": ^7.17.12 + "@babel/plugin-proposal-private-methods": ^7.10.4 + "@babel/plugin-syntax-bigint": ^7.8.3 + "@babel/plugin-transform-runtime": ^7.17.12 + "@babel/preset-env": ^7.17.12 + "@babel/preset-flow": ^7.10.4 + "@babel/preset-react": ^7.17.12 + "@babel/preset-typescript": ^7.17.12 + "@babel/runtime": ^7.17.9 + "@floating-ui/react-dom-interactions": ^0.10.3 + "@jest/globals": ^29.3.1 + "@mdx-js/react": ^1.6.22 + "@openreplay/sourcemap-uploader": ^3.0.0 + "@sentry/browser": ^5.21.1 + "@storybook/addon-actions": ^6.5.12 + "@storybook/addon-docs": ^6.5.12 + "@storybook/addon-essentials": ^6.5.12 + "@storybook/addon-interactions": ^6.5.12 + "@storybook/addon-links": ^6.5.12 + "@storybook/builder-webpack5": ^6.5.12 + "@storybook/manager-webpack5": ^6.5.12 + "@storybook/react": ^6.5.12 + "@storybook/testing-library": ^0.0.13 + "@svg-maps/world": ^1.0.1 + "@svgr/webpack": ^6.2.1 + "@types/luxon": ^3.0.0 + "@types/react": ^18.0.9 + "@types/react-dom": ^18.0.4 + "@types/react-redux": ^7.1.24 + "@types/react-router-dom": ^5.3.3 + "@types/react-virtualized": ^9.21.21 + "@typescript-eslint/eslint-plugin": ^5.24.0 + "@typescript-eslint/parser": ^5.24.0 + autoprefixer: ^10.4.7 + babel-loader: ^8.2.4 + babel-plugin-react-require: ^3.1.3 + babel-plugin-recharts: ^1.2.1 + babel-plugin-transform-decorators-legacy: ^1.3.5 + chroma-js: ^2.4.2 + classnames: ^2.3.1 + compression-webpack-plugin: ^10.0.0 + copy-to-clipboard: ^3.3.1 + copy-webpack-plugin: ^11.0.0 + country-data: 0.0.31 + css-loader: ^6.7.1 + cssnano: ^5.0.12 + cypress: ^12.3.0 + cypress-image-snapshot: ^4.0.1 + deasync-promise: ^1.0.1 + deep-diff: ^1.0.2 + deploy-aws-s3-cloudfront: ^3.6.0 + dotenv: ^6.2.0 + eslint: ^8.15.0 + eslint-plugin-react: ^7.29.4 + eslint-plugin-react-hooks: ^4.5.0 + faker: ^5.5.3 + file-loader: ^6.2.0 + flow-bin: ^0.115.0 + html-to-image: ^1.9.0 + html-webpack-plugin: ^5.5.0 + html2canvas: ^1.4.1 + immutable: ^4.0.0-rc.12 + jest: ^29.3.1 + jsbi: ^4.1.0 + jshint: ^2.11.1 + jspdf: ^2.5.1 + jsx-runtime: ^1.2.0 + luxon: ^1.24.1 + mini-css-extract-plugin: ^2.6.0 + minio: ^7.0.18 + mobx: ^6.3.8 + mobx-react-lite: ^3.1.6 + moment: ^2.29.4 + moment-locales-webpack-plugin: ^1.2.0 + moment-range: ^4.0.2 + node-gyp: ^9.0.0 + peerjs: 1.3.2 + postcss: ^8.4.14 + postcss-import: ^14.1.0 + postcss-loader: ^7.0.0 + postcss-mixins: ^9.0.2 + postcss-nesting: ^10.1.6 + postcss-simple-vars: ^6.0.3 + prettier: ^2.6.2 + rc-time-picker: ^3.7.3 + react: ^18.2.0 + react-circular-progressbar: ^2.1.0 + react-confirm: ^0.2.3 + react-daterange-picker: ^2.0.1 + react-dnd: ^16.0.1 + react-dnd-html5-backend: ^15.1.2 + react-dom: ^18.2.0 + react-draggable: ^4.4.5 + react-google-recaptcha: ^1.1.0 + react-highlight: ^0.14.0 + react-json-view: ^1.21.3 + react-lazyload: ^3.2.0 + react-merge-refs: ^2.0.1 + react-redux: ^5.1.2 + react-router: ^5.3.3 + react-router-dom: ^5.3.3 + react-select: ^5.3.2 + react-svg-map: ^2.2.0 + react-tippy: ^1.4.0 + react-toastify: ^9.1.1 + react-virtualized: ^9.22.3 + recharts: ^2.1.13 + redux: ^4.0.5 + redux-immutable: ^4.0.0 + redux-thunk: ^2.3.0 + sass: ^1.51.0 + sass-loader: ^13.0.0 + semantic-ui-css: ^2.4.1 + semantic-ui-react: ^2.1.2 + socket.io-client: ^4.4.1 + source-map: ^0.7.3 + style-loader: ^3.3.1 + svg-inline-loader: ^0.8.2 + svgo: ^2.8.0 + syncod: ^0.0.1 + tailwindcss: ^3.1.4 + ts-jest: ^29.0.5 + ts-node: ^10.7.0 + typescript: ^4.6.4 + webpack: ^5.72.1 + webpack-bundle-analyzer: ^4.5.0 + webpack-cli: ^4.9.2 + webpack-dev-server: ^4.9.0 + workbox-webpack-plugin: ^6.5.1 + languageName: unknown + linkType: soft + +"optionator@npm:^0.8.1": + version: 0.8.3 + resolution: "optionator@npm:0.8.3" + dependencies: + deep-is: ~0.1.3 + fast-levenshtein: ~2.0.6 + levn: ~0.3.0 + prelude-ls: ~1.1.2 + type-check: ~0.3.2 + word-wrap: ~1.2.3 + checksum: ad7000ea661792b3ec5f8f86aac28895850988926f483b5f308f59f4607dfbe24c05df2d049532ee227c040081f39401a268cf7bbf3301512f74c4d760dc6dd8 + languageName: node + linkType: hard + +"optionator@npm:^0.9.1": + version: 0.9.1 + resolution: "optionator@npm:0.9.1" + dependencies: + deep-is: ^0.1.3 + fast-levenshtein: ^2.0.6 + levn: ^0.4.1 + prelude-ls: ^1.2.1 + type-check: ^0.4.0 + word-wrap: ^1.2.3 + checksum: 8b574d50b032f34713dc09bfacdc351824f713c3c80773ead3a05ab977364de88f2f3962a6f15437747b93a5e0636928253949970daea3aaeeefbd3a525da6a4 + languageName: node + linkType: hard + +"os-browserify@npm:^0.3.0": + version: 0.3.0 + resolution: "os-browserify@npm:0.3.0" + checksum: 6ff32cb1efe2bc6930ad0fd4c50e30c38010aee909eba8d65be60af55efd6cbb48f0287e3649b4e3f3a63dce5a667b23c187c4293a75e557f0d5489d735bcf52 + languageName: node + linkType: hard + +"os-homedir@npm:^1.0.0": + version: 1.0.2 + resolution: "os-homedir@npm:1.0.2" + checksum: 6be4aa67317ee247b8d46142e243fb4ef1d2d65d3067f54bfc5079257a2f4d4d76b2da78cba7af3cb3f56dbb2e4202e0c47f26171d11ca1ed4008d842c90363f + languageName: node + linkType: hard + +"os-tmpdir@npm:^1.0.0": + version: 1.0.2 + resolution: "os-tmpdir@npm:1.0.2" + checksum: f438450224f8e2687605a8dd318f0db694b6293c5d835ae509a69e97c8de38b6994645337e5577f5001115470414638978cc49da1cdcc25106dad8738dc69990 + languageName: node + linkType: hard + +"osenv@npm:^0.1.4, osenv@npm:^0.1.5": + version: 0.1.5 + resolution: "osenv@npm:0.1.5" + dependencies: + os-homedir: ^1.0.0 + os-tmpdir: ^1.0.0 + checksum: b33ed4b77e662f3ee2a04bf4b56cad2107ab069dee982feb9e39ad44feb9aa0cf1016b9ac6e05d0d84c91fa496798fe48dd05a33175d624e51668068b9805302 + languageName: node + linkType: hard + +"ospath@npm:^1.2.2": + version: 1.2.2 + resolution: "ospath@npm:1.2.2" + checksum: e485a6ca91964f786163408b093860bf26a9d9704d83ec39ccf463b9f11ea712b780b23b73d1f64536de62c5f66244dd94ed83fc9ffe3c1564dd1eed5cdae923 + languageName: node + linkType: hard + +"p-all@npm:^2.1.0": + version: 2.1.0 + resolution: "p-all@npm:2.1.0" + dependencies: + p-map: ^2.0.0 + checksum: 874eafa2e3f38b258f8beed34549befbc8a52a63818e0981b8beff03f592e1e1f47b8aab2483f844f2745815ffa010def58bf1edbc95614466c55411f02f3049 + languageName: node + linkType: hard + +"p-defer@npm:^1.0.0": + version: 1.0.0 + resolution: "p-defer@npm:1.0.0" + checksum: ed603c3790e74b061ac2cb07eb6e65802cf58dce0fbee646c113a7b71edb711101329ad38f99e462bd2e343a74f6e9366b496a35f1d766c187084d3109900487 + languageName: node + linkType: hard + +"p-event@npm:^4.1.0": + version: 4.2.0 + resolution: "p-event@npm:4.2.0" + dependencies: + p-timeout: ^3.1.0 + checksum: f1b6a2fb13d47f2a8afc00150da5ece0d28940ce3d8fa562873e091d3337d298e78fee9cb18b768598ff1d11df608b2ae23868309ff6405b864a2451ccd6d25a + languageName: node + linkType: hard + +"p-filter@npm:^2.1.0": + version: 2.1.0 + resolution: "p-filter@npm:2.1.0" + dependencies: + p-map: ^2.0.0 + checksum: 5ac34b74b3b691c04212d5dd2319ed484f591c557a850a3ffc93a08cb38c4f5540be059c6b10a185773c479ca583a91ea00c7d6c9958c815e6b74d052f356645 + languageName: node + linkType: hard + +"p-finally@npm:^1.0.0": + version: 1.0.0 + resolution: "p-finally@npm:1.0.0" + checksum: 6b8552339a71fe7bd424d01d8451eea92d379a711fc62f6b2fe64cad8a472c7259a236c9a22b4733abca0b5666ad503cb497792a0478c5af31ded793d00937e7 + languageName: node + linkType: hard + +"p-limit@npm:^2.0.0, p-limit@npm:^2.2.0": + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" + dependencies: + p-try: ^2.0.0 + checksum: 8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 + languageName: node + linkType: hard + +"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: ^0.1.0 + checksum: 9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a + languageName: node + linkType: hard + +"p-locate@npm:^3.0.0": + version: 3.0.0 + resolution: "p-locate@npm:3.0.0" + dependencies: + p-limit: ^2.0.0 + checksum: 7b7f06f718f19e989ce6280ed4396fb3c34dabdee0df948376483032f9d5ec22fdf7077ec942143a75827bb85b11da72016497fc10dac1106c837ed593969ee8 + languageName: node + linkType: hard + +"p-locate@npm:^4.1.0": + version: 4.1.0 + resolution: "p-locate@npm:4.1.0" + dependencies: + p-limit: ^2.2.0 + checksum: 1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 + languageName: node + linkType: hard + +"p-locate@npm:^5.0.0": + version: 5.0.0 + resolution: "p-locate@npm:5.0.0" + dependencies: + p-limit: ^3.0.2 + checksum: 2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a + languageName: node + linkType: hard + +"p-map@npm:^2.0.0": + version: 2.1.0 + resolution: "p-map@npm:2.1.0" + checksum: 735dae87badd4737a2dd582b6d8f93e49a1b79eabbc9815a4d63a528d5e3523e978e127a21d784cccb637010e32103a40d2aaa3ab23ae60250b1a820ca752043 + languageName: node + linkType: hard + +"p-map@npm:^3.0.0": + version: 3.0.0 + resolution: "p-map@npm:3.0.0" + dependencies: + aggregate-error: ^3.0.0 + checksum: 297930737e52412ad9f5787c52774ad6496fad9a8be5f047e75fd0a3dc61930d8f7a9b2bbe1c4d1404e54324228a4f69721da2538208dadaa4ef4c81773c9f20 + languageName: node + linkType: hard + +"p-map@npm:^4.0.0": + version: 4.0.0 + resolution: "p-map@npm:4.0.0" + dependencies: + aggregate-error: ^3.0.0 + checksum: 592c05bd6262c466ce269ff172bb8de7c6975afca9b50c975135b974e9bdaafbfe80e61aaaf5be6d1200ba08b30ead04b88cfa7e25ff1e3b93ab28c9f62a2c75 + languageName: node + linkType: hard + +"p-retry@npm:^4.5.0": + version: 4.6.2 + resolution: "p-retry@npm:4.6.2" + dependencies: + "@types/retry": 0.12.0 + retry: ^0.13.1 + checksum: d58512f120f1590cfedb4c2e0c42cb3fa66f3cea8a4646632fcb834c56055bb7a6f138aa57b20cc236fb207c9d694e362e0b5c2b14d9b062f67e8925580c73b0 + languageName: node + linkType: hard + +"p-timeout@npm:^3.1.0": + version: 3.2.0 + resolution: "p-timeout@npm:3.2.0" + dependencies: + p-finally: ^1.0.0 + checksum: 524b393711a6ba8e1d48137c5924749f29c93d70b671e6db761afa784726572ca06149c715632da8f70c090073afb2af1c05730303f915604fd38ee207b70a61 + languageName: node + linkType: hard + +"p-try@npm:^2.0.0": + version: 2.2.0 + resolution: "p-try@npm:2.2.0" + checksum: c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f + languageName: node + linkType: hard + +"package-json@npm:^4.0.0": + version: 4.0.1 + resolution: "package-json@npm:4.0.1" + dependencies: + got: ^6.7.1 + registry-auth-token: ^3.0.1 + registry-url: ^3.0.3 + semver: ^5.1.0 + checksum: e3c213b9764547024dd80bb7913dada29f487de1dd3d24ef63c152d661cb75ae28ccd965bac3b9e33d0cdaf7cfe2527995e8d00b9e9e2973b7c53bf19ce522a3 + languageName: node + linkType: hard + +"pacote@npm:^9.1.0, pacote@npm:^9.5.12, pacote@npm:^9.5.3": + version: 9.5.12 + resolution: "pacote@npm:9.5.12" + dependencies: + bluebird: ^3.5.3 + cacache: ^12.0.2 + chownr: ^1.1.2 + figgy-pudding: ^3.5.1 + get-stream: ^4.1.0 + glob: ^7.1.3 + infer-owner: ^1.0.4 + lru-cache: ^5.1.1 + make-fetch-happen: ^5.0.0 + minimatch: ^3.0.4 + minipass: ^2.3.5 + mississippi: ^3.0.0 + mkdirp: ^0.5.1 + normalize-package-data: ^2.4.0 + npm-normalize-package-bin: ^1.0.0 + npm-package-arg: ^6.1.0 + npm-packlist: ^1.1.12 + npm-pick-manifest: ^3.0.0 + npm-registry-fetch: ^4.0.0 + osenv: ^0.1.5 + promise-inflight: ^1.0.1 + promise-retry: ^1.1.1 + protoduck: ^5.0.1 + rimraf: ^2.6.2 + safe-buffer: ^5.1.2 + semver: ^5.6.0 + ssri: ^6.0.1 + tar: ^4.4.10 + unique-filename: ^1.1.1 + which: ^1.3.1 + checksum: 3ab988d5951006150a63159877f79d7a456eb57a66fd18c750da47719a370d703ed4c202d0b417cdbeb8927efd56fbcf41baba15d18c192db8df1bb49eb28807 + languageName: node + linkType: hard + +"pako@npm:~1.0.5": + version: 1.0.11 + resolution: "pako@npm:1.0.11" + checksum: 86dd99d8b34c3930345b8bbeb5e1cd8a05f608eeb40967b293f72fe469d0e9c88b783a8777e4cc7dc7c91ce54c5e93d88ff4b4f060e6ff18408fd21030d9ffbe + languageName: node + linkType: hard + +"parallel-transform@npm:^1.1.0": + version: 1.2.0 + resolution: "parallel-transform@npm:1.2.0" + dependencies: + cyclist: ^1.0.1 + inherits: ^2.0.3 + readable-stream: ^2.1.5 + checksum: ab0e58569e73681ca4b9c9228189bdb6cbea535295fae344cf0d8342fd33a950961914f3c414f81894c1498fb9ad1c079b4625d2b7ceae9e6ab812f22e3bea3f + languageName: node + linkType: hard + +"param-case@npm:^3.0.3, param-case@npm:^3.0.4": + version: 3.0.4 + resolution: "param-case@npm:3.0.4" + dependencies: + dot-case: ^3.0.4 + tslib: ^2.0.3 + checksum: ccc053f3019f878eca10e70ec546d92f51a592f762917dafab11c8b532715dcff58356118a6f350976e4ab109e321756f05739643ed0ca94298e82291e6f9e76 + languageName: node + linkType: hard + +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: ^3.0.0 + checksum: c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 + languageName: node + linkType: hard + +"parse-asn1@npm:^5.0.0, parse-asn1@npm:^5.1.5": + version: 5.1.6 + resolution: "parse-asn1@npm:5.1.6" + dependencies: + asn1.js: ^5.2.0 + browserify-aes: ^1.0.0 + evp_bytestokey: ^1.0.0 + pbkdf2: ^3.0.3 + safe-buffer: ^5.1.1 + checksum: 4ed1d9b9e120c5484d29d67bb90171aac0b73422bc016d6294160aea983275c28a27ab85d862059a36a86a97dd31b7ddd97486802ca9fac67115fe3409e9dcbd + languageName: node + linkType: hard + +"parse-entities@npm:^2.0.0": + version: 2.0.0 + resolution: "parse-entities@npm:2.0.0" + dependencies: + character-entities: ^1.0.0 + character-entities-legacy: ^1.0.0 + character-reference-invalid: ^1.0.0 + is-alphanumerical: ^1.0.0 + is-decimal: ^1.0.0 + is-hexadecimal: ^1.0.0 + checksum: f85a22c0ea406ff26b53fdc28641f01cc36fa49eb2e3135f02693286c89ef0bcefc2262d99b3688e20aac2a14fd10b75c518583e875c1b9fe3d1f937795e0854 + languageName: node + linkType: hard + +"parse-json@npm:^2.2.0": + version: 2.2.0 + resolution: "parse-json@npm:2.2.0" + dependencies: + error-ex: ^1.2.0 + checksum: 7a90132aa76016f518a3d5d746a21b3f1ad0f97a68436ed71b6f995b67c7151141f5464eea0c16c59aec9b7756519a0e3007a8f98cf3714632d509ec07736df6 + languageName: node + linkType: hard + +"parse-json@npm:^5.0.0, parse-json@npm:^5.2.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + 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 + checksum: 77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 + languageName: node + linkType: hard + +"parse-passwd@npm:^1.0.0": + version: 1.0.0 + resolution: "parse-passwd@npm:1.0.0" + checksum: 1c05c05f95f184ab9ca604841d78e4fe3294d46b8e3641d305dcc28e930da0e14e602dbda9f3811cd48df5b0e2e27dbef7357bf0d7c40e41b18c11c3a8b8d17b + languageName: node + linkType: hard + +"parse5@npm:^6.0.0": + version: 6.0.1 + resolution: "parse5@npm:6.0.1" + checksum: 595821edc094ecbcfb9ddcb46a3e1fe3a718540f8320eff08b8cf6742a5114cce2d46d45f95c26191c11b184dcaf4e2960abcd9c5ed9eb9393ac9a37efcfdecb + languageName: node + linkType: hard + +"parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": + version: 1.3.3 + resolution: "parseurl@npm:1.3.3" + checksum: 90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 + languageName: node + linkType: hard + +"pascal-case@npm:^3.1.2": + version: 3.1.2 + resolution: "pascal-case@npm:3.1.2" + dependencies: + no-case: ^3.0.4 + tslib: ^2.0.3 + checksum: 05ff7c344809fd272fc5030ae0ee3da8e4e63f36d47a1e0a4855ca59736254192c5a27b5822ed4bae96e54048eec5f6907713cfcfff7cdf7a464eaf7490786d8 + languageName: node + linkType: hard + +"pascalcase@npm:^0.1.1": + version: 0.1.1 + resolution: "pascalcase@npm:0.1.1" + checksum: 48dfe90618e33810bf58211d8f39ad2c0262f19ad6354da1ba563935b5f429f36409a1fb9187c220328f7a4dc5969917f8e3e01ee089b5f1627b02aefe39567b + languageName: node + linkType: hard + +"path-browserify@npm:0.0.1": + version: 0.0.1 + resolution: "path-browserify@npm:0.0.1" + checksum: 3d59710cddeea06509d91935196185900f3d9d29376dff68ff0e146fbd41d0fb304e983d0158f30cabe4dd2ffcc6a7d3d977631994ee984c88e66aed50a1ccd3 + languageName: node + linkType: hard + +"path-browserify@npm:^1.0.1": + version: 1.0.1 + resolution: "path-browserify@npm:1.0.1" + checksum: 8b8c3fd5c66bd340272180590ae4ff139769e9ab79522e2eb82e3d571a89b8117c04147f65ad066dccfb42fcad902e5b7d794b3d35e0fd840491a8ddbedf8c66 + languageName: node + linkType: hard + +"path-dirname@npm:^1.0.0": + version: 1.0.2 + resolution: "path-dirname@npm:1.0.2" + checksum: 71e59be2bada7c91f62b976245fd421b7cb01fde3207fe53a82d8880621ad04fd8b434e628c9cf4e796259fc168a107d77cd56837725267c5b2c58cefe2c4e1b + languageName: node + linkType: hard + +"path-exists@npm:^2.0.0": + version: 2.1.0 + resolution: "path-exists@npm:2.1.0" + dependencies: + pinkie-promise: ^2.0.0 + checksum: 87352f1601c085d5a6eb202f60e5c016c1b790bd0bc09398af446ed3f5c4510b4531ff99cf8acac2d91868886e792927b4292f768b35a83dce12588fb7cbb46e + languageName: node + linkType: hard + +"path-exists@npm:^3.0.0": + version: 3.0.0 + resolution: "path-exists@npm:3.0.0" + checksum: 17d6a5664bc0a11d48e2b2127d28a0e58822c6740bde30403f08013da599182289c56518bec89407e3f31d3c2b6b296a4220bc3f867f0911fee6952208b04167 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-is-absolute@npm:^1.0.0": + version: 1.0.1 + resolution: "path-is-absolute@npm:1.0.1" + checksum: 127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078 + languageName: node + linkType: hard + +"path-is-inside@npm:^1.0.1, path-is-inside@npm:^1.0.2, path-is-inside@npm:~1.0.2": + version: 1.0.2 + resolution: "path-is-inside@npm:1.0.2" + checksum: 7fdd4b41672c70461cce734fc222b33e7b447fa489c7c4377c95e7e6852d83d69741f307d88ec0cc3b385b41cb4accc6efac3c7c511cd18512e95424f5fa980c + languageName: node + linkType: hard + +"path-key@npm:^2.0.0, path-key@npm:^2.0.1": + version: 2.0.1 + resolution: "path-key@npm:2.0.1" + checksum: dd2044f029a8e58ac31d2bf34c34b93c3095c1481942960e84dd2faa95bbb71b9b762a106aead0646695330936414b31ca0bd862bf488a937ad17c8c5d73b32b + languageName: node + linkType: hard + +"path-key@npm:^3.0.0, path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"path-to-regexp@npm:0.1.7": + version: 0.1.7 + resolution: "path-to-regexp@npm:0.1.7" + checksum: 50a1ddb1af41a9e68bd67ca8e331a705899d16fb720a1ea3a41e310480948387daf603abb14d7b0826c58f10146d49050a1291ba6a82b78a382d1c02c0b8f905 + languageName: node + linkType: hard + +"path-to-regexp@npm:^1.7.0": + version: 1.8.0 + resolution: "path-to-regexp@npm:1.8.0" + dependencies: + isarray: 0.0.1 + checksum: 7b25d6f27a8de03f49406d16195450f5ced694398adea1510b0f949d9660600d1769c5c6c83668583b7e6b503f3caf1ede8ffc08135dbe3e982f034f356fbb5c + languageName: node + linkType: hard + +"path-type@npm:^1.0.0": + version: 1.1.0 + resolution: "path-type@npm:1.1.0" + dependencies: + graceful-fs: ^4.1.2 + pify: ^2.0.0 + pinkie-promise: ^2.0.0 + checksum: 2b8c348cb52bbc0c0568afa10a0a5d8f6233adfe5ae75feb56064f6aed6324ab74185c61c2545f4e52ca08acdc76005f615da4e127ed6eecb866002cf491f350 + languageName: node + linkType: hard + +"path-type@npm:^3.0.0": + version: 3.0.0 + resolution: "path-type@npm:3.0.0" + dependencies: + pify: ^3.0.0 + checksum: 1332c632f1cac15790ebab8dd729b67ba04fc96f81647496feb1c2975d862d046f41e4b975dbd893048999b2cc90721f72924ad820acc58c78507ba7141a8e56 + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c + languageName: node + linkType: hard + +"pbkdf2@npm:^3.0.3": + version: 3.1.2 + resolution: "pbkdf2@npm:3.1.2" + dependencies: + create-hash: ^1.1.2 + create-hmac: ^1.1.4 + ripemd160: ^2.0.1 + safe-buffer: ^5.0.1 + sha.js: ^2.4.8 + checksum: 5a30374e87d33fa080a92734d778cf172542cc7e41b96198c4c88763997b62d7850de3fbda5c3111ddf79805ee7c1da7046881c90ac4920b5e324204518b05fd + languageName: node + linkType: hard + +"peerjs-js-binarypack@npm:1.0.1": + version: 1.0.1 + resolution: "peerjs-js-binarypack@npm:1.0.1" + checksum: 986813803562bfb2633ebe05a16524918dd820329e2a4b85656d3af3b8ee8fe7cdc236f1506344a496c93b825e389a735a07c024e0337bedb186b249178bfd65 + languageName: node + linkType: hard + +"peerjs@npm:1.3.2": + version: 1.3.2 + resolution: "peerjs@npm:1.3.2" + dependencies: + "@types/node": ^10.14.33 + eventemitter3: ^3.1.2 + peerjs-js-binarypack: 1.0.1 + webrtc-adapter: ^7.7.1 + checksum: e6281ea9a9d787025eaa03fa82d7a4ba03e2f0ef0ad1fc5c977e562a6cd81ca40c321ba114dbb3e3815d8c7e3c290d3fe53c2f12ecdf0fd4658591fdae6ab273 + languageName: node + linkType: hard + +"pend@npm:~1.2.0": + version: 1.2.0 + resolution: "pend@npm:1.2.0" + checksum: 8a87e63f7a4afcfb0f9f77b39bb92374afc723418b9cb716ee4257689224171002e07768eeade4ecd0e86f1fa3d8f022994219fb45634f2dbd78c6803e452458 + languageName: node + linkType: hard + +"performance-now@npm:^2.1.0": + version: 2.1.0 + resolution: "performance-now@npm:2.1.0" + checksum: 22c54de06f269e29f640e0e075207af57de5052a3d15e360c09b9a8663f393f6f45902006c1e71aa8a5a1cdfb1a47fe268826f8496d6425c362f00f5bc3e85d9 + languageName: node + linkType: hard + +"picocolors@npm:^0.2.1": + version: 0.2.1 + resolution: "picocolors@npm:0.2.1" + checksum: 98a83c77912c80aea0fc518aec184768501bfceafa490714b0f43eda9c52e372b844ce0a591e822bbfe5df16dcf366be7cbdb9534d39cf54a80796340371ee17 + languageName: node + linkType: hard + +"picocolors@npm:^1.0.0": + version: 1.0.0 + resolution: "picocolors@npm:1.0.0" + checksum: 20a5b249e331c14479d94ec6817a182fd7a5680debae82705747b2db7ec50009a5f6648d0621c561b0572703f84dbef0858abcbd5856d3c5511426afcb1961f7 + languageName: node + linkType: hard + +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.2, picomatch@npm:^2.2.3, picomatch@npm:^2.3.0, picomatch@npm:^2.3.1": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be + languageName: node + linkType: hard + +"pify@npm:^2.0.0, pify@npm:^2.2.0, pify@npm:^2.3.0": + version: 2.3.0 + resolution: "pify@npm:2.3.0" + checksum: 551ff8ab830b1052633f59cb8adc9ae8407a436e06b4a9718bcb27dc5844b83d535c3a8512b388b6062af65a98c49bdc0dd523d8b2617b188f7c8fee457158dc + languageName: node + linkType: hard + +"pify@npm:^3.0.0": + version: 3.0.0 + resolution: "pify@npm:3.0.0" + checksum: fead19ed9d801f1b1fcd0638a1ac53eabbb0945bf615f2f8806a8b646565a04a1b0e7ef115c951d225f042cca388fdc1cd3add46d10d1ed6951c20bd2998af10 + languageName: node + linkType: hard + +"pify@npm:^4.0.1": + version: 4.0.1 + resolution: "pify@npm:4.0.1" + checksum: 6f9d404b0d47a965437403c9b90eca8bb2536407f03de165940e62e72c8c8b75adda5516c6b9b23675a5877cc0bcac6bdfb0ef0e39414cd2476d5495da40e7cf + languageName: node + linkType: hard + +"pinkie-promise@npm:^2.0.0": + version: 2.0.1 + resolution: "pinkie-promise@npm:2.0.1" + dependencies: + pinkie: ^2.0.0 + checksum: 11b5e5ce2b090c573f8fad7b517cbca1bb9a247587306f05ae71aef6f9b2cd2b923c304aa9663c2409cfde27b367286179f1379bc4ec18a3fbf2bb0d473b160a + languageName: node + linkType: hard + +"pinkie@npm:^2.0.0": + version: 2.0.4 + resolution: "pinkie@npm:2.0.4" + checksum: 25228b08b5597da42dc384221aa0ce56ee0fbf32965db12ba838e2a9ca0193c2f0609c45551ee077ccd2060bf109137fdb185b00c6d7e0ed7e35006d20fdcbc6 + languageName: node + linkType: hard + +"pirates@npm:^4.0.1, pirates@npm:^4.0.4, pirates@npm:^4.0.5": + version: 4.0.5 + resolution: "pirates@npm:4.0.5" + checksum: 58b6ff0f137a3d70ff34ac4802fd19819cdc19b53e9c95adecae6c7cfc77719a11f561ad85d46e79e520ef57c31145a564c8bc3bee8cfee75d441fab2928a51d + languageName: node + linkType: hard + +"pixelmatch@npm:^5.1.0": + version: 5.3.0 + resolution: "pixelmatch@npm:5.3.0" + dependencies: + pngjs: ^6.0.0 + bin: + pixelmatch: bin/pixelmatch + checksum: 30850661db29b57cefbe6cf36e930b7517aea4e0ed129e85fcc8ec04a7e6e7648a822a972f8e01d2d3db268ca3c735555caf6b8099a164d8b64d105986d682d2 + languageName: node + linkType: hard + +"pkg-dir@npm:^3.0.0": + version: 3.0.0 + resolution: "pkg-dir@npm:3.0.0" + dependencies: + find-up: ^3.0.0 + checksum: 902a3d0c1f8ac43b1795fa1ba6ffeb37dfd53c91469e969790f6ed5e29ff2bdc50b63ba6115dc056d2efb4a040aa2446d512b3804bdafdf302f734fb3ec21847 + languageName: node + linkType: hard + +"pkg-dir@npm:^4.1.0, pkg-dir@npm:^4.2.0": + version: 4.2.0 + resolution: "pkg-dir@npm:4.2.0" + dependencies: + find-up: ^4.0.0 + checksum: c56bda7769e04907a88423feb320babaed0711af8c436ce3e56763ab1021ba107c7b0cafb11cde7529f669cfc22bffcaebffb573645cbd63842ea9fb17cd7728 + languageName: node + linkType: hard + +"pkg-dir@npm:^5.0.0": + version: 5.0.0 + resolution: "pkg-dir@npm:5.0.0" + dependencies: + find-up: ^5.0.0 + checksum: 793a496d685dc55bbbdbbb22d884535c3b29241e48e3e8d37e448113a71b9e42f5481a61fdc672d7322de12fbb2c584dd3a68bf89b18fffce5c48a390f911bc5 + languageName: node + linkType: hard + +"plist@npm:^3.0.1": + version: 3.0.6 + resolution: "plist@npm:3.0.6" + dependencies: + base64-js: ^1.5.1 + xmlbuilder: ^15.1.1 + checksum: 0afefbc29946b5ee3859a2f940ba17c4cb99b7c00b0501a22550cffb6b868f2d80ea523fc90aa216c7b7a56946664d6f7caa52e9509e3300cfae5dc810f32789 + languageName: node + linkType: hard + +"pngjs@npm:^3.4.0": + version: 3.4.0 + resolution: "pngjs@npm:3.4.0" + checksum: 88ee73e2ad3f736e0b2573722309eb80bd2aa28916f0862379b4fd0f904751b4f61bb6bd1ecd7d4242d331f2b5c28c13309dd4b7d89a9b78306e35122fdc5011 + languageName: node + linkType: hard + +"pngjs@npm:^6.0.0": + version: 6.0.0 + resolution: "pngjs@npm:6.0.0" + checksum: ac23ea329b1881d1a10575aff58116dc27b894ec3f5b84ba15c7f527d21e609fbce7ba16d48f8ccb86c7ce45ceed622472765476ab2875949d4bec55e153f87a + languageName: node + linkType: hard + +"pnp-webpack-plugin@npm:1.6.4": + version: 1.6.4 + resolution: "pnp-webpack-plugin@npm:1.6.4" + dependencies: + ts-pnp: ^1.1.6 + checksum: 6cedab8a9cd129b9f58408023f80cad528e361685f50c2149da7ad8fb79bd2043d3250c68b8723aa43ecdb913931edf04ecaa4d7afe719d0e151055d41779599 + languageName: node + linkType: hard + +"polished@npm:^4.2.2": + version: 4.2.2 + resolution: "polished@npm:4.2.2" + dependencies: + "@babel/runtime": ^7.17.8 + checksum: 1d054d1fea18ac7d921ca91504ffcf1ef0f505eda6acbfec6e205a98ebfea80b658664995deb35907dabc5f75f287dc2894812503a8aed28285bb91f25cf7400 + languageName: node + linkType: hard + +"popper.js@npm:^1.11.1": + version: 1.16.1 + resolution: "popper.js@npm:1.16.1" + checksum: 1c1a826f757edb5b8c2049dfd7a9febf6ae1e9d0e51342fc715b49a0c1020fced250d26484619883651e097c5764bbcacd2f31496e3646027f079dd83e072681 + languageName: node + linkType: hard + +"posix-character-classes@npm:^0.1.0": + version: 0.1.1 + resolution: "posix-character-classes@npm:0.1.1" + checksum: cce88011548a973b4af58361cd8f5f7b5a6faff8eef0901565802f067bcabf82597e920d4c97c22068464be3cbc6447af589f6cc8a7d813ea7165be60a0395bc + languageName: node + linkType: hard + +"postcss-calc@npm:^8.2.3": + version: 8.2.4 + resolution: "postcss-calc@npm:8.2.4" + dependencies: + postcss-selector-parser: ^6.0.9 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.2 + checksum: 8518a429488c3283ff1560c83a511f6f772329bc61d88875eb7c83e13a8683b7ccbdccaa9946024cf1553da3eacd2f40fcbcebf1095f7fdeb432bf86bc6ba6ba + languageName: node + linkType: hard + +"postcss-colormin@npm:^5.3.0": + version: 5.3.0 + resolution: "postcss-colormin@npm:5.3.0" + dependencies: + browserslist: ^4.16.6 + caniuse-api: ^3.0.0 + colord: ^2.9.1 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: ac03b47b1d76f46fa3621d9b066217e92105869af6e57245b85b304d1e866ded2818c8dc92891b84e9099f4f31f3555a5344d000beedcb2aa766faf0d52844b6 + languageName: node + linkType: hard + +"postcss-convert-values@npm:^5.1.3": + version: 5.1.3 + resolution: "postcss-convert-values@npm:5.1.3" + dependencies: + browserslist: ^4.21.4 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: cd10a81781a12487b2921ff84a1a068e948a1956b9539a284c202abecf4cacdd3e106eb026026b22dbf70933f4315c824c111f6b71f56c355e47b842ca9b1dec + languageName: node + linkType: hard + +"postcss-discard-comments@npm:^5.1.2": + version: 5.1.2 + resolution: "postcss-discard-comments@npm:5.1.2" + peerDependencies: + postcss: ^8.2.15 + checksum: cb5ba81623c498e18d406138e7d27d69fc668802a1139a8de69d28e80b3fe222cda7b634940512cae78d04f0c78afcd15d92bcf80e537c6c85fa8ff9cd61d00f + languageName: node + linkType: hard + +"postcss-discard-duplicates@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-discard-duplicates@npm:5.1.0" + peerDependencies: + postcss: ^8.2.15 + checksum: 3d3a49536c56097c06b4f085412e0cda0854fac1c559563ccb922d9fab6305ff13058cd6fee422aa66c1d7e466add4e7672d7ae2ff551a4af6f1a8d2142d471f + languageName: node + linkType: hard + +"postcss-discard-empty@npm:^5.1.1": + version: 5.1.1 + resolution: "postcss-discard-empty@npm:5.1.1" + peerDependencies: + postcss: ^8.2.15 + checksum: 36c8b2197af836dbd93168c72cde4edc1f10fe00e564824119da076d3764909745bb60e4ada04052322e26872d1bce6a37c56815f1c48c813a21adca1a41fbdc + languageName: node + linkType: hard + +"postcss-discard-overridden@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-discard-overridden@npm:5.1.0" + peerDependencies: + postcss: ^8.2.15 + checksum: 7d3fc0b0d90599606fc083327a7c24390f90270a94a0119af4b74815d518948581579281f63b9bfa62e2644edf59bc9e725dc04ea5ba213f697804f3fb4dd8dc + languageName: node + linkType: hard + +"postcss-flexbugs-fixes@npm:^4.2.1": + version: 4.2.1 + resolution: "postcss-flexbugs-fixes@npm:4.2.1" + dependencies: + postcss: ^7.0.26 + checksum: 57d2894dadd5762ae243792ca45806281ca9c32a9270519f2fd5d95cf1445590df260997b3d9ff937b9e1a551644799881c7f337352dde4e453805687c1ebee8 + languageName: node + linkType: hard + +"postcss-import@npm:^14.1.0": + version: 14.1.0 + resolution: "postcss-import@npm:14.1.0" + dependencies: + postcss-value-parser: ^4.0.0 + read-cache: ^1.0.0 + resolve: ^1.1.7 + peerDependencies: + postcss: ^8.0.0 + checksum: 0552f48b6849d48b25213e8bfb4b2ae10fcf061224ba17b5c008d8b8de69b9b85442bff6c7ac2a313aec32f14fd000f57720b06f82dc6e9f104405b221a741db + languageName: node + linkType: hard + +"postcss-js@npm:^4.0.0": + version: 4.0.0 + resolution: "postcss-js@npm:4.0.0" + dependencies: + camelcase-css: ^2.0.1 + peerDependencies: + postcss: ^8.3.3 + checksum: 12cde8a25f5346b3e413b1fde37df26845f916ec97db762868d9e44386703272a33d05511f52cb2f616f0d5e7da618b1e3ce68b9431fbd2f6cc1fc4a0fcb7dfb + languageName: node + linkType: hard + +"postcss-load-config@npm:^3.1.4": + version: 3.1.4 + resolution: "postcss-load-config@npm:3.1.4" + dependencies: + lilconfig: ^2.0.5 + yaml: ^1.10.2 + peerDependencies: + postcss: ">=8.0.9" + ts-node: ">=9.0.0" + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + checksum: 7d2cc6695c2fc063e4538316d651a687fdb55e48db453ff699de916a6ee55ab68eac2b120c28a6b8ca7aa746a588888351b810a215b5cd090eabea62c5762ede + languageName: node + linkType: hard + +"postcss-loader@npm:^4.2.0": + version: 4.3.0 + resolution: "postcss-loader@npm:4.3.0" + dependencies: + cosmiconfig: ^7.0.0 + klona: ^2.0.4 + loader-utils: ^2.0.0 + schema-utils: ^3.0.0 + semver: ^7.3.4 + peerDependencies: + postcss: ^7.0.0 || ^8.0.1 + webpack: ^4.0.0 || ^5.0.0 + checksum: 3405584e571ec4d66d7c2b665a2a4823eaa7208433fd40eb6b669ac441f23398bc81fc18fe631c7d7805a303ad31f284a5066c4097dd082c1faba7edf13db8aa + languageName: node + linkType: hard + +"postcss-loader@npm:^7.0.0": + version: 7.0.2 + resolution: "postcss-loader@npm:7.0.2" + dependencies: + cosmiconfig: ^7.0.0 + klona: ^2.0.5 + semver: ^7.3.8 + peerDependencies: + postcss: ^7.0.0 || ^8.0.1 + webpack: ^5.0.0 + checksum: deba84029de33f9bc7a5ad3f908acdf93a385e17bbb03a200a10ed5a2be8f20a2ae6ff3b5d81b857618305c5f7dd4da224b4e8373a8e29ff39bbf4708ea7c857 + languageName: node + linkType: hard + +"postcss-merge-longhand@npm:^5.1.7": + version: 5.1.7 + resolution: "postcss-merge-longhand@npm:5.1.7" + dependencies: + postcss-value-parser: ^4.2.0 + stylehacks: ^5.1.1 + peerDependencies: + postcss: ^8.2.15 + checksum: 4d9f44b03f19522cc81ae4f5b1f2a9ef2db918dbd8b3042d4f1b2461b2230b8ec1269334db6a67a863ba68f64cabd712e6e45340ddb22a3fc03cd34df69d2bf0 + languageName: node + linkType: hard + +"postcss-merge-rules@npm:^5.1.3": + version: 5.1.3 + resolution: "postcss-merge-rules@npm:5.1.3" + dependencies: + browserslist: ^4.21.4 + caniuse-api: ^3.0.0 + cssnano-utils: ^3.1.0 + postcss-selector-parser: ^6.0.5 + peerDependencies: + postcss: ^8.2.15 + checksum: 44b8652fe42e379a9418b96484a92cb9d9e51a496ef0ac04bec86041090b9a71a07c4e01005390c1666f77124ab0e7fe1a6d62b0b422806e521f95f68b8c41a1 + languageName: node + linkType: hard + +"postcss-minify-font-values@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-minify-font-values@npm:5.1.0" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 7aa4f93a853b657f79a8b28d0e924cafce3720086d9da02ce04b8b2f8de42e18ce32c8f7f1078390fb5ec82468e2d8e771614387cea3563f05fd9fa1798e1c59 + languageName: node + linkType: hard + +"postcss-minify-gradients@npm:^5.1.1": + version: 5.1.1 + resolution: "postcss-minify-gradients@npm:5.1.1" + dependencies: + colord: ^2.9.1 + cssnano-utils: ^3.1.0 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: bcb2802d7c8f0f76c7cff089884844f26c24b95f35c3ec951d7dec8c212495d1873d6ba62d6225ce264570e8e0668e271f9bc79bb6f5d2429c1f8933f4e3021d + languageName: node + linkType: hard + +"postcss-minify-params@npm:^5.1.4": + version: 5.1.4 + resolution: "postcss-minify-params@npm:5.1.4" + dependencies: + browserslist: ^4.21.4 + cssnano-utils: ^3.1.0 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: debce6f0f7dd9af69b4bb9e467ea1ccccff2d849b6020461a2b9741c0c137340e6076c245dc2e83880180eb2e82936280fa31dfe8608e5a2e3618f3d864314c5 + languageName: node + linkType: hard + +"postcss-minify-selectors@npm:^5.2.1": + version: 5.2.1 + resolution: "postcss-minify-selectors@npm:5.2.1" + dependencies: + postcss-selector-parser: ^6.0.5 + peerDependencies: + postcss: ^8.2.15 + checksum: f3f4ec110f5f697cfc9dde3e491ff10aa07509bf33cc940aa539e4b5b643d1b9f8bb97f8bb83d05fc96f5eeb220500ebdeffbde513bd176c0671e21c2c96fab9 + languageName: node + linkType: hard + +"postcss-mixins@npm:^9.0.2": + version: 9.0.4 + resolution: "postcss-mixins@npm:9.0.4" + dependencies: + fast-glob: ^3.2.11 + postcss-js: ^4.0.0 + postcss-simple-vars: ^7.0.0 + sugarss: ^4.0.1 + peerDependencies: + postcss: ^8.2.14 + checksum: 6bb90bd15bb4a06e8f50b36b38f24bba8350cdb9f1d9690e97a9610619da9fc22cdccfc81aa628bef60490cce35f76ceac7602062f5895b30d72123684016a93 + languageName: node + linkType: hard + +"postcss-modules-extract-imports@npm:^2.0.0": + version: 2.0.0 + resolution: "postcss-modules-extract-imports@npm:2.0.0" + dependencies: + postcss: ^7.0.5 + checksum: 170e8d680c267c536563e76979f04dc80e6dfa026d49f1e9ead2d0981a74b0c64d2894a8fd691e50568f12144553cf0b948ab43263872b3f696dcb34b683e238 + languageName: node + linkType: hard + +"postcss-modules-extract-imports@npm:^3.0.0": + version: 3.0.0 + resolution: "postcss-modules-extract-imports@npm:3.0.0" + peerDependencies: + postcss: ^8.1.0 + checksum: f8879d66d8162fb7a3fcd916d37574006c584ea509107b1cfb798a5e090175ef9470f601e46f0a305070d8ff2500e07489a5c1ac381c29a1dc1120e827ca7943 + languageName: node + linkType: hard + +"postcss-modules-local-by-default@npm:^3.0.2": + version: 3.0.3 + resolution: "postcss-modules-local-by-default@npm:3.0.3" + dependencies: + icss-utils: ^4.1.1 + postcss: ^7.0.32 + postcss-selector-parser: ^6.0.2 + postcss-value-parser: ^4.1.0 + checksum: 007fd7286b4e120edfdf1a41f2006e9c8cb49e1613a4e3f0fdc184ad14273a1bbfc39ced3bc7cbad9af64bf67056e8ea0dcfda16d3057562343a48ee9ec2ccac + languageName: node + linkType: hard + +"postcss-modules-local-by-default@npm:^4.0.0": + version: 4.0.0 + resolution: "postcss-modules-local-by-default@npm:4.0.0" + dependencies: + icss-utils: ^5.0.0 + postcss-selector-parser: ^6.0.2 + postcss-value-parser: ^4.1.0 + peerDependencies: + postcss: ^8.1.0 + checksum: 8ee9c0d9918fd838854d434731371874b25c412dde135df981cc28d37d0660496389b0f8653dbcdbb6ee81f2bec90cb5b14668f6208f6f517400ac064e234c5a + languageName: node + linkType: hard + +"postcss-modules-scope@npm:^2.2.0": + version: 2.2.0 + resolution: "postcss-modules-scope@npm:2.2.0" + dependencies: + postcss: ^7.0.6 + postcss-selector-parser: ^6.0.0 + checksum: 60b4438d43e6629d72b31a5122037e5574f8a6a4629038cd74afc4e5197cebc55b76c765b6bfcc2421bc740d19c3c97e68918e560a0fe88047c2131d0966df3c + languageName: node + linkType: hard + +"postcss-modules-scope@npm:^3.0.0": + version: 3.0.0 + resolution: "postcss-modules-scope@npm:3.0.0" + dependencies: + postcss-selector-parser: ^6.0.4 + peerDependencies: + postcss: ^8.1.0 + checksum: 60af503910363689568c2c3701cb019a61b58b3d739391145185eec211bea5d50ccb6ecbe6955b39d856088072fd50ea002e40a52b50e33b181ff5c41da0308a + languageName: node + linkType: hard + +"postcss-modules-values@npm:^3.0.0": + version: 3.0.0 + resolution: "postcss-modules-values@npm:3.0.0" + dependencies: + icss-utils: ^4.0.0 + postcss: ^7.0.6 + checksum: f97b4669446810aa9c4c22538e24faee203e8462f1c7d38923c57140903bc170451dfec5974e480c2c367690735042cbfec187d209d0044d99f829f29ad0e610 + languageName: node + linkType: hard + +"postcss-modules-values@npm:^4.0.0": + version: 4.0.0 + resolution: "postcss-modules-values@npm:4.0.0" + dependencies: + icss-utils: ^5.0.0 + peerDependencies: + postcss: ^8.1.0 + checksum: dd18d7631b5619fb9921b198c86847a2a075f32e0c162e0428d2647685e318c487a2566cc8cc669fc2077ef38115cde7a068e321f46fb38be3ad49646b639dbc + languageName: node + linkType: hard + +"postcss-nested@npm:6.0.0": + version: 6.0.0 + resolution: "postcss-nested@npm:6.0.0" + dependencies: + postcss-selector-parser: ^6.0.10 + peerDependencies: + postcss: ^8.2.14 + checksum: 354ffeab951ac816e3e29dfc958194b7b2f3c749b7af9218a99a7c8ebd5cad7c04fa31b7929024a2f0855c82f02e41994f5409d6377cb97421b53a917bbbad14 + languageName: node + linkType: hard + +"postcss-nesting@npm:^10.1.6": + version: 10.2.0 + resolution: "postcss-nesting@npm:10.2.0" + dependencies: + "@csstools/selector-specificity": ^2.0.0 + postcss-selector-parser: ^6.0.10 + peerDependencies: + postcss: ^8.2 + checksum: 1f44201edeedaab3af8552a7e231cf8530785245ec56e30a7f756076ffa58ec97c12b75a8761327bf278b26aa9903351b2f3324d11784f239b07dc79295e0a77 + languageName: node + linkType: hard + +"postcss-normalize-charset@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-normalize-charset@npm:5.1.0" + peerDependencies: + postcss: ^8.2.15 + checksum: aa481584d4db48e0dbf820f992fa235e6c41ff3d4701a62d349f33c1ad4c5c7dcdea3096db9ff2a5c9497e9bed2186d594ccdb1b42d57b30f58affba5829ad9c + languageName: node + linkType: hard + +"postcss-normalize-display-values@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-normalize-display-values@npm:5.1.0" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 70b164fda885c097c02c98914fba4cd19b2382ff5f85f77e5315d88a1d477b4803f0f271d95a38e044e2a6c3b781c5c9bfb83222fc577199f2aeb0b8f4254e2f + languageName: node + linkType: hard + +"postcss-normalize-positions@npm:^5.1.1": + version: 5.1.1 + resolution: "postcss-normalize-positions@npm:5.1.1" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 910d58991fd38a7cf6ed6471e6fa4a96349690ad1a99a02e8cac46d76ba5045f2fca453088b68b05ff665afd96dc617c4674c68acaeabbe83f502e4963fb78b1 + languageName: node + linkType: hard + +"postcss-normalize-repeat-style@npm:^5.1.1": + version: 5.1.1 + resolution: "postcss-normalize-repeat-style@npm:5.1.1" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 57c3817a2107ebb17e4ceee3831d230c72a3ccc7650f4d5f12aa54f6ea766777401f4f63b2615b721350b2e8c7ae0b0bbc3f1c5ad4e7fa737c9efb92cfa0cbb0 + languageName: node + linkType: hard + +"postcss-normalize-string@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-normalize-string@npm:5.1.0" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: a5e9979998f478d385ddff865bdd8a4870af69fa8c91c9398572a299ff39b39a6bda922a48fab0d2cddc639f30159c39baaed880ed7d13cd27cc64eaa9400b3b + languageName: node + linkType: hard + +"postcss-normalize-timing-functions@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-normalize-timing-functions@npm:5.1.0" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: afb34d8e313004ae8cd92910bf1a6eb9885f29ae803cd9032b6dfe7b67a9ad93f800976f10e55170b2b08fe9484825e9272629971186812c2764c73843268237 + languageName: node + linkType: hard + +"postcss-normalize-unicode@npm:^5.1.1": + version: 5.1.1 + resolution: "postcss-normalize-unicode@npm:5.1.1" + dependencies: + browserslist: ^4.21.4 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: c102888d488d05c53ab10ffcd4e0efb892ef0cc2f9b0abe9c9b175a2d7a9c226981ca6806ed9e5c1b82a8190f2b3a8342a6de800f019b417130661b0787ff6d7 + languageName: node + linkType: hard + +"postcss-normalize-url@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-normalize-url@npm:5.1.0" + dependencies: + normalize-url: ^6.0.1 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: a016cefd1ef80f74ef9dbed50593d3b533101e93aaadfc292896fddd8d6c3eb732a9fc5cb2e0d27f79c1f60f0fdfc40b045a494b514451e9610c6acf9392eb98 + languageName: node + linkType: hard + +"postcss-normalize-whitespace@npm:^5.1.1": + version: 5.1.1 + resolution: "postcss-normalize-whitespace@npm:5.1.1" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: d7b53dd90fe369bfb9838a40096db904a41f50dadfd04247ec07d7ab5588c3d4e70d1c7f930523bd061cb74e6683cef45c6e6c4eb57ea174ee3fc99f3de222d1 + languageName: node + linkType: hard + +"postcss-ordered-values@npm:^5.1.3": + version: 5.1.3 + resolution: "postcss-ordered-values@npm:5.1.3" + dependencies: + cssnano-utils: ^3.1.0 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 55abfbd2c7267eefed62a881ed0b5c0c98409c50a589526a3ebb9f8d879979203e523b8888fa84732bdd1ac887f721287a037002fa70c27c8d33f1bcbae9d9c6 + languageName: node + linkType: hard + +"postcss-reduce-initial@npm:^5.1.1": + version: 5.1.1 + resolution: "postcss-reduce-initial@npm:5.1.1" + dependencies: + browserslist: ^4.21.4 + caniuse-api: ^3.0.0 + peerDependencies: + postcss: ^8.2.15 + checksum: ab78bb780d113c9b51113af79317a99a5db5704e3042299c30c8156de5390280feca155a625bba243b9ac9d2195048b03589eab4e925db5d0a73f5ac749e672d + languageName: node + linkType: hard + +"postcss-reduce-transforms@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-reduce-transforms@npm:5.1.0" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: caefaeb78652ad8701b94e91500e38551255e4899fa298a7357519a36cbeebae088eab4535e00f17675a1230f448c4a7077045639d496da4614a46bc41df4add + languageName: node + linkType: hard + +"postcss-selector-parser@npm:^6.0.0, postcss-selector-parser@npm:^6.0.10, postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4, postcss-selector-parser@npm:^6.0.5, postcss-selector-parser@npm:^6.0.9": + version: 6.0.11 + resolution: "postcss-selector-parser@npm:6.0.11" + dependencies: + cssesc: ^3.0.0 + util-deprecate: ^1.0.2 + checksum: 70be26abb75dec3c51be312a086e640aee4a32f18114cfbdf8feac0b6373a5494b5571370ab158174e1d692afc50c198d799ae9759afe5da1da1e629e465112c + languageName: node + linkType: hard + +"postcss-simple-vars@npm:^6.0.3": + version: 6.0.3 + resolution: "postcss-simple-vars@npm:6.0.3" + peerDependencies: + postcss: ^8.2.1 + checksum: 585e5177c9744a5cbc71aaba448dc58376be4593ba1a43832e36627242b47d8d9b2e1cf640e0cfb56679f3f5fb92586fac65bc54f5f5cfb4328b8a076bdd87cb + languageName: node + linkType: hard + +"postcss-simple-vars@npm:^7.0.0": + version: 7.0.1 + resolution: "postcss-simple-vars@npm:7.0.1" + peerDependencies: + postcss: ^8.2.1 + checksum: b59c750b2f406a18b133184144b5ffefbd21a2179c5bf5d54651269c0b6bb0110c2bbed33a9161d8b93f8a11cf7725da0a65653e7c2786c8a59f937591115a33 + languageName: node + linkType: hard + +"postcss-svgo@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-svgo@npm:5.1.0" + dependencies: + postcss-value-parser: ^4.2.0 + svgo: ^2.7.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 309634a587e38fef244648bc9cd1817e12144868d24f1173d87b1edc14a4a7fca614962b2cb9d93f4801e11bd8d676083986ad40ebab4438cb84731ce1571994 + languageName: node + linkType: hard + +"postcss-unique-selectors@npm:^5.1.1": + version: 5.1.1 + resolution: "postcss-unique-selectors@npm:5.1.1" + dependencies: + postcss-selector-parser: ^6.0.5 + peerDependencies: + postcss: ^8.2.15 + checksum: 484f6409346d6244c134c5cdcd62f4f2751b269742f95222f13d8bac5fb224471ffe04e28a354670cbe0bdc2707778ead034fc1b801b473ffcbea5436807de30 + languageName: node + linkType: hard + +"postcss-value-parser@npm:^3.3.0": + version: 3.3.1 + resolution: "postcss-value-parser@npm:3.3.1" + checksum: 23eed98d8eeadb1f9ef1db4a2757da0f1d8e7c1dac2a38d6b35d971aab9eb3c6d8a967d0e9f435558834ffcd966afbbe875a56bcc5bcdd09e663008c106b3e47 + languageName: node + linkType: hard + +"postcss-value-parser@npm:^4.0.0, postcss-value-parser@npm:^4.1.0, postcss-value-parser@npm:^4.2.0": + version: 4.2.0 + resolution: "postcss-value-parser@npm:4.2.0" + checksum: f4142a4f56565f77c1831168e04e3effd9ffcc5aebaf0f538eee4b2d465adfd4b85a44257bb48418202a63806a7da7fe9f56c330aebb3cac898e46b4cbf49161 + languageName: node + linkType: hard + +"postcss@npm:^7.0.14, postcss@npm:^7.0.26, postcss@npm:^7.0.32, postcss@npm:^7.0.36, postcss@npm:^7.0.5, postcss@npm:^7.0.6": + version: 7.0.39 + resolution: "postcss@npm:7.0.39" + dependencies: + picocolors: ^0.2.1 + source-map: ^0.6.1 + checksum: fd27ee808c0d02407582cccfad4729033e2b439d56cd45534fb39aaad308bb35a290f3b7db5f2394980e8756f9381b458a625618550808c5ff01a125f51efc53 + languageName: node + linkType: hard + +"postcss@npm:^8.2.15, postcss@npm:^8.4.14, postcss@npm:^8.4.18, postcss@npm:^8.4.19": + version: 8.4.21 + resolution: "postcss@npm:8.4.21" + dependencies: + nanoid: ^3.3.4 + picocolors: ^1.0.0 + source-map-js: ^1.0.2 + checksum: a26e7cc86a1807d624d9965914c26c20faa3f237184cbd69db537387f6a4f62df97347549144284d47e9e8e27e7c60e797cb3b92ad36cb2f4c3c9cb3b73f9758 + languageName: node + linkType: hard + +"prelude-ls@npm:^1.2.1": + version: 1.2.1 + resolution: "prelude-ls@npm:1.2.1" + checksum: b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd + languageName: node + linkType: hard + +"prelude-ls@npm:~1.1.2": + version: 1.1.2 + resolution: "prelude-ls@npm:1.1.2" + checksum: 7284270064f74e0bb7f04eb9bff7be677e4146417e599ccc9c1200f0f640f8b11e592d94eb1b18f7aa9518031913bb42bea9c86af07ba69902864e61005d6f18 + languageName: node + linkType: hard + +"prepend-http@npm:^1.0.1": + version: 1.0.4 + resolution: "prepend-http@npm:1.0.4" + checksum: c6c173ca439e58163ba7bea7cbba52a1ed11e3e3da1c048da296f37d4b7654f78f7304e03f76d5923f4b83af7e2d55533e0f79064209c75b743ccddee13904f8 + languageName: node + linkType: hard + +"prettier@npm:>=2.2.1 <=2.3.0": + version: 2.3.0 + resolution: "prettier@npm:2.3.0" + bin: + prettier: bin-prettier.js + checksum: b9f434af2f25a37aad0b133894827e980885eb8bf317444c9dde0401ed2c7f463f9996d691f5ee5a0a4450ab46a894cd6557516b561e2522821522ce1f4c6668 + languageName: node + linkType: hard + +"prettier@npm:^2.6.2": + version: 2.8.3 + resolution: "prettier@npm:2.8.3" + bin: + prettier: bin-prettier.js + checksum: 373fda1908c8f7f06e6b9966986f35784152d4f10c907c7153062fe36542358d696433450f3efb356b7438c855a8d8b4133c3e486057dc63117d94596ff3f5f1 + languageName: node + linkType: hard + +"pretty-bytes@npm:^5.3.0, pretty-bytes@npm:^5.4.1, pretty-bytes@npm:^5.6.0": + version: 5.6.0 + resolution: "pretty-bytes@npm:5.6.0" + checksum: f69f494dcc1adda98dbe0e4a36d301e8be8ff99bfde7a637b2ee2820e7cb583b0fc0f3a63b0e3752c01501185a5cf38602c7be60da41bdf84ef5b70e89c370f3 + languageName: node + linkType: hard + +"pretty-error@npm:^2.1.1": + version: 2.1.2 + resolution: "pretty-error@npm:2.1.2" + dependencies: + lodash: ^4.17.20 + renderkid: ^2.0.4 + checksum: 779743faf707308e5d07c53c3ec94596c0cb631c92104a2721dd5d021ade39505a9151c5a5f838dfd26b02a06752c410eb6de1769c4fe327c90bd083f61a1fa1 + languageName: node + linkType: hard + +"pretty-error@npm:^4.0.0": + version: 4.0.0 + resolution: "pretty-error@npm:4.0.0" + dependencies: + lodash: ^4.17.20 + renderkid: ^3.0.0 + checksum: dc292c087e2857b2e7592784ab31e37a40f3fa918caa11eba51f9fb2853e1d4d6e820b219917e35f5721d833cfd20fdf4f26ae931a90fd1ad0cae2125c345138 + languageName: node + linkType: hard + +"pretty-format@npm:^27.0.2": + version: 27.5.1 + resolution: "pretty-format@npm:27.5.1" + dependencies: + ansi-regex: ^5.0.1 + ansi-styles: ^5.0.0 + react-is: ^17.0.1 + checksum: 0cbda1031aa30c659e10921fa94e0dd3f903ecbbbe7184a729ad66f2b6e7f17891e8c7d7654c458fa4ccb1a411ffb695b4f17bbcd3fe075fabe181027c4040ed + languageName: node + linkType: hard + +"pretty-format@npm:^29.3.1": + version: 29.3.1 + resolution: "pretty-format@npm:29.3.1" + dependencies: + "@jest/schemas": ^29.0.0 + ansi-styles: ^5.0.0 + react-is: ^18.0.0 + checksum: 8c0b27a7f31c678a382de70217c524b752b14c6aaf56f94098b04208d91965e4b4f92c268e6c1124c20c3cf8de146dd4ba6a4d1f1033ae67c0dcccd4de23e98b + languageName: node + linkType: hard + +"pretty-hrtime@npm:^1.0.3": + version: 1.0.3 + resolution: "pretty-hrtime@npm:1.0.3" + checksum: 67cb3fc283a72252b49ac488647e6a01b78b7aa1b8f2061834aa1650691229081518ef3ca940f77f41cc8a8f02ba9eeb74b843481596670209e493062f2e89e0 + languageName: node + linkType: hard + +"process-nextick-args@npm:~2.0.0": + version: 2.0.1 + resolution: "process-nextick-args@npm:2.0.1" + checksum: bec089239487833d46b59d80327a1605e1c5287eaad770a291add7f45fda1bb5e28b38e0e061add0a1d0ee0984788ce74fa394d345eed1c420cacf392c554367 + languageName: node + linkType: hard + +"process@npm:^0.11.10": + version: 0.11.10 + resolution: "process@npm:0.11.10" + checksum: 40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3 + languageName: node + linkType: hard + +"promise-inflight@npm:^1.0.1, promise-inflight@npm:~1.0.1": + version: 1.0.1 + resolution: "promise-inflight@npm:1.0.1" + checksum: d179d148d98fbff3d815752fa9a08a87d3190551d1420f17c4467f628214db12235ae068d98cd001f024453676d8985af8f28f002345646c4ece4600a79620bc + languageName: node + linkType: hard + +"promise-retry@npm:^1.1.1": + version: 1.1.1 + resolution: "promise-retry@npm:1.1.1" + dependencies: + err-code: ^1.0.0 + retry: ^0.10.0 + checksum: 313d5fa2563fb87654e7ddd087a89aaa860c329a52e8596d0ba6c1f4cdab61ff5265cea4533d84b534c65403061ecc90283c298ef2e2630661ce7e1e5df05a69 + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: ^2.0.2 + retry: ^0.12.0 + checksum: 9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 + languageName: node + linkType: hard + +"promise.allsettled@npm:^1.0.0": + version: 1.0.6 + resolution: "promise.allsettled@npm:1.0.6" + dependencies: + array.prototype.map: ^1.0.5 + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + get-intrinsic: ^1.1.3 + iterate-value: ^1.0.2 + checksum: 34f82313be2e9384282935d91bd6c13bb444351f86b75f218f4a388eed59be9e0db87daeafe909997d4afd8f75049b78adcc51ecfe399cc6b60f23b77f235ad9 + languageName: node + linkType: hard + +"promise.prototype.finally@npm:^3.1.0": + version: 3.1.4 + resolution: "promise.prototype.finally@npm:3.1.4" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: 7efbf88b4cc2b38452ef1d3ee934c61f3ed7034b0f4e9a072f6eded123701e409e8f2aadba870f93a5012e766d1cc9b4fd076e665a9e4adace817c7e7ea88d28 + languageName: node + linkType: hard + +"promise@npm:^7.1.1": + version: 7.3.1 + resolution: "promise@npm:7.3.1" + dependencies: + asap: ~2.0.3 + checksum: 742e5c0cc646af1f0746963b8776299701ad561ce2c70b49365d62c8db8ea3681b0a1bf0d4e2fe07910bf72f02d39e51e8e73dc8d7503c3501206ac908be107f + languageName: node + linkType: hard + +"prompts@npm:^2.0.1, prompts@npm:^2.4.0": + version: 2.4.2 + resolution: "prompts@npm:2.4.2" + dependencies: + kleur: ^3.0.3 + sisteransi: ^1.0.5 + checksum: 16f1ac2977b19fe2cf53f8411cc98db7a3c8b115c479b2ca5c82b5527cd937aa405fa04f9a5960abeb9daef53191b53b4d13e35c1f5d50e8718c76917c5f1ea4 + languageName: node + linkType: hard + +"promzard@npm:^0.3.0": + version: 0.3.0 + resolution: "promzard@npm:0.3.0" + dependencies: + read: 1 + checksum: 7fd8dbcd9764b35092da65867cc60fdcf2ea85d77e8ed1ae348ec0af1a22616f74053ccf8dad7d8de01e1e3aafe349d77ef56653c2db3791589ac2a8ef485149 + languageName: node + linkType: hard + +"prop-types@npm:15.x, prop-types@npm:^15.0.0, prop-types@npm:^15.5.0, prop-types@npm:^15.5.10, prop-types@npm:^15.5.8, prop-types@npm:^15.6.0, prop-types@npm:^15.6.1, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" + dependencies: + loose-envify: ^1.4.0 + object-assign: ^4.1.1 + react-is: ^16.13.1 + checksum: 59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077 + languageName: node + linkType: hard + +"property-information@npm:^5.0.0, property-information@npm:^5.3.0": + version: 5.6.0 + resolution: "property-information@npm:5.6.0" + dependencies: + xtend: ^4.0.0 + checksum: d54b77c31dc13bb6819559080b2c67d37d94be7dc271f404f139a16a57aa96fcc0b3ad806d4a5baef9e031744853e4afe3df2e37275aacb1f78079bbb652c5af + languageName: node + linkType: hard + +"proto-list@npm:~1.2.1": + version: 1.2.4 + resolution: "proto-list@npm:1.2.4" + checksum: b9179f99394ec8a68b8afc817690185f3b03933f7b46ce2e22c1930dc84b60d09f5ad222beab4e59e58c6c039c7f7fcf620397235ef441a356f31f9744010e12 + languageName: node + linkType: hard + +"protoduck@npm:^5.0.1": + version: 5.0.1 + resolution: "protoduck@npm:5.0.1" + dependencies: + genfun: ^5.0.0 + checksum: 8b9057ac37e97b7e3fe82da7fc8f233781ea0dd3ad5ed514a3064e2e0ed0ee444ed4d115561595a9f5b1b1ef59aaa3d1d7f05ea7670357f8f74e2244245cc239 + languageName: node + linkType: hard + +"proxy-addr@npm:~2.0.7": + version: 2.0.7 + resolution: "proxy-addr@npm:2.0.7" + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + checksum: c3eed999781a35f7fd935f398b6d8920b6fb00bbc14287bc6de78128ccc1a02c89b95b56742bf7cf0362cc333c61d138532049c7dedc7a328ef13343eff81210 + languageName: node + linkType: hard + +"proxy-from-env@npm:1.0.0": + version: 1.0.0 + resolution: "proxy-from-env@npm:1.0.0" + checksum: c64df9b21f7f820dc882cd6f7f81671840acd28b9688ee3e3e6af47a56ec7f0edcabe5bc96b32b26218b35eeff377bcc27ac27f89b6b21401003e187ff13256f + languageName: node + linkType: hard + +"prr@npm:~1.0.1": + version: 1.0.1 + resolution: "prr@npm:1.0.1" + checksum: 5b9272c602e4f4472a215e58daff88f802923b84bc39c8860376bb1c0e42aaf18c25d69ad974bd06ec6db6f544b783edecd5502cd3d184748d99080d68e4be5f + languageName: node + linkType: hard + +"pseudomap@npm:^1.0.2": + version: 1.0.2 + resolution: "pseudomap@npm:1.0.2" + checksum: 5a91ce114c64ed3a6a553aa7d2943868811377388bb31447f9d8028271bae9b05b340fe0b6961a64e45b9c72946aeb0a4ab635e8f7cb3715ffd0ff2beeb6a679 + languageName: node + linkType: hard + +"psl@npm:^1.1.28": + version: 1.9.0 + resolution: "psl@npm:1.9.0" + checksum: 6a3f805fdab9442f44de4ba23880c4eba26b20c8e8e0830eff1cb31007f6825dace61d17203c58bfe36946842140c97a1ba7f67bc63ca2d88a7ee052b65d97ab + languageName: node + linkType: hard + +"public-encrypt@npm:^4.0.0": + version: 4.0.3 + resolution: "public-encrypt@npm:4.0.3" + dependencies: + bn.js: ^4.1.0 + browserify-rsa: ^4.0.0 + create-hash: ^1.1.0 + parse-asn1: ^5.0.0 + randombytes: ^2.0.1 + safe-buffer: ^5.1.2 + checksum: 6c2cc19fbb554449e47f2175065d6b32f828f9b3badbee4c76585ac28ae8641aafb9bb107afc430c33c5edd6b05dbe318df4f7d6d7712b1093407b11c4280700 + languageName: node + linkType: hard + +"pump@npm:^2.0.0": + version: 2.0.1 + resolution: "pump@npm:2.0.1" + dependencies: + end-of-stream: ^1.1.0 + once: ^1.3.1 + checksum: f1fe8960f44d145f8617ea4c67de05392da4557052980314c8f85081aee26953bdcab64afad58a2b1df0e8ff7203e3710e848cbe81a01027978edc6e264db355 + languageName: node + linkType: hard + +"pump@npm:^3.0.0": + version: 3.0.0 + resolution: "pump@npm:3.0.0" + dependencies: + end-of-stream: ^1.1.0 + once: ^1.3.1 + checksum: bbdeda4f747cdf47db97428f3a135728669e56a0ae5f354a9ac5b74556556f5446a46f720a8f14ca2ece5be9b4d5d23c346db02b555f46739934cc6c093a5478 + languageName: node + linkType: hard + +"pumpify@npm:^1.3.3": + version: 1.5.1 + resolution: "pumpify@npm:1.5.1" + dependencies: + duplexify: ^3.6.0 + inherits: ^2.0.3 + pump: ^2.0.0 + checksum: 0bcabf9e3dbf2d0cc1f9b84ac80d3c75386111caf8963bfd98817a1e2192000ac0ccc804ca6ccd5b2b8430fdb71347b20fb2f014fe3d41adbacb1b502a841c45 + languageName: node + linkType: hard + +"punycode@npm:1.3.2": + version: 1.3.2 + resolution: "punycode@npm:1.3.2" + checksum: 281fd20eaf4704f79d80cb0dc65065bf6452ee67989b3e8941aed6360a5a9a8a01d3e2ed71d0bde3cd74fb5a5dd9db4160bed5a8c20bed4b6764c24ce4c7d2d2 + languageName: node + linkType: hard + +"punycode@npm:^1.2.4": + version: 1.4.1 + resolution: "punycode@npm:1.4.1" + checksum: 354b743320518aef36f77013be6e15da4db24c2b4f62c5f1eb0529a6ed02fbaf1cb52925785f6ab85a962f2b590d9cd5ad730b70da72b5f180e2556b8bd3ca08 + languageName: node + linkType: hard + +"punycode@npm:^2.1.0, punycode@npm:^2.1.1": + version: 2.2.0 + resolution: "punycode@npm:2.2.0" + checksum: c4c67082ab53d4304e7f2bb0635290db5baa84758cd4ade4219f24c16ecd803dd5022054186e9c778a4df851cc989b02201a35eaed6b08cb9cabec8b8e2d8332 + languageName: node + linkType: hard + +"pure-color@npm:^1.2.0": + version: 1.3.0 + resolution: "pure-color@npm:1.3.0" + checksum: 50d0e088ad0349bdd508cddf7c7afbb2d14ba3c047628dbfcfddf467a98f10462caf91f3227172ada88f64afaf761c499ecba0d4053b06926f0f914769be24b9 + languageName: node + linkType: hard + +"qrcode-terminal@npm:^0.12.0": + version: 0.12.0 + resolution: "qrcode-terminal@npm:0.12.0" + bin: + qrcode-terminal: ./bin/qrcode-terminal.js + checksum: 1d8996a743d6c95e22056bd45fe958c306213adc97d7ef8cf1e03bc1aeeb6f27180a747ec3d761141921351eb1e3ca688f7b673ab54cdae9fa358dffaa49563c + languageName: node + linkType: hard + +"qs@npm:6.11.0, qs@npm:^6.10.0": + version: 6.11.0 + resolution: "qs@npm:6.11.0" + dependencies: + side-channel: ^1.0.4 + checksum: 4e4875e4d7c7c31c233d07a448e7e4650f456178b9dd3766b7cfa13158fdb24ecb8c4f059fa91e820dc6ab9f2d243721d071c9c0378892dcdad86e9e9a27c68f + languageName: node + linkType: hard + +"qs@npm:~6.10.3": + version: 6.10.4 + resolution: "qs@npm:6.10.4" + dependencies: + side-channel: ^1.0.4 + checksum: 7a8c9d77968aeccb769aedd7e047c0e0109dad0cfa57cab1ad906f4069fd58f361b80abd2de5854ba9a09b4c5d06d6a2c82108766f1f1527572fe6130deaa471 + languageName: node + linkType: hard + +"qs@npm:~6.5.2": + version: 6.5.3 + resolution: "qs@npm:6.5.3" + checksum: 6631d4f2fa9d315e480662646745a4aa3a708817fbffe2cbdacec8ab9be130f92740c66191770fe9b704bc5fa9c1cc1f6596f55ad132fef7bd3ad1582f199eb0 + languageName: node + linkType: hard + +"query-string@npm:^6.14.1": + version: 6.14.1 + resolution: "query-string@npm:6.14.1" + dependencies: + decode-uri-component: ^0.2.0 + filter-obj: ^1.1.0 + split-on-first: ^1.0.0 + strict-uri-encode: ^2.0.0 + checksum: 900e0fa788000e9dc5f929b6f4141742dcf281f02d3bab9714bc83bea65fab3de75169ea8d61f19cda996bc0dcec72e156efe3c5614c6bce65dcf234ac955b14 + languageName: node + linkType: hard + +"query-string@npm:^7.1.1": + version: 7.1.3 + resolution: "query-string@npm:7.1.3" + dependencies: + decode-uri-component: ^0.2.2 + filter-obj: ^1.1.0 + split-on-first: ^1.0.0 + strict-uri-encode: ^2.0.0 + checksum: a896c08e9e0d4f8ffd89a572d11f668c8d0f7df9c27c6f49b92ab31366d3ba0e9c331b9a620ee747893436cd1f2f821a6327e2bc9776bde2402ac6c270b801b2 + languageName: node + linkType: hard + +"querystring-es3@npm:^0.2.0": + version: 0.2.1 + resolution: "querystring-es3@npm:0.2.1" + checksum: 476938c1adb45c141f024fccd2ffd919a3746e79ed444d00e670aad68532977b793889648980e7ca7ff5ffc7bfece623118d0fbadcaf217495eeb7059ae51580 + languageName: node + linkType: hard + +"querystring@npm:0.2.0": + version: 0.2.0 + resolution: "querystring@npm:0.2.0" + checksum: 2036c9424beaacd3978bac9e4ba514331cc73163bea7bf3ad7e2c7355e55501938ec195312c607753f9c6e70b1bf9dfcda38db6241bd299c034e27ac639d64ed + languageName: node + linkType: hard + +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 + languageName: node + linkType: hard + +"quick-lru@npm:^5.1.1": + version: 5.1.1 + resolution: "quick-lru@npm:5.1.1" + checksum: a24cba5da8cec30d70d2484be37622580f64765fb6390a928b17f60cd69e8dbd32a954b3ff9176fa1b86d86ff2ba05252fae55dc4d40d0291c60412b0ad096da + languageName: node + linkType: hard + +"qw@npm:^1.0.2": + version: 1.0.2 + resolution: "qw@npm:1.0.2" + checksum: 373404747b2b9117d3a58b5d7f609a1355eb98f2e635452d5b359ce53931518fdea72cf54e1ac2efd7edad811af4cef7b81d15c20d8cf36fac0deb1b44b9d7a3 + languageName: node + linkType: hard + +"raf@npm:^3.4.0, raf@npm:^3.4.1": + version: 3.4.1 + resolution: "raf@npm:3.4.1" + dependencies: + performance-now: ^2.1.0 + checksum: 337f0853c9e6a77647b0f499beedafea5d6facfb9f2d488a624f88b03df2be72b8a0e7f9118a3ff811377d534912039a3311815700d2b6d2313f82f736f9eb6e + languageName: node + linkType: hard + +"ramda@npm:^0.28.0": + version: 0.28.0 + resolution: "ramda@npm:0.28.0" + checksum: 0f9dc0cc3b0432ff047f1e2a5e58860c531a84574674c0f52fef535efc6e1e07fa3851102fff3da7dd551a592c743f6f6fa521379a6aa5fe50266f8af8f0b570 + languageName: node + linkType: hard + +"randombytes@npm:^2.0.0, randombytes@npm:^2.0.1, randombytes@npm:^2.0.5, randombytes@npm:^2.1.0": + version: 2.1.0 + resolution: "randombytes@npm:2.1.0" + dependencies: + safe-buffer: ^5.1.0 + checksum: 50395efda7a8c94f5dffab564f9ff89736064d32addf0cc7e8bf5e4166f09f8ded7a0849ca6c2d2a59478f7d90f78f20d8048bca3cdf8be09d8e8a10790388f3 + languageName: node + linkType: hard + +"randomfill@npm:^1.0.3": + version: 1.0.4 + resolution: "randomfill@npm:1.0.4" + dependencies: + randombytes: ^2.0.5 + safe-buffer: ^5.1.0 + checksum: 11aeed35515872e8f8a2edec306734e6b74c39c46653607f03c68385ab8030e2adcc4215f76b5e4598e028c4750d820afd5c65202527d831d2a5f207fe2bc87c + languageName: node + linkType: hard + +"range-parser@npm:^1.2.1, range-parser@npm:~1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: 96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0 + languageName: node + linkType: hard + +"raw-body@npm:2.5.1": + version: 2.5.1 + resolution: "raw-body@npm:2.5.1" + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + checksum: 5dad5a3a64a023b894ad7ab4e5c7c1ce34d3497fc7138d02f8c88a3781e68d8a55aa7d4fd3a458616fa8647cc228be314a1c03fb430a07521de78b32c4dd09d2 + languageName: node + linkType: hard + +"raw-loader@npm:^4.0.2": + version: 4.0.2 + resolution: "raw-loader@npm:4.0.2" + dependencies: + loader-utils: ^2.0.0 + schema-utils: ^3.0.0 + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + checksum: 981ebe65e1cee7230300d21ba6dcd8bd23ea81ef4ad2b167c0f62d93deba347f27921d330be848634baab3831cf9f38900af6082d6416c2e937fe612fa6a74ff + languageName: node + linkType: hard + +"rc-align@npm:^2.4.0": + version: 2.4.5 + resolution: "rc-align@npm:2.4.5" + dependencies: + babel-runtime: ^6.26.0 + dom-align: ^1.7.0 + prop-types: ^15.5.8 + rc-util: ^4.0.4 + checksum: 460b3717636f9eea8e0537af4b8179690c1deeda2628bdcce6f218b099ebcf8ea48ad8b487b5d971e59fbbce24f10abca89d98eeff99316e8f056658c7854df0 + languageName: node + linkType: hard + +"rc-animate@npm:2.x": + version: 2.11.1 + resolution: "rc-animate@npm:2.11.1" + dependencies: + babel-runtime: 6.x + classnames: ^2.2.6 + css-animation: ^1.3.2 + prop-types: 15.x + raf: ^3.4.0 + rc-util: ^4.15.3 + react-lifecycles-compat: ^3.0.4 + checksum: a4d31bb5065031de58ee9f4a06ebf4bd4f8e4b8a10103fbad5a68ec39c655f7bff84df21b05356a94405fcb3de0dd7d9e45d526042199d80c88d02a57c3acbdc + languageName: node + linkType: hard + +"rc-time-picker@npm:^3.7.3": + version: 3.7.3 + resolution: "rc-time-picker@npm:3.7.3" + dependencies: + classnames: 2.x + moment: 2.x + prop-types: ^15.5.8 + raf: ^3.4.1 + rc-trigger: ^2.2.0 + react-lifecycles-compat: ^3.0.4 + checksum: f9c3e39a40a3db2c0a89c07bdbae82b053ea99cf169ec26f26b16492ff37961f73541756177b28e618028246baa72148ba2083b9d25e9053da4319f0f3d2d268 + languageName: node + linkType: hard + +"rc-trigger@npm:^2.2.0": + version: 2.6.5 + resolution: "rc-trigger@npm:2.6.5" + dependencies: + babel-runtime: 6.x + classnames: ^2.2.6 + prop-types: 15.x + rc-align: ^2.4.0 + rc-animate: 2.x + rc-util: ^4.4.0 + react-lifecycles-compat: ^3.0.4 + checksum: 29ba7a0eab6a281e77754050c84a80d9aaa4134e89db8319a61d9d1cc9296b873c208135d01495733e3f8e2bbe4c90d2fa28754da92a5f0b1064ee08b0dd1d4d + languageName: node + linkType: hard + +"rc-util@npm:^4.0.4, rc-util@npm:^4.15.3, rc-util@npm:^4.4.0": + version: 4.21.1 + resolution: "rc-util@npm:4.21.1" + dependencies: + add-dom-event-listener: ^1.1.0 + prop-types: ^15.5.10 + react-is: ^16.12.0 + react-lifecycles-compat: ^3.0.4 + shallowequal: ^1.1.0 + checksum: f91fe2ba98658c1bd67d8d3edd5ed5a2425ff44d3cd30f96b71b6058bd6c852bbf82e00716e219c10f6fac20e9b9cbb447e39cd69e12cdcfeda6dcd824adc790 + languageName: node + linkType: hard + +"rc@npm:^1.0.1, rc@npm:^1.1.6": + version: 1.2.8 + resolution: "rc@npm:1.2.8" + dependencies: + deep-extend: ^0.6.0 + ini: ~1.3.0 + minimist: ^1.2.0 + strip-json-comments: ~2.0.1 + bin: + rc: ./cli.js + checksum: 24a07653150f0d9ac7168e52943cc3cb4b7a22c0e43c7dff3219977c2fdca5a2760a304a029c20811a0e79d351f57d46c9bde216193a0f73978496afc2b85b15 + languageName: node + linkType: hard + +"react-addons-pure-render-mixin@npm:^15.6.2": + version: 15.6.3 + resolution: "react-addons-pure-render-mixin@npm:15.6.3" + dependencies: + object-assign: ^4.1.0 + checksum: 4029513771abcae0c738e310469bdd1cdc95a2737d4f4c9fe23b749a75c6c60fb2f9374134f75db6910769a2a9ed80bc2495c148e21ac9f8f6b48eb5aaa39107 + languageName: node + linkType: hard + +"react-async-script@npm:^1.0.0": + version: 1.2.0 + resolution: "react-async-script@npm:1.2.0" + dependencies: + hoist-non-react-statics: ^3.3.0 + prop-types: ^15.5.0 + peerDependencies: + react: ">=16.4.1" + checksum: 89450912110c380abc08258ce17d2fb18d31d6b7179a74f6bc504c0761a4ca271edb671e402fa8e5ea4250b5c17fa953af80a9f1c4ebb26c9e81caee8476c903 + languageName: node + linkType: hard + +"react-base16-styling@npm:^0.6.0": + version: 0.6.0 + resolution: "react-base16-styling@npm:0.6.0" + dependencies: + base16: ^1.0.0 + lodash.curry: ^4.0.1 + lodash.flow: ^3.3.0 + pure-color: ^1.2.0 + checksum: 4887ac57b36fedc7e1ebc99ae431c5feb07d60a9150770d0ca3a59f4ae7059434ea8813ca4f915e7434d4d8d8529b9ba072ceb85041fd52ca1cd6289c57c9621 + languageName: node + linkType: hard + +"react-circular-progressbar@npm:^2.1.0": + version: 2.1.0 + resolution: "react-circular-progressbar@npm:2.1.0" + peerDependencies: + react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 4019235abdf588baa97491fb53e258e83e467aecce14672120bca687719fa025efd4a3b63b44c7a59867ebba547ee6edf035f3b883585d46332c5334cf505c85 + languageName: node + linkType: hard + +"react-confirm@npm:^0.2.3": + version: 0.2.3 + resolution: "react-confirm@npm:0.2.3" + peerDependencies: + react: 18.x + react-dom: 18.x + checksum: 05252ed4c773416c3011df16ecf6565b5fcde41556d07ef8739a7f4df48a4fcffec2508e864ac311f57fa3e710defd155d474938ad0ac91e504a03c3841d3efd + languageName: node + linkType: hard + +"react-daterange-picker@npm:^2.0.1": + version: 2.0.1 + resolution: "react-daterange-picker@npm:2.0.1" + dependencies: + calendar: ^0.1.0 + classnames: ^2.1.1 + create-react-class: ^15.6.3 + immutable: ^3.7.2 + prop-types: ^15.6.0 + react-addons-pure-render-mixin: ^15.6.2 + peerDependencies: + moment: ^2.18.1 + moment-range: ^3.0.3 + react: 0.14.x || 15.x.x || 16.x.x + react-dom: 0.14.x || 15.x.x || 16.x.x + checksum: 52d950831eefed98678dbd6e09e18d009f6c5d61aa7ba2a2ae859bbc8cbd7f2e049be83a846a45844ed20227fb900a5aa4c51e25b004f6b63278349575c4ce02 + languageName: node + linkType: hard + +"react-dnd-html5-backend@npm:^15.1.2": + version: 15.1.3 + resolution: "react-dnd-html5-backend@npm:15.1.3" + dependencies: + dnd-core: 15.1.2 + checksum: d19523cd622ff6b9eedf0bc40ac75c604e2740de64116f7cd764610a6e7e5d02f35e2147b732676e5ea76fd61225c83fa11cb3c1b0fcfe123ab0470a5334ddce + languageName: node + linkType: hard + +"react-dnd@npm:^16.0.1": + version: 16.0.1 + resolution: "react-dnd@npm:16.0.1" + dependencies: + "@react-dnd/invariant": ^4.0.1 + "@react-dnd/shallowequal": ^4.0.1 + dnd-core: ^16.0.1 + fast-deep-equal: ^3.1.3 + hoist-non-react-statics: ^3.3.2 + peerDependencies: + "@types/hoist-non-react-statics": ">= 3.3.1" + "@types/node": ">= 12" + "@types/react": ">= 16" + react: ">= 16.14" + peerDependenciesMeta: + "@types/hoist-non-react-statics": + optional: true + "@types/node": + optional: true + "@types/react": + optional: true + checksum: d069435750f0d6653cfa2b951cac8abb3583fb144ff134a20176608877d9c5964c63384ebbacaa0fdeef819b592a103de0d8e06f3b742311d64a029ffed0baa3 + languageName: node + linkType: hard + +"react-docgen-typescript@npm:^2.1.1": + version: 2.2.2 + resolution: "react-docgen-typescript@npm:2.2.2" + peerDependencies: + typescript: ">= 4.3.x" + checksum: d31a061a21b5d4b67d4af7bc742541fd9e16254bd32861cd29c52565bc2175f40421a3550d52b6a6b0d0478e7cc408558eb0060a0bdd2957b02cfceeb0ee1e88 + languageName: node + linkType: hard + +"react-docgen@npm:^5.0.0": + version: 5.4.3 + resolution: "react-docgen@npm:5.4.3" + dependencies: + "@babel/core": ^7.7.5 + "@babel/generator": ^7.12.11 + "@babel/runtime": ^7.7.6 + ast-types: ^0.14.2 + commander: ^2.19.0 + doctrine: ^3.0.0 + estree-to-babel: ^3.1.0 + neo-async: ^2.6.1 + node-dir: ^0.1.10 + strip-indent: ^3.0.0 + bin: + react-docgen: bin/react-docgen.js + checksum: c920e9611e08317f8fdae707114cf02baaa18e2f1bd23ed18f57e66b9e1042e51dc98cc9de828b03d018ccc4e26300c9a6c4f74e862fc94dc64029267c801a01 + languageName: node + linkType: hard + +"react-dom@npm:^18.2.0": + version: 18.2.0 + resolution: "react-dom@npm:18.2.0" + dependencies: + loose-envify: ^1.1.0 + scheduler: ^0.23.0 + peerDependencies: + react: ^18.2.0 + checksum: 66dfc5f93e13d0674e78ef41f92ed21dfb80f9c4ac4ac25a4b51046d41d4d2186abc915b897f69d3d0ebbffe6184e7c5876f2af26bfa956f179225d921be713a + languageName: node + linkType: hard + +"react-draggable@npm:^4.4.5": + version: 4.4.5 + resolution: "react-draggable@npm:4.4.5" + dependencies: + clsx: ^1.1.1 + prop-types: ^15.8.1 + peerDependencies: + react: ">= 16.3.0" + react-dom: ">= 16.3.0" + checksum: d950e25b41092ffd689e9882c287f7f49dbd8eb7e8a220af6e08c0dc06f1ae778871b0f414e30bd2891a96c6be1f673c3a698c0e642903542ff5ab5b0cbaf805 + languageName: node + linkType: hard + +"react-element-to-jsx-string@npm:^14.3.4": + version: 14.3.4 + resolution: "react-element-to-jsx-string@npm:14.3.4" + dependencies: + "@base2/pretty-print-object": 1.0.1 + is-plain-object: 5.0.0 + react-is: 17.0.2 + peerDependencies: + react: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 + react-dom: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 + checksum: 4ead664b2e26e76af57c9ce2f2a46e79fda1d3a408afb5f34d03357d195b7f41a1a86bb9286b6d6ba76c9c2611fe56bc038665cf27fdb56f571d235ddfce9ffb + languageName: node + linkType: hard + +"react-fast-compare@npm:^3.0.1": + version: 3.2.0 + resolution: "react-fast-compare@npm:3.2.0" + checksum: 2a7d75ce9fb5da1e3c01f74a5cd592f3369a8cc8d44e93654bf147ab221f430238e8be70677e896f2bfcb96a1cb7a47a8d05d84633de764a9d57d27005a4bb9e + languageName: node + linkType: hard + +"react-google-recaptcha@npm:^1.1.0": + version: 1.1.0 + resolution: "react-google-recaptcha@npm:1.1.0" + dependencies: + prop-types: ^15.5.0 + react-async-script: ^1.0.0 + peerDependencies: + react: ">=16.4.1" + checksum: 1d6fc0df8c2ed9c1fd9291998c3e0539940790b95f08b04165fc83712ef14fcbe911a9373af1df8ab4800f4851da810a8af902307289139fa87ce9f25de81a90 + languageName: node + linkType: hard + +"react-highlight@npm:^0.14.0": + version: 0.14.0 + resolution: "react-highlight@npm:0.14.0" + dependencies: + highlight.js: ^10.5.0 + peerDependencies: + react: ^15.0.0 || ^16.0.0 || ^17.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: e2a4224977da519025986fa9a844f37d7ceea9c01004ddd6b2bbd5e2a421b949e1b72d2b1e3b4619d69c27faacf429b13f5aae9c1a7fe81edc93caf263a7eaf6 + languageName: node + linkType: hard + +"react-inspector@npm:^5.1.0": + version: 5.1.1 + resolution: "react-inspector@npm:5.1.1" + dependencies: + "@babel/runtime": ^7.0.0 + is-dom: ^1.0.0 + prop-types: ^15.0.0 + peerDependencies: + react: ^16.8.4 || ^17.0.0 + checksum: 64282953f1e9318501ae9ff64dc955845fce0b543577fcc5b6a5cf786d9a1872edadc5df5821d830a8510ecf629e9a220b323e5cd45b091508939f71ea332239 + languageName: node + linkType: hard + +"react-is@npm:17.0.2, react-is@npm:^17.0.1": + version: 17.0.2 + resolution: "react-is@npm:17.0.2" + checksum: 2bdb6b93fbb1820b024b496042cce405c57e2f85e777c9aabd55f9b26d145408f9f74f5934676ffdc46f3dcff656d78413a6e43968e7b3f92eea35b3052e9053 + languageName: node + linkType: hard + +"react-is@npm:^16.10.2, react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.6.0, react-is@npm:^16.6.3, react-is@npm:^16.7.0": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: 33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 + languageName: node + linkType: hard + +"react-is@npm:^16.8.6 || ^17.0.0 || ^18.0.0, react-is@npm:^18.0.0": + version: 18.2.0 + resolution: "react-is@npm:18.2.0" + checksum: 6eb5e4b28028c23e2bfcf73371e72cd4162e4ac7ab445ddae2afe24e347a37d6dc22fae6e1748632cd43c6d4f9b8f86dcf26bf9275e1874f436d129952528ae0 + languageName: node + linkType: hard + +"react-json-view@npm:^1.21.3": + version: 1.21.3 + resolution: "react-json-view@npm:1.21.3" + dependencies: + flux: ^4.0.1 + react-base16-styling: ^0.6.0 + react-lifecycles-compat: ^3.0.4 + react-textarea-autosize: ^8.3.2 + peerDependencies: + react: ^17.0.0 || ^16.3.0 || ^15.5.4 + react-dom: ^17.0.0 || ^16.3.0 || ^15.5.4 + checksum: f41b38e599f148cf922f60390e56bb821f17a091373b08310fd82ebc526428683011751aa023687041481a46b20aeb1c47f660979d43db77674486aec9dc1d3f + languageName: node + linkType: hard + +"react-lazyload@npm:^3.2.0": + version: 3.2.0 + resolution: "react-lazyload@npm:3.2.0" + peerDependencies: + react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 94ffa876aa582156f9cc1b068b43e64be2cd87996c5a93f575019f40e0beb43574ed7b2cdf252b751e089ff5ffb8203d974f51589ca154e0c7065fe6f4e3df4b + languageName: node + linkType: hard + +"react-lifecycles-compat@npm:^3.0.0, react-lifecycles-compat@npm:^3.0.4": + version: 3.0.4 + resolution: "react-lifecycles-compat@npm:3.0.4" + checksum: 1d0df3c85af79df720524780f00c064d53a9dd1899d785eddb7264b378026979acbddb58a4b7e06e7d0d12aa1494fd5754562ee55d32907b15601068dae82c27 + languageName: node + linkType: hard + +"react-merge-refs@npm:^1.0.0": + version: 1.1.0 + resolution: "react-merge-refs@npm:1.1.0" + checksum: afb937156d834a058e5021535a63fd8a35984365c38b613478c31186da920b3b469e38b0b161a2075fbf5db7118fb8e96bb8a52a563f271d8f8f166fe8ffe2a4 + languageName: node + linkType: hard + +"react-merge-refs@npm:^2.0.1": + version: 2.0.1 + resolution: "react-merge-refs@npm:2.0.1" + checksum: ba180fe70d4c4356563b2ede072eb28a06b39d1f14ae8df61c3c941b9b3b5b4b1cc3fc7182cb826d66856fc93390e52c982a0ea1c040329d388522d6c7926544 + languageName: node + linkType: hard + +"react-popper@npm:^2.3.0": + version: 2.3.0 + resolution: "react-popper@npm:2.3.0" + dependencies: + react-fast-compare: ^3.0.1 + warning: ^4.0.2 + peerDependencies: + "@popperjs/core": ^2.0.0 + react: ^16.8.0 || ^17 || ^18 + react-dom: ^16.8.0 || ^17 || ^18 + checksum: 23f93540537ca4c035425bb8d5e51b11131fbc921d7ac1d041d0ae557feac8c877f3a012d36b94df8787803f52ed81e6df9257ac9e58719875f7805518d6db3f + languageName: node + linkType: hard + +"react-redux@npm:^5.1.2": + version: 5.1.2 + resolution: "react-redux@npm:5.1.2" + dependencies: + "@babel/runtime": ^7.1.2 + hoist-non-react-statics: ^3.3.0 + invariant: ^2.2.4 + loose-envify: ^1.1.0 + prop-types: ^15.6.1 + react-is: ^16.6.0 + react-lifecycles-compat: ^3.0.0 + peerDependencies: + react: ^0.14.0 || ^15.0.0-0 || ^16.0.0-0 + redux: ^2.0.0 || ^3.0.0 || ^4.0.0-0 + checksum: 3b8675ff45081181ccac1213a356fab3bfbf84e13639cc483f2e2dd8bb20ff2ec50fd47dfb46ed02b6df0f5fc23acafd5f9951c484cd98126db292341571557f + languageName: node + linkType: hard + +"react-refresh@npm:^0.11.0": + version: 0.11.0 + resolution: "react-refresh@npm:0.11.0" + checksum: cbb5616c7ba670bbd2f37ddadcdfefa66e727ea188e89733ccb8184d3b874631104b0bc016d5676a7ade4d9c79100b99b46b6ed10cd117ab5d1ddcbf8653a9f2 + languageName: node + linkType: hard + +"react-resize-detector@npm:^7.1.2": + version: 7.1.2 + resolution: "react-resize-detector@npm:7.1.2" + dependencies: + lodash: ^4.17.21 + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 2285b0024bcc736c7d5e80279e819835a5e8bef0899778100d2434b1c4b10971e6ae253df073ca96a20cacc47b3c249bc675479e5fc4ec1f6652fcca7f48ec22 + languageName: node + linkType: hard + +"react-router-dom@npm:^5.3.3": + version: 5.3.4 + resolution: "react-router-dom@npm:5.3.4" + dependencies: + "@babel/runtime": ^7.12.13 + history: ^4.9.0 + loose-envify: ^1.3.1 + prop-types: ^15.6.2 + react-router: 5.3.4 + tiny-invariant: ^1.0.2 + tiny-warning: ^1.0.0 + peerDependencies: + react: ">=15" + checksum: f04f727e2ed2e9d1d3830af02cc61690ff67b1524c0d18690582bfba0f4d14142ccc88fb6da6befad644fddf086f5ae4c2eb7048c67da8a0b0929c19426421b0 + languageName: node + linkType: hard + +"react-router@npm:5.3.4, react-router@npm:^5.3.3": + version: 5.3.4 + resolution: "react-router@npm:5.3.4" + dependencies: + "@babel/runtime": ^7.12.13 + history: ^4.9.0 + hoist-non-react-statics: ^3.1.0 + loose-envify: ^1.3.1 + path-to-regexp: ^1.7.0 + prop-types: ^15.6.2 + react-is: ^16.6.0 + tiny-invariant: ^1.0.2 + tiny-warning: ^1.0.0 + peerDependencies: + react: ">=15" + checksum: e15c00dfef199249b4c6e6d98e5e76cc352ce66f3270f13df37cc069ddf7c05e43281e8c308fc407e4435d72924373baef1d2890e0f6b0b1eb423cf47315a053 + languageName: node + linkType: hard + +"react-select@npm:^5.3.2": + version: 5.7.0 + resolution: "react-select@npm:5.7.0" + dependencies: + "@babel/runtime": ^7.12.0 + "@emotion/cache": ^11.4.0 + "@emotion/react": ^11.8.1 + "@floating-ui/dom": ^1.0.1 + "@types/react-transition-group": ^4.4.0 + memoize-one: ^6.0.0 + prop-types: ^15.6.0 + react-transition-group: ^4.3.0 + use-isomorphic-layout-effect: ^1.1.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 5ab64144930245cabedd08a399deaa35a19281163b0d4637811ae1cffd3b9ba45090d640c4f3ab95864229d07509fbdee69e960d074ad22dbacb223d21876443 + languageName: node + linkType: hard + +"react-smooth@npm:^2.0.1": + version: 2.0.1 + resolution: "react-smooth@npm:2.0.1" + dependencies: + fast-equals: ^2.0.0 + react-transition-group: 2.9.0 + peerDependencies: + prop-types: ^15.6.0 + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 3227aecfb2c2783a53045e0b5931432fed3376c98893d1190c259ed4281182dc0b25f200f50b55733e01d9d6d1950641d1b5177d899277e3d4fe8264b6bddcc2 + languageName: node + linkType: hard + +"react-svg-map@npm:^2.2.0": + version: 2.2.0 + resolution: "react-svg-map@npm:2.2.0" + dependencies: + prop-types: ^15.7.2 + peerDependencies: + react: ^16.0.0 + react-dom: ^16.0.0 + checksum: 1c724530fac92f5646f4c1336e61044ee79115a49ccd6fc6ecfc4993a08cdcb690f71642a0f9bd9e796c0a1b1044970698014bedc88ad2e70e7de03fb818b8ce + languageName: node + linkType: hard + +"react-textarea-autosize@npm:^8.3.2": + version: 8.4.0 + resolution: "react-textarea-autosize@npm:8.4.0" + dependencies: + "@babel/runtime": ^7.10.2 + use-composed-ref: ^1.3.0 + use-latest: ^1.2.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: cef5cc06e353c50db19689f4c61260572fea6a48a4b3d7600a1e0b69f20b33b5899db15f272c402c649c999d76834ad11de63f0053c1edab5ae72b05661e22c8 + languageName: node + linkType: hard + +"react-tippy@npm:^1.4.0": + version: 1.4.0 + resolution: "react-tippy@npm:1.4.0" + dependencies: + popper.js: ^1.11.1 + checksum: ec604d14056b8d96c9f9f9beca287f4c50f55095c9b572059e7dba0ef703f0247bb9199b9cb174435bab49bac74daf34bb9b2956164bedb217e67ff95072c404 + languageName: node + linkType: hard + +"react-toastify@npm:^9.1.1": + version: 9.1.1 + resolution: "react-toastify@npm:9.1.1" + dependencies: + clsx: ^1.1.1 + peerDependencies: + react: ">=16" + react-dom: ">=16" + checksum: 76caec6c926b1fdebf08a3ab3e78f2cba6ebdfd91dfb7042c86a30bdf2d0372332298072a9e16494c9d50fd61d929f9a403da9305899be351ca29c6dc2160ff8 + languageName: node + linkType: hard + +"react-transition-group@npm:2.9.0": + version: 2.9.0 + resolution: "react-transition-group@npm:2.9.0" + dependencies: + dom-helpers: ^3.4.0 + loose-envify: ^1.4.0 + prop-types: ^15.6.2 + react-lifecycles-compat: ^3.0.4 + peerDependencies: + react: ">=15.0.0" + react-dom: ">=15.0.0" + checksum: df40608e9defb6873290b9f2165921f17139b8edbb2019e2de38f77477f9cbd8fdb739b20e1e04cb16a513137c80e85cf5f0fff96049a94b740d389313394476 + languageName: node + linkType: hard + +"react-transition-group@npm:^4.3.0": + version: 4.4.5 + resolution: "react-transition-group@npm:4.4.5" + dependencies: + "@babel/runtime": ^7.5.5 + dom-helpers: ^5.0.1 + loose-envify: ^1.4.0 + prop-types: ^15.6.2 + peerDependencies: + react: ">=16.6.0" + react-dom: ">=16.6.0" + checksum: 2ba754ba748faefa15f87c96dfa700d5525054a0141de8c75763aae6734af0740e77e11261a1e8f4ffc08fd9ab78510122e05c21c2d79066c38bb6861a886c82 + languageName: node + linkType: hard + +"react-virtualized@npm:^9.22.3": + version: 9.22.3 + resolution: "react-virtualized@npm:9.22.3" + dependencies: + "@babel/runtime": ^7.7.2 + clsx: ^1.0.4 + dom-helpers: ^5.1.3 + loose-envify: ^1.4.0 + prop-types: ^15.7.2 + react-lifecycles-compat: ^3.0.4 + peerDependencies: + react: ^15.3.0 || ^16.0.0-alpha + react-dom: ^15.3.0 || ^16.0.0-alpha + checksum: 2c35c802f84e0accf224732d5c9e56a39819bcef30c579ea84bccff4b5daebf0fc5f174f68d4f968886adc2e809fd647ec82c8a13f4babb416127dbb4e06b2c8 + languageName: node + linkType: hard + +"react@npm:^18.2.0": + version: 18.2.0 + resolution: "react@npm:18.2.0" + dependencies: + loose-envify: ^1.1.0 + checksum: b562d9b569b0cb315e44b48099f7712283d93df36b19a39a67c254c6686479d3980b7f013dc931f4a5a3ae7645eae6386b4aa5eea933baa54ecd0f9acb0902b8 + languageName: node + linkType: hard + +"read-cache@npm:^1.0.0": + version: 1.0.0 + resolution: "read-cache@npm:1.0.0" + dependencies: + pify: ^2.3.0 + checksum: 90cb2750213c7dd7c80cb420654344a311fdec12944e81eb912cd82f1bc92aea21885fa6ce442e3336d9fccd663b8a7a19c46d9698e6ca55620848ab932da814 + languageName: node + linkType: hard + +"read-cmd-shim@npm:^1.0.1, read-cmd-shim@npm:^1.0.5": + version: 1.0.5 + resolution: "read-cmd-shim@npm:1.0.5" + dependencies: + graceful-fs: ^4.1.2 + checksum: ce3ccaa069239a6dcbbf9ba62ac8b7f786fac0cd0bc1d42d944ebc3059d6d2f5dd3e25eee3c26f77bc7b46778e48ba517bcde3ce0d1176ea7720f1aa68896a49 + languageName: node + linkType: hard + +"read-installed@npm:~4.0.3": + version: 4.0.3 + resolution: "read-installed@npm:4.0.3" + dependencies: + debuglog: ^1.0.1 + graceful-fs: ^4.1.2 + read-package-json: ^2.0.0 + readdir-scoped-modules: ^1.0.0 + semver: 2 || 3 || 4 || 5 + slide: ~1.1.3 + util-extend: ^1.0.1 + dependenciesMeta: + graceful-fs: + optional: true + checksum: 3b90a1897c84ea7a59d9265f228c0543e6f66bb902b3beb65a159804e9e60cd4a520e24460325959c49f4e31c1f03d9d35aa275589d36721fc08cee162bcedcb + languageName: node + linkType: hard + +"read-package-json@npm:1 || 2, read-package-json@npm:^2.0.0, read-package-json@npm:^2.0.13, read-package-json@npm:^2.1.2": + version: 2.1.2 + resolution: "read-package-json@npm:2.1.2" + dependencies: + glob: ^7.1.1 + json-parse-even-better-errors: ^2.3.0 + normalize-package-data: ^2.0.0 + npm-normalize-package-bin: ^1.0.0 + checksum: 2ff44e00a2e71bd87209021e3dd2b21d94f72dad2f5230c9ec3afb66acaf6d8c3ee4b6d09aa5c1ec660207d5c8d0b92b4b932459038ab448e74f35dbd8f2aa6a + languageName: node + linkType: hard + +"read-package-tree@npm:^5.3.1": + version: 5.3.1 + resolution: "read-package-tree@npm:5.3.1" + dependencies: + read-package-json: ^2.0.0 + readdir-scoped-modules: ^1.0.0 + util-promisify: ^2.1.0 + checksum: d74a62fef914c408e893b7f8cf28ff81f2d2d175e2d3fba3b832ec1cf8b6d61d31bab15b55092d0acad0132175bdfe84297f1c07d7b90aa618f0ec165d496fdf + languageName: node + linkType: hard + +"read-pkg-up@npm:^1.0.1": + version: 1.0.1 + resolution: "read-pkg-up@npm:1.0.1" + dependencies: + find-up: ^1.0.0 + read-pkg: ^1.0.0 + checksum: 36c4fc8bd73edf77a4eeb497b6e43010819ea4aef64cbf8e393439fac303398751c5a299feab84e179a74507e3a1416e1ed033a888b1dac3463bf46d1765f7ac + languageName: node + linkType: hard + +"read-pkg-up@npm:^7.0.1": + version: 7.0.1 + resolution: "read-pkg-up@npm:7.0.1" + dependencies: + find-up: ^4.1.0 + read-pkg: ^5.2.0 + type-fest: ^0.8.1 + checksum: 82b3ac9fd7c6ca1bdc1d7253eb1091a98ff3d195ee0a45386582ce3e69f90266163c34121e6a0a02f1630073a6c0585f7880b3865efcae9c452fa667f02ca385 + languageName: node + linkType: hard + +"read-pkg@npm:^1.0.0": + version: 1.1.0 + resolution: "read-pkg@npm:1.1.0" + dependencies: + load-json-file: ^1.0.0 + normalize-package-data: ^2.3.2 + path-type: ^1.0.0 + checksum: 51fce9f7066787dc7688ea7014324cedeb9f38daa7dace4f1147d526f22354a07189ef728710bc97e27fcf5ed3a03b68ad8b60afb4251984640b6f09c180d572 + languageName: node + linkType: hard + +"read-pkg@npm:^5.2.0": + version: 5.2.0 + resolution: "read-pkg@npm:5.2.0" + dependencies: + "@types/normalize-package-data": ^2.4.0 + normalize-package-data: ^2.5.0 + parse-json: ^5.0.0 + type-fest: ^0.6.0 + checksum: b51a17d4b51418e777029e3a7694c9bd6c578a5ab99db544764a0b0f2c7c0f58f8a6bc101f86a6fceb8ba6d237d67c89acf6170f6b98695d0420ddc86cf109fb + languageName: node + linkType: hard + +"read@npm:1, read@npm:~1.0.1, read@npm:~1.0.7": + version: 1.0.7 + resolution: "read@npm:1.0.7" + dependencies: + mute-stream: ~0.0.4 + checksum: 443533f05d5bb11b36ef1c6d625aae4e2ced8967e93cf546f35aa77b4eb6bd157f4256619e446bae43467f8f6619c7bc5c76983348dffaf36afedf4224f46216 + languageName: node + linkType: hard + +"readable-stream@npm:1 || 2, readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.6, readable-stream@npm:^2.1.5, readable-stream@npm:^2.2.2, readable-stream@npm:^2.3.3, readable-stream@npm:^2.3.6, readable-stream@npm:~2.3.6": + version: 2.3.7 + resolution: "readable-stream@npm:2.3.7" + 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 + checksum: 1708755e6cf9daff6ff60fa5b4575636472289c5b95d38058a91f94732b8d024a940a0d4d955639195ce42c22cab16973ee8fea8deedd24b5fec3dd596465f86 + languageName: node + linkType: hard + +"readable-stream@npm:1.1, readable-stream@npm:~1.1.10": + version: 1.1.14 + resolution: "readable-stream@npm:1.1.14" + dependencies: + core-util-is: ~1.0.0 + inherits: ~2.0.1 + isarray: 0.0.1 + string_decoder: ~0.10.x + checksum: b7f41b16b305103d598e3c8964fa30d52d6e0b5d9fdad567588964521691c24b279c7a8bb71f11927c3613acf355bac72d8396885a43d50425b2caafd49bc83d + languageName: node + linkType: hard + +"readable-stream@npm:2 || 3, readable-stream@npm:^3.0.6, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": + version: 3.6.0 + resolution: "readable-stream@npm:3.6.0" + dependencies: + inherits: ^2.0.3 + string_decoder: ^1.1.1 + util-deprecate: ^1.0.1 + checksum: 937bedd29ac8a68331666291922bea892fa2be1a33269e582de9f844a2002f146cf831e39cd49fe6a378d3f0c27358f259ed0e20d20f0bdc6a3f8fc21fce42dc + languageName: node + linkType: hard + +"readdir-scoped-modules@npm:^1.0.0, readdir-scoped-modules@npm:^1.1.0": + version: 1.1.0 + resolution: "readdir-scoped-modules@npm:1.1.0" + dependencies: + debuglog: ^1.0.1 + dezalgo: ^1.0.0 + graceful-fs: ^4.1.2 + once: ^1.3.0 + checksum: 21a53741c488775cbf78b0b51f1b897e9c523b1bcf54567fc2c8ed09b12d9027741f45fcb720f388c0c3088021b54dc3f616c07af1531417678cc7962fc15e5c + languageName: node + linkType: hard + +"readdirp@npm:^2.2.1": + version: 2.2.1 + resolution: "readdirp@npm:2.2.1" + dependencies: + graceful-fs: ^4.1.11 + micromatch: ^3.1.10 + readable-stream: ^2.0.2 + checksum: 770d177372ff2212d382d425d55ca48301fcbf3231ab3827257bbcca7ff44fb51fe4af6acc2dda8512dc7f29da390e9fbea5b2b3fc724b86e85cc828395b7797 + languageName: node + linkType: hard + +"readdirp@npm:~3.6.0": + version: 3.6.0 + resolution: "readdirp@npm:3.6.0" + dependencies: + picomatch: ^2.2.1 + checksum: 6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b + languageName: node + linkType: hard + +"recharts-scale@npm:^0.4.4": + version: 0.4.5 + resolution: "recharts-scale@npm:0.4.5" + dependencies: + decimal.js-light: ^2.4.1 + checksum: 64ce1fc4ebe62001787bf4dc4cbb779452d33831619309c71c50277c58e8968ffe98941562d9d0d5ffdb02588ebd62f4fe6548fa826110fd458db9c3cc6dadc1 + languageName: node + linkType: hard + +"recharts@npm:^2.1.13": + version: 2.3.2 + resolution: "recharts@npm:2.3.2" + dependencies: + classnames: ^2.2.5 + eventemitter3: ^4.0.1 + lodash: ^4.17.19 + react-is: ^16.10.2 + react-resize-detector: ^7.1.2 + react-smooth: ^2.0.1 + recharts-scale: ^0.4.4 + reduce-css-calc: ^2.1.8 + victory-vendor: ^36.6.8 + peerDependencies: + prop-types: ^15.6.0 + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 88e86118388df94c07dc9b39d4e0a0c47defb1a6348fdbc0e2ad457f1329e5fbc4525fbd67bb94485a7ef0e8b23320e4e9066640b18241b3e0fad80b5eabfd28 + languageName: node + linkType: hard + +"rechoir@npm:^0.7.0": + version: 0.7.1 + resolution: "rechoir@npm:0.7.1" + dependencies: + resolve: ^1.9.0 + checksum: 22c565f89845f8b9a0574d8bbc157fe489612d2882d036b5520640d4395dc837a997225de535513a847c5fcc47b7e0530b8c84e0ca51fa17dff44a83f41b2568 + languageName: node + linkType: hard + +"redent@npm:^1.0.0": + version: 1.0.0 + resolution: "redent@npm:1.0.0" + dependencies: + indent-string: ^2.1.0 + strip-indent: ^1.0.1 + checksum: 9fa48d250d4e645acac9de57cb82dc29cd7f5f27257ec367461e3dd0c9f14c55f1c40fd3d9cf7f9a3ed337f209ad4e0370abfcf5cf75569ebd31c97a7949b8a2 + languageName: node + linkType: hard + +"reduce-css-calc@npm:^2.1.8": + version: 2.1.8 + resolution: "reduce-css-calc@npm:2.1.8" + dependencies: + css-unit-converter: ^1.1.1 + postcss-value-parser: ^3.3.0 + checksum: 9f311cdb38d4a72d5dc9275b9558199e63a6ceff2892a309691e4281fe418730995910a2179a7dd1566c11138c834c6e5958203c629c9f4357600a478470b17b + languageName: node + linkType: hard + +"redux-immutable@npm:^4.0.0": + version: 4.0.0 + resolution: "redux-immutable@npm:4.0.0" + peerDependencies: + immutable: ^3.8.1 || ^4.0.0-rc.1 + checksum: c706c9f72a1fbce92d54ab9117ab641b6d7ee69f2860ec6de827dbed5bed918d4677a0895e6564bb59011202bb5e639cf69f4e2d2d14086053b32e5c4e35f512 + languageName: node + linkType: hard + +"redux-thunk@npm:^2.3.0": + version: 2.4.2 + resolution: "redux-thunk@npm:2.4.2" + peerDependencies: + redux: ^4 + checksum: e202d6ef7dfa7df08ed24cb221aa89d6c84dbaa7d65fe90dbd8e826d0c10d801f48388f9a7598a4fd970ecbc93d335014570a61ca7bc8bf569eab5de77b31a3c + languageName: node + linkType: hard + +"redux@npm:^4.0.0, redux@npm:^4.0.5, redux@npm:^4.1.2, redux@npm:^4.2.0": + version: 4.2.0 + resolution: "redux@npm:4.2.0" + dependencies: + "@babel/runtime": ^7.9.2 + checksum: 6b8b543499c9b8aa6afa01ef68950f4b2ea68d803381ac65797b1a5a7e39ba88ee3650c2a5a1dd500c78ad022de45cd5ed4a5f41fe7d51db8b07d12fbe84d146 + languageName: node + linkType: hard + +"regenerate-unicode-properties@npm:^10.1.0": + version: 10.1.0 + resolution: "regenerate-unicode-properties@npm:10.1.0" + dependencies: + regenerate: ^1.4.2 + checksum: 17818ea6f67c5a4884b9e18842edc4b3838a12f62e24f843e80fbb6d8cb649274b5b86d98bb02075074e02021850e597a92ff6b58bbe5caba4bf5fd8e4e38b56 + languageName: node + linkType: hard + +"regenerate@npm:^1.4.2": + version: 1.4.2 + resolution: "regenerate@npm:1.4.2" + checksum: f73c9eba5d398c818edc71d1c6979eaa05af7a808682749dd079f8df2a6d91a9b913db216c2c9b03e0a8ba2bba8701244a93f45211afbff691c32c7b275db1b8 + languageName: node + linkType: hard + +"regenerator-runtime@npm:^0.11.0": + version: 0.11.1 + resolution: "regenerator-runtime@npm:0.11.1" + checksum: 69cfa839efcf2d627fe358bf302ab8b24e5f182cb69f13e66f0612d3640d7838aad1e55662135e3ef2c1cc4322315b757626094fab13a48f9a64ab4bdeb8795b + languageName: node + linkType: hard + +"regenerator-runtime@npm:^0.13.11, regenerator-runtime@npm:^0.13.2, regenerator-runtime@npm:^0.13.7": + version: 0.13.11 + resolution: "regenerator-runtime@npm:0.13.11" + checksum: 12b069dc774001fbb0014f6a28f11c09ebfe3c0d984d88c9bced77fdb6fedbacbca434d24da9ae9371bfbf23f754869307fb51a4c98a8b8b18e5ef748677ca24 + languageName: node + linkType: hard + +"regenerator-transform@npm:^0.15.1": + version: 0.15.1 + resolution: "regenerator-transform@npm:0.15.1" + dependencies: + "@babel/runtime": ^7.8.4 + checksum: 6588e0c454e92ed6c2b3ed7ab24f61270aef47ae7052eceb5367cc15658948a2e84fdd6849f7c96e561d1f8a7474dc4c292166792e07498fdde226299b9ff374 + languageName: node + linkType: hard + +"regex-not@npm:^1.0.0, regex-not@npm:^1.0.2": + version: 1.0.2 + resolution: "regex-not@npm:1.0.2" + dependencies: + extend-shallow: ^3.0.2 + safe-regex: ^1.1.0 + checksum: a0f8d6045f63b22e9759db10e248369c443b41cedd7dba0922d002b66c2734bc2aef0d98c4d45772d1f756245f4c5203856b88b9624bba2a58708858a8d485d6 + languageName: node + linkType: hard + +"regexp.prototype.flags@npm:^1.4.3": + version: 1.4.3 + resolution: "regexp.prototype.flags@npm:1.4.3" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.3 + functions-have-names: ^1.2.2 + checksum: 5d797c7fb95f72a52dd9685a485faf0af3c55a4d1f2fafc1153a7be3df036cc3274b195b3ae051ee3d896a01960b446d726206e0d9a90b749e90d93445bb781f + languageName: node + linkType: hard + +"regexpp@npm:^3.2.0": + version: 3.2.0 + resolution: "regexpp@npm:3.2.0" + checksum: d1da82385c8754a1681416b90b9cca0e21b4a2babef159099b88f640637d789c69011d0bc94705dacab85b81133e929d027d85210e8b8b03f8035164dbc14710 + languageName: node + linkType: hard + +"regexpu-core@npm:^5.2.1": + version: 5.2.2 + resolution: "regexpu-core@npm:5.2.2" + dependencies: + regenerate: ^1.4.2 + regenerate-unicode-properties: ^10.1.0 + regjsgen: ^0.7.1 + regjsparser: ^0.9.1 + unicode-match-property-ecmascript: ^2.0.0 + unicode-match-property-value-ecmascript: ^2.1.0 + checksum: 1d025e2144ee7207db424125a81f5989bd337f56cddc23c0c83c1051679eee33d8c65c0e1e23fa494c2d8c9f0b19c47df0315a924445ad40e733c8aad4286f83 + languageName: node + linkType: hard + +"registry-auth-token@npm:^3.0.1": + version: 3.4.0 + resolution: "registry-auth-token@npm:3.4.0" + dependencies: + rc: ^1.1.6 + safe-buffer: ^5.0.1 + checksum: 44c0cbf380bcf0af02996b7d0215e2f789c97d2c762bb420fd844b34588bf77ab0cf94fbe33eeaf945456ff022dbfebec4309cf5ef110a653aa3696134efd081 + languageName: node + linkType: hard + +"registry-url@npm:^3.0.3": + version: 3.1.0 + resolution: "registry-url@npm:3.1.0" + dependencies: + rc: ^1.0.1 + checksum: 345cf9638f99d95863d92800b3f595ac312c19d6865595e499fbeb33fcda04021a0dbdafbb5e61a838a89a558bc239d78752a1f90eb68cf53fdf0d91da816a7c + languageName: node + linkType: hard + +"regjsgen@npm:^0.7.1": + version: 0.7.1 + resolution: "regjsgen@npm:0.7.1" + checksum: 5e49462fb782d43f6dd25bb39f92dbc93980392e66def07fa181638180a2a68752b568e1d323791a4ccbfd737b39ba794c37a224326e0eb7fe5b09cafd2b0c07 + languageName: node + linkType: hard + +"regjsparser@npm:^0.9.1": + version: 0.9.1 + resolution: "regjsparser@npm:0.9.1" + dependencies: + jsesc: ~0.5.0 + bin: + regjsparser: bin/parser + checksum: fe44fcf19a99fe4f92809b0b6179530e5ef313ff7f87df143b08ce9a2eb3c4b6189b43735d645be6e8f4033bfb015ed1ca54f0583bc7561bed53fd379feb8225 + languageName: node + linkType: hard + +"relateurl@npm:^0.2.7": + version: 0.2.7 + resolution: "relateurl@npm:0.2.7" + checksum: c248b4e3b32474f116a804b537fa6343d731b80056fb506dffd91e737eef4cac6be47a65aae39b522b0db9d0b1011d1a12e288d82a109ecd94a5299d82f6573a + languageName: node + linkType: hard + +"remark-external-links@npm:^8.0.0": + version: 8.0.0 + resolution: "remark-external-links@npm:8.0.0" + 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 + checksum: 5f0affc97e18ad3247e3b29449f4df98be5a75950cf0f0f13dd1755c4ef1065f9ab44626bba34d913d32bb92afd6f06a8e2f8068e83b48337f0b7a5d1f0cecfe + languageName: node + linkType: hard + +"remark-footnotes@npm:2.0.0": + version: 2.0.0 + resolution: "remark-footnotes@npm:2.0.0" + checksum: 45b55b3440b74bfeed11fba5ed6b31f2fd35ab4e9ba169061b76a19f5ff4d16d851c9f3c423c7fa54eb0fa5e6043b89098cb9478e9b5b417cf4bdef5571b0236 + languageName: node + linkType: hard + +"remark-mdx@npm:1.6.22": + version: 1.6.22 + resolution: "remark-mdx@npm:1.6.22" + dependencies: + "@babel/core": 7.12.9 + "@babel/helper-plugin-utils": 7.10.4 + "@babel/plugin-proposal-object-rest-spread": 7.12.1 + "@babel/plugin-syntax-jsx": 7.12.1 + "@mdx-js/util": 1.6.22 + is-alphabetical: 1.0.4 + remark-parse: 8.0.3 + unified: 9.2.0 + checksum: 3a964048e58cba7848d59fc920baa330a9b7f619fedb44d4d7985d84875eba8d92e0d0dd0617e28326c6086e21ef441664748526a2517a42555d44c648453b0a + languageName: node + linkType: hard + +"remark-parse@npm:8.0.3": + version: 8.0.3 + resolution: "remark-parse@npm:8.0.3" + dependencies: + ccount: ^1.0.0 + collapse-white-space: ^1.0.2 + is-alphabetical: ^1.0.0 + is-decimal: ^1.0.0 + is-whitespace-character: ^1.0.0 + is-word-character: ^1.0.0 + markdown-escapes: ^1.0.0 + parse-entities: ^2.0.0 + repeat-string: ^1.5.4 + state-toggle: ^1.0.0 + trim: 0.0.1 + trim-trailing-lines: ^1.0.0 + unherit: ^1.0.4 + unist-util-remove-position: ^2.0.0 + vfile-location: ^3.0.0 + xtend: ^4.0.1 + checksum: cbb859e2585864942823ce4d23a1b1514168a066ba91d47ca09ff45a5563b81bf17160c182ac7efed718712291c35a117db89b6ce603d04a845497ae7041c185 + languageName: node + linkType: hard + +"remark-slug@npm:^6.0.0": + version: 6.1.0 + resolution: "remark-slug@npm:6.1.0" + dependencies: + github-slugger: ^1.0.0 + mdast-util-to-string: ^1.0.0 + unist-util-visit: ^2.0.0 + checksum: 7cc2857936fce9c9c00b9c7d70de46d594cedf93bd8560fd006164dee7aacccdf472654ee35b33f4fb4bd0af882d89998c6d0c9088c2e95702a9fc15ebae002a + languageName: node + linkType: hard + +"remark-squeeze-paragraphs@npm:4.0.0": + version: 4.0.0 + resolution: "remark-squeeze-paragraphs@npm:4.0.0" + dependencies: + mdast-squeeze-paragraphs: ^4.0.0 + checksum: 61b39acfde3bebb1e9364a6991957f83ab0d878c0fd1de0e86e9bf9e060574cefb7a76057d64e7422e2a2bcf6e3c54635a4ae43f00b3dda38812ae4b6f4342f4 + languageName: node + linkType: hard + +"remove-trailing-separator@npm:^1.0.1": + version: 1.1.0 + resolution: "remove-trailing-separator@npm:1.1.0" + checksum: 3568f9f8f5af3737b4aee9e6e1e8ec4be65a92da9cb27f989e0893714d50aa95ed2ff02d40d1fa35e1b1a234dc9c2437050ef356704a3999feaca6667d9e9bfc + languageName: node + linkType: hard + +"renderkid@npm:^2.0.4": + version: 2.0.7 + resolution: "renderkid@npm:2.0.7" + dependencies: + css-select: ^4.1.3 + dom-converter: ^0.2.0 + htmlparser2: ^6.1.0 + lodash: ^4.17.21 + strip-ansi: ^3.0.1 + checksum: 05e19c8861e0f9f3d379a175fbb52e3be3c957022acf52d19d36b23f99bb401b6bc3c493d43213f4d76efb08cb2f13e66df38c9a487249cb8dad1f6170da6a14 + languageName: node + linkType: hard + +"renderkid@npm:^3.0.0": + version: 3.0.0 + resolution: "renderkid@npm:3.0.0" + dependencies: + css-select: ^4.1.3 + dom-converter: ^0.2.0 + htmlparser2: ^6.1.0 + lodash: ^4.17.21 + strip-ansi: ^6.0.1 + checksum: 24a9fae4cc50e731d059742d1b3eec163dc9e3872b12010d120c3fcbd622765d9cda41f79a1bbb4bf63c1d3442f18a08f6e1642cb5d7ebf092a0ce3f7a3bd143 + languageName: node + linkType: hard + +"repeat-element@npm:^1.1.2": + version: 1.1.4 + resolution: "repeat-element@npm:1.1.4" + checksum: 81aa8d82bc845780803ef52df3533fa399974b99df571d0bb86e91f0ffca9ee4b9c4e8e5e72af087938cc28d2aef93d106a6d01da685d72ce96455b90a9f9f69 + languageName: node + linkType: hard + +"repeat-string@npm:^1.5.4, repeat-string@npm:^1.6.1": + version: 1.6.1 + resolution: "repeat-string@npm:1.6.1" + checksum: 87fa21bfdb2fbdedc44b9a5b118b7c1239bdd2c2c1e42742ef9119b7d412a5137a1d23f1a83dc6bb686f4f27429ac6f542e3d923090b44181bafa41e8ac0174d + languageName: node + linkType: hard + +"repeating@npm:^2.0.0": + version: 2.0.1 + resolution: "repeating@npm:2.0.1" + dependencies: + is-finite: ^1.0.0 + checksum: 7f5cd293ec47d9c074ef0852800d5ff5c49028ce65242a7528d84f32bd2fe200b142930562af58c96d869c5a3046e87253030058e45231acaa129c1a7087d2e7 + languageName: node + linkType: hard + +"request-progress@npm:^3.0.0": + version: 3.0.0 + resolution: "request-progress@npm:3.0.0" + dependencies: + throttleit: ^1.0.0 + checksum: d5dcb7155a738572c8781436f6b418e866066a30eea0f99a9ab26b6f0ed6c13637462bba736357de3899b8d30431ee9202ac956a5f8ccdd0d9d1ed0962000d14 + languageName: node + linkType: hard + +"request@npm:^2.88.0, request@npm:^2.88.2": + version: 2.88.2 + resolution: "request@npm:2.88.2" + dependencies: + aws-sign2: ~0.7.0 + aws4: ^1.8.0 + caseless: ~0.12.0 + combined-stream: ~1.0.6 + extend: ~3.0.2 + forever-agent: ~0.6.1 + form-data: ~2.3.2 + har-validator: ~5.1.3 + http-signature: ~1.2.0 + is-typedarray: ~1.0.0 + isstream: ~0.1.2 + json-stringify-safe: ~5.0.1 + mime-types: ~2.1.19 + oauth-sign: ~0.9.0 + performance-now: ^2.1.0 + qs: ~6.5.2 + safe-buffer: ^5.1.2 + tough-cookie: ~2.5.0 + tunnel-agent: ^0.6.0 + uuid: ^3.3.2 + checksum: 0ec66e7af1391e51ad231de3b1c6c6aef3ebd0a238aa50d4191c7a792dcdb14920eea8d570c702dc5682f276fe569d176f9b8ebc6031a3cf4a630a691a431a63 + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 + languageName: node + linkType: hard + +"require-from-string@npm:^2.0.2": + version: 2.0.2 + resolution: "require-from-string@npm:2.0.2" + checksum: aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2 + languageName: node + linkType: hard + +"require-main-filename@npm:^2.0.0": + version: 2.0.0 + resolution: "require-main-filename@npm:2.0.0" + checksum: db91467d9ead311b4111cbd73a4e67fa7820daed2989a32f7023785a2659008c6d119752d9c4ac011ae07e537eb86523adff99804c5fdb39cd3a017f9b401bb6 + languageName: node + linkType: hard + +"requires-port@npm:^1.0.0": + version: 1.0.0 + resolution: "requires-port@npm:1.0.0" + checksum: b2bfdd09db16c082c4326e573a82c0771daaf7b53b9ce8ad60ea46aa6e30aaf475fe9b164800b89f93b748d2c234d8abff945d2551ba47bf5698e04cd7713267 + languageName: node + linkType: hard + +"resolve-cwd@npm:^3.0.0": + version: 3.0.0 + resolution: "resolve-cwd@npm:3.0.0" + dependencies: + resolve-from: ^5.0.0 + checksum: e608a3ebd15356264653c32d7ecbc8fd702f94c6703ea4ac2fb81d9c359180cba0ae2e6b71faa446631ed6145454d5a56b227efc33a2d40638ac13f8beb20ee4 + languageName: node + linkType: hard + +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 + languageName: node + linkType: hard + +"resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 + languageName: node + linkType: hard + +"resolve-pathname@npm:^3.0.0": + version: 3.0.0 + resolution: "resolve-pathname@npm:3.0.0" + checksum: c6ec49b670dc35b9a303c47fa83ba9348a71e92d64a4c4bb85e1b659a29b407aa1ac1cb14a9b5b502982132ca77482bd80534bca147439d66880d35a137fe723 + languageName: node + linkType: hard + +"resolve-url@npm:^0.2.1": + version: 0.2.1 + resolution: "resolve-url@npm:0.2.1" + checksum: c285182cfcddea13a12af92129ce0569be27fb0074ffaefbd3ba3da2eac2acecdfc996d435c4982a9fa2b4708640e52837c9153a5ab9255886a00b0b9e8d2a54 + languageName: node + linkType: hard + +"resolve.exports@npm:^1.1.0": + version: 1.1.1 + resolution: "resolve.exports@npm:1.1.1" + checksum: 902ac0c643d03385b2719f3aed8c289e9d4b2dd42c993de946de5b882bc18b74fad07d672d29f71a63c251be107f6d0d343e2390ca224c04ba9a8b8e35d1653a + languageName: node + linkType: hard + +"resolve@npm:^1.1.7, resolve@npm:^1.10.0, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.3.2, resolve@npm:^1.9.0": + version: 1.22.1 + resolution: "resolve@npm:1.22.1" + dependencies: + is-core-module: ^2.9.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: 6d58b1cb40f3fc80b9e45dd799d84cdc3829a993e4b9fa3b59d331e1dfacd0870e1851f4d0eb549d68c796e0b7087b43d1aec162653ccccff9e18191221a6e7d + languageName: node + linkType: hard + +"resolve@npm:^2.0.0-next.4": + version: 2.0.0-next.4 + resolution: "resolve@npm:2.0.0-next.4" + dependencies: + is-core-module: ^2.9.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: 1de92669e7c46cfe125294c66d5405e13288bb87b97e9bdab71693ceebbcc0255c789bde30e2834265257d330d8ff57414d7d88e3097d8f69951f3ce978bf045 + languageName: node + linkType: hard + +"resolve@patch:resolve@^1.1.7#~builtin<compat/resolve>, resolve@patch:resolve@^1.10.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.14.2#~builtin<compat/resolve>, resolve@patch:resolve@^1.19.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.20.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.1#~builtin<compat/resolve>, resolve@patch:resolve@^1.3.2#~builtin<compat/resolve>, resolve@patch:resolve@^1.9.0#~builtin<compat/resolve>": + version: 1.22.1 + resolution: "resolve@patch:resolve@npm%3A1.22.1#~builtin<compat/resolve>::version=1.22.1&hash=07638b" + dependencies: + is-core-module: ^2.9.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: 0d8ccceba5537769c42aa75e4aa75ae854aac866a11d7e9ffdb1663f0158ee646a0d48fc2818ed5e7fb364d64220a1fb9092a160e11e00cbdd5fbab39a13092c + languageName: node + linkType: hard + +"resolve@patch:resolve@^2.0.0-next.4#~builtin<compat/resolve>": + version: 2.0.0-next.4 + resolution: "resolve@patch:resolve@npm%3A2.0.0-next.4#~builtin<compat/resolve>::version=2.0.0-next.4&hash=07638b" + dependencies: + is-core-module: ^2.9.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: ed2bb51d616b9cd30fe85cf49f7a2240094d9fa01a221d361918462be81f683d1855b7f192391d2ab5325245b42464ca59690db5bd5dad0a326fc0de5974dd10 + languageName: node + linkType: hard + +"restore-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "restore-cursor@npm:3.1.0" + dependencies: + onetime: ^5.1.0 + signal-exit: ^3.0.2 + checksum: 8051a371d6aa67ff21625fa94e2357bd81ffdc96267f3fb0fc4aaf4534028343836548ef34c240ffa8c25b280ca35eb36be00b3cb2133fa4f51896d7e73c6b4f + languageName: node + linkType: hard + +"ret@npm:~0.1.10": + version: 0.1.15 + resolution: "ret@npm:0.1.15" + checksum: 01f77cad0f7ea4f955852c03d66982609893edc1240c0c964b4c9251d0f9fb6705150634060d169939b096d3b77f4c84d6b6098a5b5d340160898c8581f1f63f + languageName: node + linkType: hard + +"retry@npm:^0.10.0": + version: 0.10.1 + resolution: "retry@npm:0.10.1" + checksum: d5a7cbc7eca5589a4cf048355150c6746965ace4193080c46b34fe92059506ce39887f5d2bbc58d1d14ecf3b53c5c86d01bd82d158eac9b58aa2f075c2ae7b21 + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"retry@npm:^0.13.1": + version: 0.13.1 + resolution: "retry@npm:0.13.1" + checksum: 9ae822ee19db2163497e074ea919780b1efa00431d197c7afdb950e42bf109196774b92a49fc9821f0b8b328a98eea6017410bfc5e8a0fc19c85c6d11adb3772 + languageName: node + linkType: hard + +"reusify@npm:^1.0.4": + version: 1.0.4 + resolution: "reusify@npm:1.0.4" + checksum: c19ef26e4e188f408922c46f7ff480d38e8dfc55d448310dfb518736b23ed2c4f547fb64a6ed5bdba92cd7e7ddc889d36ff78f794816d5e71498d645ef476107 + languageName: node + linkType: hard + +"rfdc@npm:^1.3.0": + version: 1.3.0 + resolution: "rfdc@npm:1.3.0" + checksum: a17fd7b81f42c7ae4cb932abd7b2f677b04cc462a03619fb46945ae1ccae17c3bc87c020ffdde1751cbfa8549860a2883486fdcabc9b9de3f3108af32b69a667 + languageName: node + linkType: hard + +"rgbcolor@npm:^1.0.1": + version: 1.0.1 + resolution: "rgbcolor@npm:1.0.1" + checksum: 13af06c523351bac2854b85a22d1dfafd9310efd898e9bd96c8706f9aa09a3ddc8392ab00ae03d12950782164a97677f21834ffd84ffebf76ae106add319f956 + languageName: node + linkType: hard + +"rimraf@npm:^2.5.2, rimraf@npm:^2.5.4, rimraf@npm:^2.6.2, rimraf@npm:^2.6.3, rimraf@npm:^2.7.1": + version: 2.7.1 + resolution: "rimraf@npm:2.7.1" + dependencies: + glob: ^7.1.3 + bin: + rimraf: ./bin.js + checksum: 4eef73d406c6940927479a3a9dee551e14a54faf54b31ef861250ac815172bade86cc6f7d64a4dc5e98b65e4b18a2e1c9ff3b68d296be0c748413f092bb0dd40 + languageName: node + linkType: hard + +"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": + version: 3.0.2 + resolution: "rimraf@npm:3.0.2" + dependencies: + glob: ^7.1.3 + bin: + rimraf: bin.js + checksum: 9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 + languageName: node + linkType: hard + +"ripemd160@npm:^2.0.0, ripemd160@npm:^2.0.1": + version: 2.0.2 + resolution: "ripemd160@npm:2.0.2" + dependencies: + hash-base: ^3.0.0 + inherits: ^2.0.1 + checksum: f6f0df78817e78287c766687aed4d5accbebc308a8e7e673fb085b9977473c1f139f0c5335d353f172a915bb288098430755d2ad3c4f30612f4dd0c901cd2c3a + languageName: node + linkType: hard + +"rollup-plugin-terser@npm:^7.0.0": + version: 7.0.2 + resolution: "rollup-plugin-terser@npm:7.0.2" + dependencies: + "@babel/code-frame": ^7.10.4 + jest-worker: ^26.2.1 + serialize-javascript: ^4.0.0 + terser: ^5.0.0 + peerDependencies: + rollup: ^2.0.0 + checksum: f79b851c6f7b06555d3a8ce7a4e32abd2b7cb8318e89fb8db73e662fa6e3af1a59920e881d111efc65a7437fd9582b61b1f4859b6fd839ba948616829d92432d + languageName: node + linkType: hard + +"rollup@npm:^2.43.1": + version: 2.79.1 + resolution: "rollup@npm:2.79.1" + dependencies: + fsevents: ~2.3.2 + dependenciesMeta: + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 421418687f5dcd7324f4387f203c6bfc7118b7ace789e30f5da022471c43e037a76f5fd93837052754eeeae798a4fb266ac05ccee1e594406d912a59af98dde9 + languageName: node + linkType: hard + +"rsvp@npm:^4.8.4": + version: 4.8.5 + resolution: "rsvp@npm:4.8.5" + checksum: 7978f01060a48204506a8ebe15cdbd468498f5ae538b1d7ee3e7630375ba7cb2f98df2f596c12d3f4d5d5c21badc1c6ca8009f5142baded8511609a28eabd19a + languageName: node + linkType: hard + +"rtcpeerconnection-shim@npm:^1.2.15": + version: 1.2.15 + resolution: "rtcpeerconnection-shim@npm:1.2.15" + dependencies: + sdp: ^2.6.0 + checksum: efa4d9ba66146e2e03b6fb34bd1ceb77bf14f2fb2ee8c75c2c8f6a2494ffacc37c214caf5d4a3dcc761ba0b5bfd20fa61fa0820447f64e24b1e959f725ff7410 + languageName: node + linkType: hard + +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: ^1.2.2 + checksum: 200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 + languageName: node + linkType: hard + +"run-queue@npm:^1.0.0, run-queue@npm:^1.0.3": + version: 1.0.3 + resolution: "run-queue@npm:1.0.3" + dependencies: + aproba: ^1.1.1 + checksum: 4e8964279d8f160f9ffaabe82eaad11a1d4c0db596a0f2b5257ae9d2b900c7e1ffcece3e5719199436f50718e1e7f45bb4bf7a82e331a4e734d67c2588a90cbb + languageName: node + linkType: hard + +"rxjs@npm:^7.5.1": + version: 7.8.0 + resolution: "rxjs@npm:7.8.0" + dependencies: + tslib: ^2.1.0 + checksum: c48833638ae5d339332f8b792e716c3c662950ba95ba04e9e97a8cfd4628d8f009129672793c6c067c872a4dab5757231d41d7256a2114a5fabbf30d8a5b9d67 + languageName: node + linkType: hard + +"safe-buffer@npm:5.1.1": + version: 5.1.1 + resolution: "safe-buffer@npm:5.1.1" + checksum: 1c233bd105deeba3c9a8911ed4ec24ba45adbb51fec02f7944a10a202c38e3df4ef2b524bdeb55f2e4f8c77c13b2959e2e2e6022e5d99acdd70633b5f7e138cf + languageName: node + linkType: hard + +"safe-buffer@npm:5.1.2, safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": + version: 5.1.2 + resolution: "safe-buffer@npm:5.1.2" + checksum: 780ba6b5d99cc9a40f7b951d47152297d0e260f0df01472a1b99d4889679a4b94a13d644f7dbc4f022572f09ae9005fa2fbb93bbbd83643316f365a3e9a45b21 + languageName: node + linkType: hard + +"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 + languageName: node + linkType: hard + +"safe-regex-test@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-regex-test@npm:1.0.0" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.1.3 + is-regex: ^1.1.4 + checksum: 14a81a7e683f97b2d6e9c8be61fddcf8ed7a02f4e64a825515f96bb1738eb007145359313741d2704d28b55b703a0f6300c749dde7c1dbc13952a2b85048ede2 + languageName: node + linkType: hard + +"safe-regex@npm:^1.1.0": + version: 1.1.0 + resolution: "safe-regex@npm:1.1.0" + dependencies: + ret: ~0.1.10 + checksum: 547d58aa5184cbef368fd5ed5f28d20f911614748c5da6b35f53fd6626396707587251e6e3d1e3010fd3ff1212e413841b8825eaa5f317017ca62a30899af31a + languageName: node + linkType: hard + +"safe-stable-stringify@npm:^2.3.1": + version: 2.4.2 + resolution: "safe-stable-stringify@npm:2.4.2" + checksum: 9120fb0576693a3d04fc39fec594df446ad9975a5b8c0771d1adcad164c64810996880328c0092550764d094f33a58dc823fc85204f2f1ee40b040e24f106335 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.0.2, safer-buffer@npm:^2.1.0, safer-buffer@npm:~2.1.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"sane@npm:^4.0.3": + version: 4.1.0 + resolution: "sane@npm:4.1.0" + dependencies: + "@cnakazawa/watch": ^1.0.3 + anymatch: ^2.0.0 + capture-exit: ^2.0.0 + exec-sh: ^0.3.2 + execa: ^1.0.0 + fb-watchman: ^2.0.0 + micromatch: ^3.1.4 + minimist: ^1.1.1 + walker: ~1.0.5 + bin: + sane: ./src/cli.js + checksum: 7d0991ecaa10b02c6d0339a6f7e31db776971f3b659a351916dcc7ce3464671e72b54d80bcce118e39d4343e1e56c699fe35f6cb89fbd88b07095b72841cbfb0 + languageName: node + linkType: hard + +"sass-loader@npm:^13.0.0": + version: 13.2.0 + resolution: "sass-loader@npm:13.2.0" + dependencies: + klona: ^2.0.4 + neo-async: ^2.6.2 + peerDependencies: + fibers: ">= 3.1.0" + node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + sass: ^1.3.0 + sass-embedded: "*" + webpack: ^5.0.0 + peerDependenciesMeta: + fibers: + optional: true + node-sass: + optional: true + sass: + optional: true + sass-embedded: + optional: true + checksum: 5b62ee7704172f80516382eca3a333a4740da0648104e97a74cacd2bf1d8bd40e15005bdeb5d480cc713b5ab1b5dde8f132f4ce00fa866d6cd18fe9476f80e67 + languageName: node + linkType: hard + +"sass@npm:^1.51.0": + version: 1.57.1 + resolution: "sass@npm:1.57.1" + dependencies: + chokidar: ">=3.0.0 <4.0.0" + immutable: ^4.0.0 + source-map-js: ">=0.6.2 <2.0.0" + bin: + sass: sass.js + checksum: de8ab22b4b4b962df8629d0a6e3ff1397c37406a8c422b23a2c3c82cb25f18f3fd2dceff0cbb679e600af1b17e331e5517bd7b4fa02cf068556128f2a45b5569 + languageName: node + linkType: hard + +"sax@npm:1.2.1": + version: 1.2.1 + resolution: "sax@npm:1.2.1" + checksum: 1ae269cfde0b3774b4c92eb744452b6740bde5c5744fe5cadef6f496e42d9b632f483fb6aff9a23c0749c94c6951b06b0c5a90a5e99c879d3401cfd5ba61dc02 + languageName: node + linkType: hard + +"sax@npm:>=0.6.0": + version: 1.2.4 + resolution: "sax@npm:1.2.4" + checksum: 6e9b05ff443ee5e5096ce92d31c0740a20d33002fad714ebcb8fc7a664d9ee159103ebe8f7aef0a1f7c5ecacdd01f177f510dff95611c589399baf76437d3fe3 + languageName: node + linkType: hard + +"scheduler@npm:^0.23.0": + version: 0.23.0 + resolution: "scheduler@npm:0.23.0" + dependencies: + loose-envify: ^1.1.0 + checksum: b777f7ca0115e6d93e126ac490dbd82642d14983b3079f58f35519d992fa46260be7d6e6cede433a92db70306310c6f5f06e144f0e40c484199e09c1f7be53dd + languageName: node + linkType: hard + +"schema-utils@npm:2.7.0": + version: 2.7.0 + resolution: "schema-utils@npm:2.7.0" + dependencies: + "@types/json-schema": ^7.0.4 + ajv: ^6.12.2 + ajv-keywords: ^3.4.1 + checksum: 723c3c856a0313a89aa81c5fb2c93d4b11225f5cdd442665fddd55d3c285ae72e079f5286a3a9a1a973affe888f6c33554a2cf47b79b24cd8de2f1f756a6fb1b + languageName: node + linkType: hard + +"schema-utils@npm:^1.0.0": + version: 1.0.0 + resolution: "schema-utils@npm:1.0.0" + dependencies: + ajv: ^6.1.0 + ajv-errors: ^1.0.0 + ajv-keywords: ^3.1.0 + checksum: 670e22d7f0ff0b6f4514a4d6fb27c359101b44b7dbfd9563af201af72eb4a9ff06144020cab5f85b16e88821fd09b97cbdae6c893721c6528c8cb704124e6a2f + languageName: node + linkType: hard + +"schema-utils@npm:^2.6.5, schema-utils@npm:^2.7.0": + version: 2.7.1 + resolution: "schema-utils@npm:2.7.1" + dependencies: + "@types/json-schema": ^7.0.5 + ajv: ^6.12.4 + ajv-keywords: ^3.5.2 + checksum: f484f34464edd8758712d5d3ba25a306e367dac988aecaf4ce112e99baae73f33a807b5cf869240bb6648c80720b36af2d7d72be3a27faa49a2d4fc63fa3f85f + languageName: node + linkType: hard + +"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.0, schema-utils@npm:^3.1.1": + version: 3.1.1 + resolution: "schema-utils@npm:3.1.1" + dependencies: + "@types/json-schema": ^7.0.8 + ajv: ^6.12.5 + ajv-keywords: ^3.5.2 + checksum: 55a8da802a5f8f0ce6f68b6a139f3261cb423bd23795766da866a0f5738fc40303370fbe0c3eeba60b2a91c569ad7ce5318fea455f8fe866098c5a3a6b9050b0 + languageName: node + linkType: hard + +"schema-utils@npm:^4.0.0": + version: 4.0.0 + resolution: "schema-utils@npm:4.0.0" + dependencies: + "@types/json-schema": ^7.0.9 + ajv: ^8.8.0 + ajv-formats: ^2.1.1 + ajv-keywords: ^5.0.0 + checksum: d76f1b0724fb74fa9da19d4f98ebe89c2703d8d28df9dc44d66ab9a9cbca869b434181a36a2bc00ec53980f27e8fabe143759bdc8754692bbf7ef614fc6e9da4 + languageName: node + linkType: hard + +"sdp@npm:^2.12.0, sdp@npm:^2.6.0": + version: 2.12.0 + resolution: "sdp@npm:2.12.0" + checksum: 1a2ffdc20d79711175f89e87a6ce8db9b4757e694bed9760e5f919eed5925c9fb43ea63c5fd38f428a3edd45baae826318153fdc1db590a504eed7a809a23e32 + languageName: node + linkType: hard + +"select-hose@npm:^2.0.0": + version: 2.0.0 + resolution: "select-hose@npm:2.0.0" + checksum: 01cc52edd29feddaf379efb4328aededa633f0ac43c64b11a8abd075ff34f05b0d280882c4fbcbdf1a0658202c9cd2ea8d5985174dcf9a2dac7e3a4996fa9b67 + languageName: node + linkType: hard + +"selfsigned@npm:^2.1.1": + version: 2.1.1 + resolution: "selfsigned@npm:2.1.1" + dependencies: + node-forge: ^1 + checksum: 4a2509c8a5bd49c3630a799de66b317352b52746bec981133d4f8098365da35d2344f0fbedf14aacf2cd1e88682048e2df11ad9dc59331d3b1c0a5ec3e6e16ad + languageName: node + linkType: hard + +"semantic-ui-css@npm:^2.4.1": + version: 2.5.0 + resolution: "semantic-ui-css@npm:2.5.0" + dependencies: + jquery: x.* + checksum: e127afb9de9b40495aa412bcb973b70ea64f8146234415a3a355ff6fbfa0e034f63b6db78458755e64db7f439eb7de76309bf23ff743f550604e69fe2014b79e + languageName: node + linkType: hard + +"semantic-ui-react@npm:^2.1.2": + version: 2.1.4 + resolution: "semantic-ui-react@npm:2.1.4" + dependencies: + "@babel/runtime": ^7.10.5 + "@fluentui/react-component-event-listener": ~0.63.0 + "@fluentui/react-component-ref": ~0.63.0 + "@popperjs/core": ^2.6.0 + "@semantic-ui-react/event-stack": ^3.1.3 + clsx: ^1.1.1 + keyboard-key: ^1.1.0 + lodash: ^4.17.21 + lodash-es: ^4.17.21 + prop-types: ^15.7.2 + react-is: ^16.8.6 || ^17.0.0 || ^18.0.0 + react-popper: ^2.3.0 + shallowequal: ^1.1.0 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: e40c9dcbbd587e86d431fb6f2e7c33e9eb3cd43cbbbbc96e7f4073f2a487911bce4f89e1896ea9d30c80a2235ec0d1197dd73c20e681c6a6449ea4dc9235ae8f + languageName: node + linkType: hard + +"semver-diff@npm:^2.0.0": + version: 2.1.0 + resolution: "semver-diff@npm:2.1.0" + dependencies: + semver: ^5.0.3 + checksum: 5825827a82e848908c6b6f9248aad4a15fe5baeed74ae41d67cf6d96107425028a5a5432e17abf10f0696719b0efcbbc34b47d071a70fb85e11cb451feb637e6 + languageName: node + linkType: hard + +"semver@npm:2 || 3 || 4 || 5, semver@npm:2.x || 3.x || 4 || 5, semver@npm:^2.3.0 || 3.x || 4 || 5, semver@npm:^5.0.3, semver@npm:^5.1.0, semver@npm:^5.4.1, semver@npm:^5.5.0, semver@npm:^5.5.1, semver@npm:^5.6.0, semver@npm:^5.7.0, semver@npm:^5.7.1": + version: 5.7.1 + resolution: "semver@npm:5.7.1" + bin: + semver: ./bin/semver + checksum: d4884f2aeca28bff35d0bd40ff0a9b2dfc4b36a883bf0ea5dc15d10d9a01bdc9041035b05f825d4b5ac8a56e490703dbf0d986d054de82cc5e9bad3f02ca6e00 + languageName: node + linkType: hard + +"semver@npm:7.x, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8": + version: 7.3.8 + resolution: "semver@npm:7.3.8" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: 7e581d679530db31757301c2117721577a2bb36a301a443aac833b8efad372cda58e7f2a464fe4412ae1041cc1f63a6c1fe0ced8c57ce5aca1e0b57bb0d627b9 + languageName: node + linkType: hard + +"semver@npm:^6.0.0, semver@npm:^6.1.1, semver@npm:^6.1.2, semver@npm:^6.3.0": + version: 6.3.0 + resolution: "semver@npm:6.3.0" + bin: + semver: ./bin/semver.js + checksum: 1f4959e15bcfbaf727e964a4920f9260141bb8805b399793160da4e7de128e42a7d1f79c1b7d5cd21a6073fba0d55feb9966f5fef3e5ccb8e1d7ead3d7527458 + languageName: node + linkType: hard + +"send@npm:0.18.0": + version: 0.18.0 + resolution: "send@npm:0.18.0" + 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 + checksum: 0eb134d6a51fc13bbcb976a1f4214ea1e33f242fae046efc311e80aff66c7a43603e26a79d9d06670283a13000e51be6e0a2cb80ff0942eaf9f1cd30b7ae736a + languageName: node + linkType: hard + +"serialize-javascript@npm:^4.0.0": + version: 4.0.0 + resolution: "serialize-javascript@npm:4.0.0" + dependencies: + randombytes: ^2.1.0 + checksum: 510dfe7f0311c0b2f7ab06311afa1668ba2969ab2f1faaac0a4924ede76b7f22ba85cfdeaa0052ec5a047bca42c8cd8ac8df8f0efe52f9bd290b3a39ae69fe9d + languageName: node + linkType: hard + +"serialize-javascript@npm:^5.0.1": + version: 5.0.1 + resolution: "serialize-javascript@npm:5.0.1" + dependencies: + randombytes: ^2.1.0 + checksum: 646bd92a8298d764d38316f3006bce0b0def6d0e254791396ac34403847654d9346b0b6ed7865efd799d93d4c47d900e08a8fa7a6f7f8d2dbaebab5444c3b431 + languageName: node + linkType: hard + +"serialize-javascript@npm:^6.0.0": + version: 6.0.1 + resolution: "serialize-javascript@npm:6.0.1" + dependencies: + randombytes: ^2.1.0 + checksum: 1af427f4fee3fee051f54ffe15f77068cff78a3c96d20f5c1178d20630d3ab122d8350e639d5e13cde8111ef9db9439b871305ffb185e24be0a2149cec230988 + languageName: node + linkType: hard + +"serve-favicon@npm:^2.5.0": + version: 2.5.0 + resolution: "serve-favicon@npm:2.5.0" + dependencies: + etag: ~1.8.1 + fresh: 0.5.2 + ms: 2.1.1 + parseurl: ~1.3.2 + safe-buffer: 5.1.1 + checksum: 7244ced3c46f8dfde591dc801f1e21ebc8fa07c4870cbbaee3ce37104b3aad32858e674e251a8ed4837867ea0dd67cb734b485ae5a7b0895cb6022f8b8c79303 + languageName: node + linkType: hard + +"serve-index@npm:^1.9.1": + version: 1.9.1 + resolution: "serve-index@npm:1.9.1" + dependencies: + accepts: ~1.3.4 + batch: 0.6.1 + debug: 2.6.9 + escape-html: ~1.0.3 + http-errors: ~1.6.2 + mime-types: ~2.1.17 + parseurl: ~1.3.2 + checksum: a666471a24196f74371edf2c3c7bcdd82adbac52f600804508754b5296c3567588bf694258b19e0cb23a567acfa20d9721bfdaed3286007b81f9741ada8a3a9c + languageName: node + linkType: hard + +"serve-static@npm:1.15.0": + version: 1.15.0 + resolution: "serve-static@npm:1.15.0" + dependencies: + encodeurl: ~1.0.2 + escape-html: ~1.0.3 + parseurl: ~1.3.3 + send: 0.18.0 + checksum: fa9f0e21a540a28f301258dfe1e57bb4f81cd460d28f0e973860477dd4acef946a1f41748b5bd41c73b621bea2029569c935faa38578fd34cd42a9b4947088ba + languageName: node + linkType: hard + +"set-blocking@npm:^2.0.0, set-blocking@npm:~2.0.0": + version: 2.0.0 + resolution: "set-blocking@npm:2.0.0" + checksum: 9f8c1b2d800800d0b589de1477c753492de5c1548d4ade52f57f1d1f5e04af5481554d75ce5e5c43d4004b80a3eb714398d6907027dc0534177b7539119f4454 + languageName: node + linkType: hard + +"set-value@npm:^2.0.0, set-value@npm:^2.0.1": + version: 2.0.1 + resolution: "set-value@npm:2.0.1" + dependencies: + extend-shallow: ^2.0.1 + is-extendable: ^0.1.1 + is-plain-object: ^2.0.3 + split-string: ^3.0.1 + checksum: 4c40573c4f6540456e4b38b95f570272c4cfbe1d12890ad4057886da8535047cd772dfadf5b58e2e87aa244dfb4c57e3586f6716b976fc47c5144b6b09e1811b + languageName: node + linkType: hard + +"setimmediate@npm:^1.0.4, setimmediate@npm:^1.0.5": + version: 1.0.5 + resolution: "setimmediate@npm:1.0.5" + checksum: 5bae81bfdbfbd0ce992893286d49c9693c82b1bcc00dcaaf3a09c8f428fdeacf4190c013598b81875dfac2b08a572422db7df779a99332d0fce186d15a3e4d49 + languageName: node + linkType: hard + +"setprototypeof@npm:1.1.0": + version: 1.1.0 + resolution: "setprototypeof@npm:1.1.0" + checksum: a77b20876689c6a89c3b42f0c3596a9cae02f90fc902570cbd97198e9e8240382086c9303ad043e88cee10f61eae19f1004e51d885395a1e9bf49f9ebed12872 + languageName: node + linkType: hard + +"setprototypeof@npm:1.2.0": + version: 1.2.0 + resolution: "setprototypeof@npm:1.2.0" + checksum: 68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc + languageName: node + linkType: hard + +"sha.js@npm:^2.4.0, sha.js@npm:^2.4.8": + version: 2.4.11 + resolution: "sha.js@npm:2.4.11" + dependencies: + inherits: ^2.0.1 + safe-buffer: ^5.0.1 + bin: + sha.js: ./bin.js + checksum: b7a371bca8821c9cc98a0aeff67444a03d48d745cb103f17228b96793f455f0eb0a691941b89ea1e60f6359207e36081d9be193252b0f128e0daf9cfea2815a5 + languageName: node + linkType: hard + +"sha@npm:^3.0.0": + version: 3.0.0 + resolution: "sha@npm:3.0.0" + dependencies: + graceful-fs: ^4.1.2 + checksum: 17a35a4086c6b138dfc8683648900173e8f1d853557a6f95f815512414acc0ea62a3d93564e88afb4ba7f2740357cab41a5fa8e2ee993b8361149c4931f5b990 + languageName: node + linkType: hard + +"shallow-clone@npm:^3.0.0": + version: 3.0.1 + resolution: "shallow-clone@npm:3.0.1" + dependencies: + kind-of: ^6.0.2 + checksum: 7bab09613a1b9f480c85a9823aebec533015579fa055ba6634aa56ba1f984380670eaf33b8217502931872aa1401c9fcadaa15f9f604d631536df475b05bcf1e + languageName: node + linkType: hard + +"shallowequal@npm:^1.1.0": + version: 1.1.0 + resolution: "shallowequal@npm:1.1.0" + checksum: b926efb51cd0f47aa9bc061add788a4a650550bbe50647962113a4579b60af2abe7b62f9b02314acc6f97151d4cf87033a2b15fc20852fae306d1a095215396c + languageName: node + linkType: hard + +"shebang-command@npm:^1.2.0": + version: 1.2.0 + resolution: "shebang-command@npm:1.2.0" + dependencies: + shebang-regex: ^1.0.0 + checksum: 7b20dbf04112c456b7fc258622dafd566553184ac9b6938dd30b943b065b21dabd3776460df534cc02480db5e1b6aec44700d985153a3da46e7db7f9bd21326d + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: ^3.0.0 + checksum: a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^1.0.0": + version: 1.0.0 + resolution: "shebang-regex@npm:1.0.0" + checksum: 9abc45dee35f554ae9453098a13fdc2f1730e525a5eb33c51f096cc31f6f10a4b38074c1ebf354ae7bffa7229506083844008dfc3bb7818228568c0b2dc1fff2 + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"side-channel@npm:^1.0.4": + version: 1.0.4 + resolution: "side-channel@npm:1.0.4" + dependencies: + call-bind: ^1.0.0 + get-intrinsic: ^1.0.2 + object-inspect: ^1.9.0 + checksum: 054a5d23ee35054b2c4609b9fd2a0587760737782b5d765a9c7852264710cc39c6dcb56a9bbd6c12cd84071648aea3edb2359d2f6e560677eedadce511ac1da5 + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 + languageName: node + linkType: hard + +"simple-html-tokenizer@npm:^0.1.1": + version: 0.1.1 + resolution: "simple-html-tokenizer@npm:0.1.1" + checksum: 627fd911bd98874a77df7a87e1552cb7403be7c3aa629bcc99ac13e34b9d989c318c83ea42eff130aeb3a98cea27f80fd8ed5511518396cbc2570e1e2409d9fe + languageName: node + linkType: hard + +"simple-swizzle@npm:^0.2.2": + version: 0.2.2 + resolution: "simple-swizzle@npm:0.2.2" + dependencies: + is-arrayish: ^0.3.1 + checksum: df5e4662a8c750bdba69af4e8263c5d96fe4cd0f9fe4bdfa3cbdeb45d2e869dff640beaaeb1ef0e99db4d8d2ec92f85508c269f50c972174851bc1ae5bd64308 + languageName: node + linkType: hard + +"sirv@npm:^1.0.7": + version: 1.0.19 + resolution: "sirv@npm:1.0.19" + dependencies: + "@polka/url": ^1.0.0-next.20 + mrmime: ^1.0.0 + totalist: ^1.0.0 + checksum: 393cc0471e82d3e754a8c1b2b348a86249db1f686aeb11c17e4217326a8b1a96029d9f1b58362ebb3e511b7b98c47cd43c4305dde98322bb1259d07dec2d4908 + languageName: node + linkType: hard + +"sisteransi@npm:^1.0.5": + version: 1.0.5 + resolution: "sisteransi@npm:1.0.5" + checksum: 230ac975cca485b7f6fe2b96a711aa62a6a26ead3e6fb8ba17c5a00d61b8bed0d7adc21f5626b70d7c33c62ff4e63933017a6462942c719d1980bb0b1207ad46 + languageName: node + linkType: hard + +"slash@npm:^2.0.0": + version: 2.0.0 + resolution: "slash@npm:2.0.0" + checksum: f83dbd3cb62c41bb8fcbbc6bf5473f3234b97fa1d008f571710a9d3757a28c7169e1811cad1554ccb1cc531460b3d221c9a7b37f549398d9a30707f0a5af9193 + languageName: node + linkType: hard + +"slash@npm:^3.0.0": + version: 3.0.0 + resolution: "slash@npm:3.0.0" + checksum: e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b + languageName: node + linkType: hard + +"slash@npm:^4.0.0": + version: 4.0.0 + resolution: "slash@npm:4.0.0" + checksum: b522ca75d80d107fd30d29df0549a7b2537c83c4c4ecd12cd7d4ea6c8aaca2ab17ada002e7a1d78a9d736a0261509f26ea5b489082ee443a3a810586ef8eff18 + languageName: node + linkType: hard + +"slice-ansi@npm:^3.0.0": + version: 3.0.0 + resolution: "slice-ansi@npm:3.0.0" + dependencies: + ansi-styles: ^4.0.0 + astral-regex: ^2.0.0 + is-fullwidth-code-point: ^3.0.0 + checksum: 88083c9d0ca67d09f8b4c78f68833d69cabbb7236b74df5d741ad572bbf022deaf243fa54009cd434350622a1174ab267710fcc80a214ecc7689797fe00cb27c + languageName: node + linkType: hard + +"slice-ansi@npm:^4.0.0": + version: 4.0.0 + resolution: "slice-ansi@npm:4.0.0" + dependencies: + ansi-styles: ^4.0.0 + astral-regex: ^2.0.0 + is-fullwidth-code-point: ^3.0.0 + checksum: 6c25678db1270d4793e0327620f1e0f9f5bea4630123f51e9e399191bc52c87d6e6de53ed33538609e5eacbd1fab769fae00f3705d08d029f02102a540648918 + languageName: node + linkType: hard + +"slide@npm:^1.1.6, slide@npm:~1.1.3, slide@npm:~1.1.6": + version: 1.1.6 + resolution: "slide@npm:1.1.6" + checksum: f3bde70fd4c0a2ba6c23c674f010849865ddfacbc0ae3a57522d7ce88e4cc6c186d627943c34004d4f009a3fb477c03307b247ab69a266de4b3c72b271a6a03a + languageName: node + linkType: hard + +"smart-buffer@npm:^4.1.0, smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"snapdragon-node@npm:^2.0.1": + version: 2.1.1 + resolution: "snapdragon-node@npm:2.1.1" + dependencies: + define-property: ^1.0.0 + isobject: ^3.0.0 + snapdragon-util: ^3.0.1 + checksum: 7616e6a1ca054afe3ad8defda17ebe4c73b0800d2e0efd635c44ee1b286f8ac7900517314b5330862ce99b28cd2782348ee78bae573ff0f55832ad81d9657f3f + languageName: node + linkType: hard + +"snapdragon-util@npm:^3.0.1": + version: 3.0.1 + resolution: "snapdragon-util@npm:3.0.1" + dependencies: + kind-of: ^3.2.0 + checksum: 4441856d343399ba7f37f79681949d51b922e290fcc07e7bc94655a50f584befa4fb08f40c3471cd160e004660161964d8ff140cba49baa59aa6caba774240e3 + languageName: node + linkType: hard + +"snapdragon@npm:^0.8.1": + version: 0.8.2 + resolution: "snapdragon@npm:0.8.2" + dependencies: + base: ^0.11.1 + debug: ^2.2.0 + define-property: ^0.2.5 + extend-shallow: ^2.0.1 + map-cache: ^0.2.2 + source-map: ^0.5.6 + source-map-resolve: ^0.5.0 + use: ^3.1.0 + checksum: dfdac1f73d47152d72fc07f4322da09bbddfa31c1c9c3ae7346f252f778c45afa5b03e90813332f02f04f6de8003b34a168c456f8bb719024d092f932520ffca + languageName: node + linkType: hard + +"socket.io-client@npm:^4.4.1": + version: 4.5.4 + resolution: "socket.io-client@npm:4.5.4" + dependencies: + "@socket.io/component-emitter": ~3.1.0 + debug: ~4.3.2 + engine.io-client: ~6.2.3 + socket.io-parser: ~4.2.1 + checksum: 3ac19b94da6ee75c830c2c07c844bacc0b07c905625f8c8bba0403ebef185ecfc1af4fa1c1ee3bf0ae78e3428eb593301e0bd40850f938a336254a425a461971 + languageName: node + linkType: hard + +"socket.io-parser@npm:~4.2.1": + version: 4.2.2 + resolution: "socket.io-parser@npm:4.2.2" + dependencies: + "@socket.io/component-emitter": ~3.1.0 + debug: ~4.3.1 + checksum: 463d8aa73ee877adb2047e2e590fb5f3db562d99dbe6dcb32ea08fc756f0654345476e326287645b8a43bc1c137018f7525fc4d0291828749fb7445bcc56631b + languageName: node + linkType: hard + +"sockjs@npm:^0.3.24": + version: 0.3.24 + resolution: "sockjs@npm:0.3.24" + dependencies: + faye-websocket: ^0.11.3 + uuid: ^8.3.2 + websocket-driver: ^0.7.4 + checksum: aa102c7d921bf430215754511c81ea7248f2dcdf268fbdb18e4d8183493a86b8793b164c636c52f474a886f747447c962741df2373888823271efdb9d2594f33 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^4.0.0": + version: 4.0.2 + resolution: "socks-proxy-agent@npm:4.0.2" + dependencies: + agent-base: ~4.2.1 + socks: ~2.3.2 + checksum: f25ca0ef6da4f59261de6b8336c0d44f0e245dd769f51ab8b844f91f0c735a29842c927ff4c0352a030287fcac9329bccbd9ddd38031d6394dd64a503f0dc04a + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^7.0.0": + version: 7.0.0 + resolution: "socks-proxy-agent@npm:7.0.0" + dependencies: + agent-base: ^6.0.2 + debug: ^4.3.3 + socks: ^2.6.2 + checksum: b859f7eb8e96ec2c4186beea233ae59c02404094f3eb009946836af27d6e5c1627d1975a69b4d2e20611729ed543b6db3ae8481eb38603433c50d0345c987600 + languageName: node + linkType: hard + +"socks@npm:^2.6.2": + version: 2.7.1 + resolution: "socks@npm:2.7.1" + dependencies: + ip: ^2.0.0 + smart-buffer: ^4.2.0 + checksum: 43f69dbc9f34fc8220bc51c6eea1c39715ab3cfdb115d6e3285f6c7d1a603c5c75655668a5bbc11e3c7e2c99d60321fb8d7ab6f38cda6a215fadd0d6d0b52130 + languageName: node + linkType: hard + +"socks@npm:~2.3.2": + version: 2.3.3 + resolution: "socks@npm:2.3.3" + dependencies: + ip: 1.1.5 + smart-buffer: ^4.1.0 + checksum: c006c063f42724fdb25c1d6f4a04526b71023a0b15dd0386d4f33823a4d934ec30205da49f0381850398a3027a1b472f447ea16e840a0f2e2c90af2d30cd38b8 + languageName: node + linkType: hard + +"sorted-object@npm:~2.0.1": + version: 2.0.1 + resolution: "sorted-object@npm:2.0.1" + checksum: 9da0e8ae8a986ed324a787a54d9fe1921ab53b5e4cedf573d387cb0089b68d590b9bfc030ab23c87a33f0b63d88a7407050668415f482cb3a29512ea47c0c232 + languageName: node + linkType: hard + +"sorted-union-stream@npm:~2.1.3": + version: 2.1.3 + resolution: "sorted-union-stream@npm:2.1.3" + dependencies: + from2: ^1.3.0 + stream-iterate: ^1.1.0 + checksum: ef5aacf44857b9a28d2556b5ed7d034fdd2aa987905f9940ab362f12580241e7bbc3173e375a8cb8fd52ba8295038774efa632c809c6ed3ead060f17fb77e582 + languageName: node + linkType: hard + +"source-list-map@npm:^2.0.0": + version: 2.0.1 + resolution: "source-list-map@npm:2.0.1" + checksum: 2e5e421b185dcd857f46c3c70e2e711a65d717b78c5f795e2e248c9d67757882ea989b80ebc08cf164eeeda5f4be8aa95d3b990225070b2daaaf3257c5958149 + languageName: node + linkType: hard + +"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.2": + version: 1.0.2 + resolution: "source-map-js@npm:1.0.2" + checksum: 32f2dfd1e9b7168f9a9715eb1b4e21905850f3b50cf02cf476e47e4eebe8e6b762b63a64357896aa29b37e24922b4282df0f492e0d2ace572b43d15525976ff8 + languageName: node + linkType: hard + +"source-map-resolve@npm:^0.5.0": + version: 0.5.3 + resolution: "source-map-resolve@npm:0.5.3" + dependencies: + atob: ^2.1.2 + decode-uri-component: ^0.2.0 + resolve-url: ^0.2.1 + source-map-url: ^0.4.0 + urix: ^0.1.0 + checksum: 410acbe93882e058858d4c1297be61da3e1533f95f25b95903edddc1fb719654e705663644677542d1fb78a66390238fad1a57115fc958a0724cf9bb509caf57 + languageName: node + linkType: hard + +"source-map-support@npm:0.5.13": + version: 0.5.13 + resolution: "source-map-support@npm:0.5.13" + dependencies: + buffer-from: ^1.0.0 + source-map: ^0.6.0 + checksum: 137539f8c453fa0f496ea42049ab5da4569f96781f6ac8e5bfda26937be9494f4e8891f523c5f98f0e85f71b35d74127a00c46f83f6a4f54672b58d53202565e + languageName: node + linkType: hard + +"source-map-support@npm:^0.5.16, source-map-support@npm:~0.5.12, source-map-support@npm:~0.5.20": + version: 0.5.21 + resolution: "source-map-support@npm:0.5.21" + dependencies: + buffer-from: ^1.0.0 + source-map: ^0.6.0 + checksum: 9ee09942f415e0f721d6daad3917ec1516af746a8120bba7bb56278707a37f1eb8642bde456e98454b8a885023af81a16e646869975f06afc1a711fb90484e7d + languageName: node + linkType: hard + +"source-map-url@npm:^0.4.0": + version: 0.4.1 + resolution: "source-map-url@npm:0.4.1" + checksum: f8af0678500d536c7f643e32094d6718a4070ab4ca2d2326532512cfbe2d5d25a45849b4b385879326f2d7523bb3b686d0360dd347a3cda09fd89a5c28d4bc58 + languageName: node + linkType: hard + +"source-map@npm:^0.5.0, source-map@npm:^0.5.6, source-map@npm:^0.5.7": + version: 0.5.7 + resolution: "source-map@npm:0.5.7" + checksum: 904e767bb9c494929be013017380cbba013637da1b28e5943b566031e29df04fba57edf3f093e0914be094648b577372bd8ad247fa98cfba9c600794cd16b599 + languageName: node + linkType: hard + +"source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.0, source-map@npm:~0.6.1": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 + languageName: node + linkType: hard + +"source-map@npm:^0.7.3": + version: 0.7.4 + resolution: "source-map@npm:0.7.4" + checksum: dc0cf3768fe23c345ea8760487f8c97ef6fca8a73c83cd7c9bf2fde8bc2c34adb9c0824d6feb14bc4f9e37fb522e18af621543f1289038a66ac7586da29aa7dc + languageName: node + linkType: hard + +"source-map@npm:^0.8.0-beta.0": + version: 0.8.0-beta.0 + resolution: "source-map@npm:0.8.0-beta.0" + dependencies: + whatwg-url: ^7.0.0 + checksum: fb4d9bde9a9fdb2c29b10e5eae6c71d10e09ef467e1afb75fdec2eb7e11fa5b343a2af553f74f18b695dbc0b81f9da2e9fa3d7a317d5985e9939499ec6087835 + languageName: node + linkType: hard + +"sourcemap-codec@npm:^1.4.8": + version: 1.4.8 + resolution: "sourcemap-codec@npm:1.4.8" + checksum: f099279fdaae070ff156df7414bbe39aad69cdd615454947ed3e19136bfdfcb4544952685ee73f56e17038f4578091e12b17b283ed8ac013882916594d95b9e6 + languageName: node + linkType: hard + +"space-separated-tokens@npm:^1.0.0": + version: 1.1.5 + resolution: "space-separated-tokens@npm:1.1.5" + checksum: 3ee0a6905f89e1ffdfe474124b1ade9fe97276a377a0b01350bc079b6ec566eb5b219e26064cc5b7f3899c05bde51ffbc9154290b96eaf82916a1e2c2c13ead9 + languageName: node + linkType: hard + +"spdx-correct@npm:^3.0.0": + version: 3.1.1 + resolution: "spdx-correct@npm:3.1.1" + dependencies: + spdx-expression-parse: ^3.0.0 + spdx-license-ids: ^3.0.0 + checksum: 25909eecc4024963a8e398399dbdd59ddb925bd7dbecd9c9cf6df0d75c29b68cd30b82123564acc51810eb02cfc4b634a2e16e88aa982433306012e318849249 + languageName: node + linkType: hard + +"spdx-exceptions@npm:^2.1.0": + version: 2.3.0 + resolution: "spdx-exceptions@npm:2.3.0" + checksum: 83089e77d2a91cb6805a5c910a2bedb9e50799da091f532c2ba4150efdef6e53f121523d3e2dc2573a340dc0189e648b03157097f65465b3a0c06da1f18d7e8a + languageName: node + linkType: hard + +"spdx-expression-parse@npm:^3.0.0": + version: 3.0.1 + resolution: "spdx-expression-parse@npm:3.0.1" + dependencies: + spdx-exceptions: ^2.1.0 + spdx-license-ids: ^3.0.0 + checksum: 6f8a41c87759fa184a58713b86c6a8b028250f158159f1d03ed9d1b6ee4d9eefdc74181c8ddc581a341aa971c3e7b79e30b59c23b05d2436d5de1c30bdef7171 + languageName: node + linkType: hard + +"spdx-license-ids@npm:^3.0.0": + version: 3.0.12 + resolution: "spdx-license-ids@npm:3.0.12" + checksum: b749db2fdecf4ac1893b8e4c435c3bfe5247af9cb412a3cd8375c8bc5a24ad7f3c4263dfe0fc04701f98613f189787700f1deac3e9272c96dfaffc01826c2d0f + languageName: node + linkType: hard + +"spdy-transport@npm:^3.0.0": + version: 3.0.0 + resolution: "spdy-transport@npm:3.0.0" + dependencies: + debug: ^4.1.0 + detect-node: ^2.0.4 + hpack.js: ^2.1.6 + obuf: ^1.1.2 + readable-stream: ^3.0.6 + wbuf: ^1.7.3 + checksum: eaf7440fa90724fffc813c386d4a8a7427d967d6e46d7c51d8f8a533d1a6911b9823ea9218703debbae755337e85f110185d7a00ae22ec5c847077b908ce71bb + languageName: node + linkType: hard + +"spdy@npm:^4.0.2": + version: 4.0.2 + resolution: "spdy@npm:4.0.2" + dependencies: + debug: ^4.1.0 + handle-thing: ^2.0.0 + http-deceiver: ^1.2.7 + select-hose: ^2.0.0 + spdy-transport: ^3.0.0 + checksum: 983509c0be9d06fd00bb9dff713c5b5d35d3ffd720db869acdd5ad7aa6fc0e02c2318b58f75328957d8ff772acdf1f7d19382b6047df342044ff3e2d6805ccdf + languageName: node + linkType: hard + +"split-on-first@npm:^1.0.0": + version: 1.1.0 + resolution: "split-on-first@npm:1.1.0" + checksum: 56df8344f5a5de8521898a5c090023df1d8b8c75be6228f56c52491e0fc1617a5236f2ac3a066adb67a73231eac216ccea7b5b4a2423a543c277cb2f48d24c29 + languageName: node + linkType: hard + +"split-string@npm:^3.0.1, split-string@npm:^3.0.2": + version: 3.1.0 + resolution: "split-string@npm:3.1.0" + dependencies: + extend-shallow: ^3.0.0 + checksum: 72d7cd625445c7af215130e1e2bc183013bb9dd48a074eda1d35741e2b0dcb355e6df5b5558a62543a24dcec37dd1d6eb7a6228ff510d3c9de0f3dc1d1da8a70 + languageName: node + linkType: hard + +"sprintf-js@npm:~1.0.2": + version: 1.0.3 + resolution: "sprintf-js@npm:1.0.3" + checksum: ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb + languageName: node + linkType: hard + +"sshpk@npm:^1.14.1, sshpk@npm:^1.7.0": + version: 1.17.0 + resolution: "sshpk@npm:1.17.0" + dependencies: + asn1: ~0.2.3 + assert-plus: ^1.0.0 + bcrypt-pbkdf: ^1.0.0 + dashdash: ^1.12.0 + ecc-jsbn: ~0.1.1 + getpass: ^0.1.1 + jsbn: ~0.1.0 + safer-buffer: ^2.0.2 + tweetnacl: ~0.14.0 + bin: + sshpk-conv: bin/sshpk-conv + sshpk-sign: bin/sshpk-sign + sshpk-verify: bin/sshpk-verify + checksum: cf5e7f4c72e8a505ef41daac9f9ca26da365cfe26ae265a01ce98a8868991943857a8526c1cf98a42ef0dc4edf1dbe4e77aeea378cfeb58054beb78505e85402 + languageName: node + linkType: hard + +"ssim.js@npm:^3.1.1": + version: 3.5.0 + resolution: "ssim.js@npm:3.5.0" + checksum: 9e7101da17395d3acbd417aac712d8f156522e79059a27cb38882eedd5a8868e31871c8f58bec3a150f8cf0660883cf22310cbd2fd63b408c1fd0ab02fda9fbc + languageName: node + linkType: hard + +"ssri@npm:^6.0.0, ssri@npm:^6.0.1, ssri@npm:^6.0.2": + version: 6.0.2 + resolution: "ssri@npm:6.0.2" + dependencies: + figgy-pudding: ^3.5.1 + checksum: e6f18c57dc9fed69343db5c59f95ef334e9664bfbdbad686c190ef2c6ad6b35e9b56cb203f3e4eb7eee6cb7bb602daa26dab6685e3847f0b5c464cdf7d9c2cee + languageName: node + linkType: hard + +"ssri@npm:^8.0.1": + version: 8.0.1 + resolution: "ssri@npm:8.0.1" + dependencies: + minipass: ^3.1.1 + checksum: 5cfae216ae02dcd154d1bbed2d0a60038a4b3a2fcaac3c7e47401ff4e058e551ee74cfdba618871bf168cd583db7b8324f94af6747d4303b73cd4c3f6dc5c9c2 + languageName: node + linkType: hard + +"ssri@npm:^9.0.0": + version: 9.0.1 + resolution: "ssri@npm:9.0.1" + dependencies: + minipass: ^3.1.1 + checksum: c5d153ce03b5980d683ecaa4d805f6a03d8dc545736213803e168a1907650c46c08a4e5ce6d670a0205482b35c35713d9d286d9133bdd79853a406e22ad81f04 + languageName: node + linkType: hard + +"stable@npm:^0.1.8": + version: 0.1.8 + resolution: "stable@npm:0.1.8" + checksum: df74b5883075076e78f8e365e4068ecd977af6c09da510cfc3148a303d4b87bc9aa8f7c48feb67ed4ef970b6140bd9eabba2129e28024aa88df5ea0114cba39d + languageName: node + linkType: hard + +"stack-trace@npm:0.0.x": + version: 0.0.10 + resolution: "stack-trace@npm:0.0.10" + checksum: 9ff3dabfad4049b635a85456f927a075c9d0c210e3ea336412d18220b2a86cbb9b13ec46d6c37b70a302a4ea4d49e30e5d4944dd60ae784073f1cde778ac8f4b + languageName: node + linkType: hard + +"stack-utils@npm:^2.0.3": + version: 2.0.6 + resolution: "stack-utils@npm:2.0.6" + dependencies: + escape-string-regexp: ^2.0.0 + checksum: 651c9f87667e077584bbe848acaecc6049bc71979f1e9a46c7b920cad4431c388df0f51b8ad7cfd6eed3db97a2878d0fc8b3122979439ea8bac29c61c95eec8a + languageName: node + linkType: hard + +"stackblur-canvas@npm:^2.0.0": + version: 2.5.0 + resolution: "stackblur-canvas@npm:2.5.0" + checksum: 0f62f47526eff2a27618450e86c21cb78bf2a8a3c8d2addd674f6c187dfd468cab274bc344580268cce03f3893ef8b85be0c37dc77d88894ca0580e841b58274 + languageName: node + linkType: hard + +"stackframe@npm:^1.3.4": + version: 1.3.4 + resolution: "stackframe@npm:1.3.4" + checksum: 18410f7a1e0c5d211a4effa83bdbf24adbe8faa8c34db52e1cd3e89837518c592be60b60d8b7270ac53eeeb8b807cd11b399a41667f6c9abb41059c3ccc8a989 + languageName: node + linkType: hard + +"state-toggle@npm:^1.0.0": + version: 1.0.3 + resolution: "state-toggle@npm:1.0.3" + checksum: 6051ee5654b39b0006911ae3130fa7f47675e07db16a711d8cd23d43b63f383e98f3bd9fa80e118a3f5964a11284d8eee180baef27a556146e628f8da74aba12 + languageName: node + linkType: hard + +"static-extend@npm:^0.1.1": + version: 0.1.2 + resolution: "static-extend@npm:0.1.2" + dependencies: + define-property: ^0.2.5 + object-copy: ^0.1.0 + checksum: 284f5865a9e19d079f1badbcd70d5f9f82e7a08393f818a220839cd5f71729e89105e1c95322bd28e833161d484cee671380ca443869ae89578eef2bf55c0653 + languageName: node + linkType: hard + +"statuses@npm:2.0.1": + version: 2.0.1 + resolution: "statuses@npm:2.0.1" + checksum: 34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0 + languageName: node + linkType: hard + +"statuses@npm:>= 1.4.0 < 2": + version: 1.5.0 + resolution: "statuses@npm:1.5.0" + checksum: e433900956357b3efd79b1c547da4d291799ac836960c016d10a98f6a810b1b5c0dcc13b5a7aa609a58239b5190e1ea176ad9221c2157d2fd1c747393e6b2940 + languageName: node + linkType: hard + +"stop-iteration-iterator@npm:^1.0.0": + version: 1.0.0 + resolution: "stop-iteration-iterator@npm:1.0.0" + dependencies: + internal-slot: ^1.0.4 + checksum: c4158d6188aac510d9e92925b58709207bd94699e9c31186a040c80932a687f84a51356b5895e6dc72710aad83addb9411c22171832c9ae0e6e11b7d61b0dfb9 + languageName: node + linkType: hard + +"store2@npm:^2.12.0": + version: 2.14.2 + resolution: "store2@npm:2.14.2" + checksum: 2f27c3eaa7207b81410e170e7c41379816d22c1566308a9d97fbf853c4facff531fcb2a85f085c7503c578736570972f747c26018ebeaba7d1341fb82a7b6d52 + languageName: node + linkType: hard + +"stream-browserify@npm:^2.0.1": + version: 2.0.2 + resolution: "stream-browserify@npm:2.0.2" + dependencies: + inherits: ~2.0.1 + readable-stream: ^2.0.2 + checksum: 485562bd5d962d633ae178449029c6fa2611052e356bdb5668f768544aa4daa94c4f9a97de718f3f30ad98f3cb98a5f396252bb3855aff153c138f79c0e8f6ac + languageName: node + linkType: hard + +"stream-each@npm:^1.1.0": + version: 1.2.3 + resolution: "stream-each@npm:1.2.3" + dependencies: + end-of-stream: ^1.1.0 + stream-shift: ^1.0.0 + checksum: 7ed229d3b7c24373058b5742b00066da8d3122d1487c8219a025ed53a8978545c77654a529a8e9c62ba83ae80c424cbb0204776b49abf72270d2e8154831dd5f + languageName: node + linkType: hard + +"stream-http@npm:^2.7.2": + version: 2.8.3 + resolution: "stream-http@npm:2.8.3" + dependencies: + builtin-status-codes: ^3.0.0 + inherits: ^2.0.1 + readable-stream: ^2.3.6 + to-arraybuffer: ^1.0.0 + xtend: ^4.0.0 + checksum: fbe7d327a29216bbabe88d3819bb8f7a502f11eeacf3212579e5af1f76fa7283f6ffa66134ab7d80928070051f571d1029e85f65ce3369fffd4c4df3669446c4 + languageName: node + linkType: hard + +"stream-iterate@npm:^1.1.0": + version: 1.2.0 + resolution: "stream-iterate@npm:1.2.0" + dependencies: + readable-stream: ^2.1.5 + stream-shift: ^1.0.0 + checksum: 7f50d63658244f046758fdc2c2fce52d82fc0cc7760704c5ffc9ca8eb3932bd38ab6213aa3b94c63e65fdb6e9f0d6445daa3d9468f4da05c91077775277d7404 + languageName: node + linkType: hard + +"stream-shift@npm:^1.0.0": + version: 1.0.1 + resolution: "stream-shift@npm:1.0.1" + checksum: b63a0d178cde34b920ad93e2c0c9395b840f408d36803b07c61416edac80ef9e480a51910e0ceea0d679cec90921bcd2cccab020d3a9fa6c73a98b0fbec132fd + languageName: node + linkType: hard + +"strict-uri-encode@npm:^2.0.0": + version: 2.0.0 + resolution: "strict-uri-encode@npm:2.0.0" + checksum: 010cbc78da0e2cf833b0f5dc769e21ae74cdc5d5f5bd555f14a4a4876c8ad2c85ab8b5bdf9a722dc71a11dcd3184085e1c3c0bd50ec6bb85fffc0f28cf82597d + languageName: node + linkType: hard + +"string-length@npm:^4.0.1": + version: 4.0.2 + resolution: "string-length@npm:4.0.2" + dependencies: + char-regex: ^1.0.2 + strip-ansi: ^6.0.0 + checksum: 1cd77409c3d7db7bc59406f6bcc9ef0783671dcbabb23597a1177c166906ef2ee7c8290f78cae73a8aec858768f189d2cb417797df5e15ec4eb5e16b3346340c + languageName: node + linkType: hard + +"string-width@npm:^1.0.1": + version: 1.0.2 + resolution: "string-width@npm:1.0.2" + dependencies: + code-point-at: ^1.0.0 + is-fullwidth-code-point: ^1.0.0 + strip-ansi: ^3.0.0 + checksum: c558438baed23a9ab9370bb6a939acbdb2b2ffc517838d651aad0f5b2b674fb85d460d9b1d0b6a4c210dffd09e3235222d89a5bd4c0c1587f78b2bb7bc00c65e + languageName: node + linkType: hard + +"string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.2, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: ^8.0.0 + is-fullwidth-code-point: ^3.0.0 + strip-ansi: ^6.0.1 + checksum: 1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + languageName: node + linkType: hard + +"string-width@npm:^2.0.0, string-width@npm:^2.1.1": + version: 2.1.1 + resolution: "string-width@npm:2.1.1" + dependencies: + is-fullwidth-code-point: ^2.0.0 + strip-ansi: ^4.0.0 + checksum: e5f2b169fcf8a4257a399f95d069522f056e92ec97dbdcb9b0cdf14d688b7ca0b1b1439a1c7b9773cd79446cbafd582727279d6bfdd9f8edd306ea5e90e5b610 + languageName: node + linkType: hard + +"string-width@npm:^3.0.0, string-width@npm:^3.1.0": + version: 3.1.0 + resolution: "string-width@npm:3.1.0" + dependencies: + emoji-regex: ^7.0.1 + is-fullwidth-code-point: ^2.0.0 + strip-ansi: ^5.1.0 + checksum: 85fa0d4f106e7999bb68c1c640c76fa69fb8c069dab75b009e29c123914e2d3b532e6cfa4b9d1bd913176fc83dedd7a2d7bf40d21a81a8a1978432cedfb65b91 + languageName: node + linkType: hard + +"string.prototype.matchall@npm:^4.0.0 || ^3.0.1, string.prototype.matchall@npm:^4.0.6, string.prototype.matchall@npm:^4.0.8": + version: 4.0.8 + resolution: "string.prototype.matchall@npm:4.0.8" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + get-intrinsic: ^1.1.3 + has-symbols: ^1.0.3 + internal-slot: ^1.0.3 + regexp.prototype.flags: ^1.4.3 + side-channel: ^1.0.4 + checksum: 644523d05c1ee93bab7474e999a5734ee5f6ad2d7ad24ed6ea8706c270dc92b352bde0f2a5420bfbeed54e28cb6a770c3800e1988a5267a70fd5e677c7750abc + languageName: node + linkType: hard + +"string.prototype.padend@npm:^3.0.0": + version: 3.1.4 + resolution: "string.prototype.padend@npm:3.1.4" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: 11feb9ae51a32febc73bb3b38d29900405c736f47613badff773ab830f23faad796c43d51e18090cada975b82831f66bdcb6b5353739a019b7fcc321900205ad + languageName: node + linkType: hard + +"string.prototype.padstart@npm:^3.0.0": + version: 3.1.4 + resolution: "string.prototype.padstart@npm:3.1.4" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: 4f395af27917b4cc52db2f77fe2221401cf396fcac1a96bed5e7dc53d08e453f6cdc4959e1f3ed45e8a29fd583f98af9ac67f6fd8b931d34618ddbecba8ea20b + languageName: node + linkType: hard + +"string.prototype.trimend@npm:^1.0.6": + version: 1.0.6 + resolution: "string.prototype.trimend@npm:1.0.6" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: 51b663e3195a74b58620a250b3fc4efb58951000f6e7d572a9f671c038f2f37f24a2b8c6994500a882aeab2f1c383fac1e8c023c01eb0c8b4e52d2f13b6c4513 + languageName: node + linkType: hard + +"string.prototype.trimstart@npm:^1.0.6": + version: 1.0.6 + resolution: "string.prototype.trimstart@npm:1.0.6" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: 13b9970d4e234002dfc8069c655c1fe19e83e10ced208b54858c41bb0f7544e581ac0ce746e92b279563664ad63910039f7253f36942113fec413b2b4e7c1fcd + languageName: node + linkType: hard + +"string_decoder@npm:^1.0.0, string_decoder@npm:^1.1.1": + version: 1.3.0 + resolution: "string_decoder@npm:1.3.0" + dependencies: + safe-buffer: ~5.2.0 + checksum: 810614ddb030e271cd591935dcd5956b2410dd079d64ff92a1844d6b7588bf992b3e1b69b0f4d34a3e06e0bd73046ac646b5264c1987b20d0601f81ef35d731d + languageName: node + linkType: hard + +"string_decoder@npm:~0.10.x": + version: 0.10.31 + resolution: "string_decoder@npm:0.10.31" + checksum: 1c628d78f974aa7539c496029f48e7019acc32487fc695464f9d6bdfec98edd7d933a06b3216bc2016918f6e75074c611d84430a53cb0e43071597d6c1ac5e25 + languageName: node + linkType: hard + +"string_decoder@npm:~1.1.1": + version: 1.1.1 + resolution: "string_decoder@npm:1.1.1" + dependencies: + safe-buffer: ~5.1.0 + checksum: b4f89f3a92fd101b5653ca3c99550e07bdf9e13b35037e9e2a1c7b47cec4e55e06ff3fc468e314a0b5e80bfbaf65c1ca5a84978764884ae9413bec1fc6ca924e + languageName: node + linkType: hard + +"stringify-object@npm:^3.3.0": + version: 3.3.0 + resolution: "stringify-object@npm:3.3.0" + dependencies: + get-own-enumerable-property-symbols: ^3.0.0 + is-obj: ^1.0.1 + is-regexp: ^1.0.0 + checksum: ba8078f84128979ee24b3de9a083489cbd3c62cb8572a061b47d4d82601a8ae4b4d86fa8c54dd955593da56bb7c16a6de51c27221fdc6b7139bb4f29d815f35b + languageName: node + linkType: hard + +"stringify-package@npm:^1.0.0, stringify-package@npm:^1.0.1": + version: 1.0.1 + resolution: "stringify-package@npm:1.0.1" + checksum: d531d27f29f31680d0d80ae7d4f4515b2a5988550f40f5f73bfc812f89c5fd2ea91a2ceb2361d04b03cfd8b49ee5715191fab252eea3618eee0b6c6ad9d882c8 + languageName: node + linkType: hard + +"strip-ansi@npm:^3.0.0, strip-ansi@npm:^3.0.1": + version: 3.0.1 + resolution: "strip-ansi@npm:3.0.1" + dependencies: + ansi-regex: ^2.0.0 + checksum: f6e7fbe8e700105dccf7102eae20e4f03477537c74b286fd22cfc970f139002ed6f0d9c10d0e21aa9ed9245e0fa3c9275930e8795c5b947da136e4ecb644a70f + languageName: node + linkType: hard + +"strip-ansi@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-ansi@npm:4.0.0" + dependencies: + ansi-regex: ^3.0.0 + checksum: d75d9681e0637ea316ddbd7d4d3be010b1895a17e885155e0ed6a39755ae0fd7ef46e14b22162e66a62db122d3a98ab7917794e255532ab461bb0a04feb03e7d + languageName: node + linkType: hard + +"strip-ansi@npm:^5.0.0, strip-ansi@npm:^5.1.0, strip-ansi@npm:^5.2.0": + version: 5.2.0 + resolution: "strip-ansi@npm:5.2.0" + dependencies: + ansi-regex: ^4.1.0 + checksum: de4658c8a097ce3b15955bc6008f67c0790f85748bdc025b7bc8c52c7aee94bc4f9e50624516150ed173c3db72d851826cd57e7a85fe4e4bb6dbbebd5d297fdf + languageName: node + linkType: hard + +"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: ^5.0.1 + checksum: 1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-bom@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-bom@npm:2.0.0" + dependencies: + is-utf8: ^0.2.0 + checksum: 4fcbb248af1d5c1f2d710022b7d60245077e7942079bfb7ef3fc8c1ae78d61e96278525ba46719b15ab12fced5c7603777105bc898695339d7c97c64d300ed0b + languageName: node + linkType: hard + +"strip-bom@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-bom@npm:4.0.0" + checksum: 26abad1172d6bc48985ab9a5f96c21e440f6e7e476686de49be813b5a59b3566dccb5c525b831ec54fe348283b47f3ffb8e080bc3f965fde12e84df23f6bb7ef + languageName: node + linkType: hard + +"strip-comments@npm:^2.0.1": + version: 2.0.1 + resolution: "strip-comments@npm:2.0.1" + checksum: 984321b1ec47a531bdcfddd87f217590934e2d2f142198a080ec88588280239a5b58a81ca780730679b6195e52afef83673c6d6466c07c2277f71f44d7d9553d + languageName: node + linkType: hard + +"strip-eof@npm:^1.0.0": + version: 1.0.0 + resolution: "strip-eof@npm:1.0.0" + checksum: f336beed8622f7c1dd02f2cbd8422da9208fae81daf184f73656332899978919d5c0ca84dc6cfc49ad1fc4dd7badcde5412a063cf4e0d7f8ed95a13a63f68f45 + languageName: node + linkType: hard + +"strip-final-newline@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-final-newline@npm:2.0.0" + checksum: bddf8ccd47acd85c0e09ad7375409d81653f645fda13227a9d459642277c253d877b68f2e5e4d819fe75733b0e626bac7e954c04f3236f6d196f79c94fa4a96f + languageName: node + linkType: hard + +"strip-indent@npm:^1.0.1": + version: 1.0.1 + resolution: "strip-indent@npm:1.0.1" + dependencies: + get-stdin: ^4.0.1 + bin: + strip-indent: cli.js + checksum: 671370d44105b63daf4582a42f0a0168d58a351f6558eb913d1ede05d0ad5f964548b99f15c63fa6c7415c3980aad72f28c62997fd98fbb6da2eee1051d3c21a + languageName: node + linkType: hard + +"strip-indent@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-indent@npm:3.0.0" + dependencies: + min-indent: ^1.0.0 + checksum: ae0deaf41c8d1001c5d4fbe16cb553865c1863da4fae036683b474fa926af9fc121e155cb3fc57a68262b2ae7d5b8420aa752c97a6428c315d00efe2a3875679 + languageName: node + linkType: hard + +"strip-json-comments@npm:1.0.x": + version: 1.0.4 + resolution: "strip-json-comments@npm:1.0.4" + bin: + strip-json-comments: cli.js + checksum: 8388f352771ea508977f519758cc725670710e388ca24333bf61c7aaf073f40d99961b6b802432787ea5e5e2bf7dcbca9c391d6d7c5774f17495bf567ba08df4 + languageName: node + linkType: hard + +"strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1": + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: 9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd + languageName: node + linkType: hard + +"strip-json-comments@npm:~2.0.1": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: b509231cbdee45064ff4f9fd73609e2bcc4e84a4d508e9dd0f31f70356473fde18abfb5838c17d56fb236f5a06b102ef115438de0600b749e818a35fbbc48c43 + languageName: node + linkType: hard + +"strnum@npm:^1.0.4": + version: 1.0.5 + resolution: "strnum@npm:1.0.5" + checksum: 64fb8cc2effbd585a6821faa73ad97d4b553c8927e49086a162ffd2cc818787643390b89d567460a8e74300148d11ac052e21c921ef2049f2987f4b1b89a7ff1 + languageName: node + linkType: hard + +"style-loader@npm:^1.3.0": + version: 1.3.0 + resolution: "style-loader@npm:1.3.0" + dependencies: + loader-utils: ^2.0.0 + schema-utils: ^2.7.0 + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + checksum: 21137d63623690af0c8b135f94e01af724bc0dea560c65ff553aa06c560fac69c068ec19ae7893b3667e50e79a660e051783803c949bcd559a8fc2f839397056 + languageName: node + linkType: hard + +"style-loader@npm:^2.0.0": + version: 2.0.0 + resolution: "style-loader@npm:2.0.0" + dependencies: + loader-utils: ^2.0.0 + schema-utils: ^3.0.0 + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + checksum: 6febd1809b4f67a722e4e366fa3b3f8e1083425f7214b7a8962cf53aa7cc9c522623fb55a5e64049e46d637bbbda3b29ebbe14ec9f7652b27345450fcef6ea80 + languageName: node + linkType: hard + +"style-loader@npm:^3.3.1": + version: 3.3.1 + resolution: "style-loader@npm:3.3.1" + peerDependencies: + webpack: ^5.0.0 + checksum: b325f4ce5d0ee9797878d9db42a5c45ef6d757ad42de85bc550ef90c2fb78b762bbdff3214ddf1f4c8e1307fe1879fc47ea34ee48f8f56191309f8fc28f4d2b6 + languageName: node + linkType: hard + +"style-to-object@npm:0.3.0, style-to-object@npm:^0.3.0": + version: 0.3.0 + resolution: "style-to-object@npm:0.3.0" + dependencies: + inline-style-parser: 0.1.1 + checksum: afe9b96ba077a9068baf8887091870f50298157c0ebf5378151792cf2a2ce084fec9b34fc544da0d9f8e6c22ca0c9e23aa6f075bb8eb051aa1d64363e9987600 + languageName: node + linkType: hard + +"stylehacks@npm:^5.1.1": + version: 5.1.1 + resolution: "stylehacks@npm:5.1.1" + dependencies: + browserslist: ^4.21.4 + postcss-selector-parser: ^6.0.4 + peerDependencies: + postcss: ^8.2.15 + checksum: 402c2b545eeda0e972f125779adddc88df11bcf3a89de60c92026bd98cd49c1abffcd5bfe41766398835e0a1c7e5e72bdb6905809ecbb60716cd8d3a32ea7cd3 + languageName: node + linkType: hard + +"stylis@npm:4.1.3": + version: 4.1.3 + resolution: "stylis@npm:4.1.3" + checksum: 3e4670f26f79bcfba628dcc2756d9d415edfcbf4ec51e40f3b628fd15286222257317cad57390752964eba85cca6163a7621ce90038d68dd630a674479e52334 + languageName: node + linkType: hard + +"sugarss@npm:^4.0.1": + version: 4.0.1 + resolution: "sugarss@npm:4.0.1" + peerDependencies: + postcss: ^8.3.3 + checksum: 18b78c8839ab9eea9681366223277e10adf6b1dd54e7b64fbdae3a1234eedcc16a772bb62c8f3dc9665acc07496981e24bf773c8c362b5f294b9b1e12fbe58aa + languageName: node + linkType: hard + +"supports-color@npm:^2.0.0": + version: 2.0.0 + resolution: "supports-color@npm:2.0.0" + checksum: 570e0b63be36cccdd25186350a6cb2eaad332a95ff162fa06d9499982315f2fe4217e69dd98e862fbcd9c81eaff300a825a1fe7bf5cc752e5b84dfed042b0dda + languageName: node + linkType: hard + +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: ^3.0.0 + checksum: 6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05 + languageName: node + linkType: hard + +"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: ^4.0.0 + checksum: afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"supports-color@npm:^8.0.0, supports-color@npm:^8.1.1": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: ^4.0.0 + checksum: ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + +"svg-inline-loader@npm:^0.8.2": + version: 0.8.2 + resolution: "svg-inline-loader@npm:0.8.2" + dependencies: + loader-utils: ^1.1.0 + object-assign: ^4.0.1 + simple-html-tokenizer: ^0.1.1 + checksum: 989466d607c431bcdc82df7814b0bb3cab703e16c59875cf22a4cd62e026c6e1490bbee5c8722b4ae5fb6bc49450cbc03482bfa33842ca337fb73288d548173c + languageName: node + linkType: hard + +"svg-parser@npm:^2.0.4": + version: 2.0.4 + resolution: "svg-parser@npm:2.0.4" + checksum: 02f6cb155dd7b63ebc2f44f36365bc294543bebb81b614b7628f1af3c54ab64f7e1cec20f06e252bf95bdde78441ae295a412c68ad1678f16a6907d924512b7a + languageName: node + linkType: hard + +"svg-pathdata@npm:^6.0.3": + version: 6.0.3 + resolution: "svg-pathdata@npm:6.0.3" + checksum: 1ba4ad2fa81e86df37d6e78d3be9e664bbedf97773b725a863a85db384285be32dc37d9c0d61e477d89594ee95b967d2c53d6bee2d76420aab670ab4124a38b9 + languageName: node + linkType: hard + +"svgo@npm:^2.7.0, svgo@npm:^2.8.0": + version: 2.8.0 + resolution: "svgo@npm:2.8.0" + dependencies: + "@trysound/sax": 0.2.0 + commander: ^7.2.0 + css-select: ^4.1.3 + css-tree: ^1.1.3 + csso: ^4.2.0 + picocolors: ^1.0.0 + stable: ^0.1.8 + bin: + svgo: bin/svgo + checksum: 0741f5d5cad63111a90a0ce7a1a5a9013f6d293e871b75efe39addb57f29a263e45294e485a4d2ff9cc260a5d142c8b5937b2234b4ef05efdd2706fb2d360ecc + languageName: node + linkType: hard + +"symbol.prototype.description@npm:^1.0.0": + version: 1.0.5 + resolution: "symbol.prototype.description@npm:1.0.5" + dependencies: + call-bind: ^1.0.2 + get-symbol-description: ^1.0.0 + has-symbols: ^1.0.2 + object.getownpropertydescriptors: ^2.1.2 + checksum: 6009bb54faa50fd899772baa0c047d4d4fc85cd03b7e2b5e5385f23f4688879518103ab4a95a03b0b25e4c89c10cf0bb16c159865df2c932682cc56502693650 + languageName: node + linkType: hard + +"synchronous-promise@npm:^2.0.15": + version: 2.0.16 + resolution: "synchronous-promise@npm:2.0.16" + checksum: b0cbad61b24b36ab1a8c6ac5b10335ad7c1a006b69bf5e73e5de9a141a54abaef20b4b808a9939e81dc52cf27f4c4c7a62e90cec42a0005efda712618985ba5a + languageName: node + linkType: hard + +"syncod@npm:^0.0.1": + version: 0.0.1 + resolution: "syncod@npm:0.0.1" + checksum: 8a1009bd29cf89a672e05b5845414e870fd5c57c1f5cce6db67cd4bfb88538cb62b9ddd470f79e829481c29b3476fd33f654eb6c307e1c5d7b598181798704a6 + languageName: node + linkType: hard + +"tailwindcss@npm:^3.1.4": + version: 3.2.4 + resolution: "tailwindcss@npm:3.2.4" + dependencies: + arg: ^5.0.2 + chokidar: ^3.5.3 + color-name: ^1.1.4 + detective: ^5.2.1 + didyoumean: ^1.2.2 + dlv: ^1.1.3 + fast-glob: ^3.2.12 + glob-parent: ^6.0.2 + is-glob: ^4.0.3 + lilconfig: ^2.0.6 + micromatch: ^4.0.5 + normalize-path: ^3.0.0 + object-hash: ^3.0.0 + picocolors: ^1.0.0 + postcss: ^8.4.18 + postcss-import: ^14.1.0 + postcss-js: ^4.0.0 + postcss-load-config: ^3.1.4 + postcss-nested: 6.0.0 + postcss-selector-parser: ^6.0.10 + postcss-value-parser: ^4.2.0 + quick-lru: ^5.1.1 + resolve: ^1.22.1 + peerDependencies: + postcss: ^8.0.9 + bin: + tailwind: lib/cli.js + tailwindcss: lib/cli.js + checksum: c5d02bb594f3d0a1ff8e26ba312a86c7cec8a8a741dc00696375971c303116640acaeef8f35617297eaf79214524b5c88a27f2bdc1e1cfac47fcc6446b6260f7 + languageName: node + linkType: hard + +"tapable@npm:^1.0.0, tapable@npm:^1.1.3": + version: 1.1.3 + resolution: "tapable@npm:1.1.3" + checksum: c9f0265e55e45821ec672b9b9ee8a35d95bf3ea6b352199f8606a2799018e89cfe4433c554d424b31fc67c4be26b05d4f36dc3c607def416fdb2514cd63dba50 + languageName: node + linkType: hard + +"tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0": + version: 2.2.1 + resolution: "tapable@npm:2.2.1" + checksum: bc40e6efe1e554d075469cedaba69a30eeb373552aaf41caeaaa45bf56ffacc2674261b106245bd566b35d8f3329b52d838e851ee0a852120acae26e622925c9 + languageName: node + linkType: hard + +"tar@npm:^4.4.10, tar@npm:^4.4.12, tar@npm:^4.4.19": + version: 4.4.19 + resolution: "tar@npm:4.4.19" + dependencies: + chownr: ^1.1.4 + fs-minipass: ^1.2.7 + minipass: ^2.9.0 + minizlib: ^1.3.3 + mkdirp: ^0.5.5 + safe-buffer: ^5.2.1 + yallist: ^3.1.1 + checksum: 1a32a68feabd55e040f399f75fed37c35fd76202bb60e393986312cdee0175ff0dfd1aec9cc04ad2ade8a252d2a08c7d191fda877ce23f14a3da954d91d301d7 + languageName: node + linkType: hard + +"tar@npm:^6.0.2, tar@npm:^6.1.11, tar@npm:^6.1.2": + version: 6.1.13 + resolution: "tar@npm:6.1.13" + dependencies: + chownr: ^2.0.0 + fs-minipass: ^2.0.0 + minipass: ^4.0.0 + minizlib: ^2.1.1 + mkdirp: ^1.0.3 + yallist: ^4.0.0 + checksum: eee5f264f3f3c27cd8d4934f80c568470f92811c416144ab671bb36b45a8ed55fbfbbd31f0146f3eddaca91fd564c9a7ec4d2086940968b836f4a2c54146c060 + languageName: node + linkType: hard + +"telejson@npm:^6.0.8": + version: 6.0.8 + resolution: "telejson@npm:6.0.8" + dependencies: + "@types/is-function": ^1.0.0 + global: ^4.4.0 + is-function: ^1.0.2 + is-regex: ^1.1.2 + is-symbol: ^1.0.3 + isobject: ^4.0.0 + lodash: ^4.17.21 + memoizerific: ^1.11.3 + checksum: b9b723259504a24eae3343ca2c1020fd74e748dc7d6e532ca8171d8c3f678418f06708e2332c452480a9c8d56f8abe01e33b9e1ca3153a7bcd7640cdbfa3317b + languageName: node + linkType: hard + +"temp-dir@npm:^2.0.0": + version: 2.0.0 + resolution: "temp-dir@npm:2.0.0" + checksum: b1df969e3f3f7903f3426861887ed76ba3b495f63f6d0c8e1ce22588679d9384d336df6064210fda14e640ed422e2a17d5c40d901f60e161c99482d723f4d309 + languageName: node + linkType: hard + +"tempy@npm:^0.6.0": + version: 0.6.0 + resolution: "tempy@npm:0.6.0" + dependencies: + is-stream: ^2.0.0 + temp-dir: ^2.0.0 + type-fest: ^0.16.0 + unique-string: ^2.0.0 + checksum: ca0882276732d1313b85006b0427620cb4a8d7a57738a2311a72befae60ed152be7d5b41b951dcb447a01a35404bed76f33eb4e37c55263cd7f807eee1187f8f + languageName: node + linkType: hard + +"term-img@npm:^4.0.0": + version: 4.1.0 + resolution: "term-img@npm:4.1.0" + dependencies: + ansi-escapes: ^4.1.0 + iterm2-version: ^4.1.0 + checksum: 2cafc9b1d3b1989c7b74c6423d7511311c0316eb808bf73d9cd82dc38d3827b11bf33010e293779acb65f1f906e4755ea288102c5256c00d052e69d15aed2567 + languageName: node + linkType: hard + +"term-size@npm:^1.2.0": + version: 1.2.0 + resolution: "term-size@npm:1.2.0" + dependencies: + execa: ^0.7.0 + checksum: 2fbb2668cdd3b5e63038c28355145e98789d16143fc6754462cd4a194706c7153f15c2a6f05f579ffed27bcf2f35bdf752007927457128cc9a9ce3ec20075649 + languageName: node + linkType: hard + +"terser-webpack-plugin@npm:^1.4.3": + version: 1.4.5 + resolution: "terser-webpack-plugin@npm:1.4.5" + dependencies: + cacache: ^12.0.2 + find-cache-dir: ^2.1.0 + is-wsl: ^1.1.0 + schema-utils: ^1.0.0 + serialize-javascript: ^4.0.0 + source-map: ^0.6.1 + terser: ^4.1.2 + webpack-sources: ^1.4.0 + worker-farm: ^1.7.0 + peerDependencies: + webpack: ^4.0.0 + checksum: 97164cfa383cf988832427e912cd9606471452f15f8bfb905ae51f1a42561f90ea541141e1e530e59f8307639fed7dfdbd626aec8390acd6ad80e58ea3fcf6df + languageName: node + linkType: hard + +"terser-webpack-plugin@npm:^4.2.3": + version: 4.2.3 + resolution: "terser-webpack-plugin@npm:4.2.3" + dependencies: + cacache: ^15.0.5 + find-cache-dir: ^3.3.1 + jest-worker: ^26.5.0 + p-limit: ^3.0.2 + schema-utils: ^3.0.0 + serialize-javascript: ^5.0.1 + source-map: ^0.6.1 + terser: ^5.3.4 + webpack-sources: ^1.4.3 + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + checksum: 52bd036b72b596b162e65dce314f1ee7ba1e82b97200d919b61ad50592dc72608b5fe50d7e3f6c0934e42183dfc746b98b922c9e1d00d75253933f799687fa4b + languageName: node + linkType: hard + +"terser-webpack-plugin@npm:^5.0.3, terser-webpack-plugin@npm:^5.1.3": + version: 5.3.6 + resolution: "terser-webpack-plugin@npm:5.3.6" + dependencies: + "@jridgewell/trace-mapping": ^0.3.14 + jest-worker: ^27.4.5 + schema-utils: ^3.1.1 + serialize-javascript: ^6.0.0 + terser: ^5.14.1 + peerDependencies: + webpack: ^5.1.0 + peerDependenciesMeta: + "@swc/core": + optional: true + esbuild: + optional: true + uglify-js: + optional: true + checksum: 75ac4de6f95e62667166572b1db9f54ef163f02a7f9234549239d1a42462a5a0df67a821d791e1eb105a5a6e02941a5b03c271c56a886a508b83b90c2d52863e + languageName: node + linkType: hard + +"terser@npm:^4.1.2, terser@npm:^4.6.3": + version: 4.8.1 + resolution: "terser@npm:4.8.1" + dependencies: + commander: ^2.20.0 + source-map: ~0.6.1 + source-map-support: ~0.5.12 + bin: + terser: bin/terser + checksum: 1ec2620e58df0ea787ac579daf097df0fee2dd402f37acb4de0df1135f0598a29212e5f03042a9c2dc7e1bf1248b1dd9d9ea0724d34331a2017f32da8783b3d7 + languageName: node + linkType: hard + +"terser@npm:^5.0.0, terser@npm:^5.10.0, terser@npm:^5.14.1, terser@npm:^5.3.4": + version: 5.16.1 + resolution: "terser@npm:5.16.1" + dependencies: + "@jridgewell/source-map": ^0.3.2 + acorn: ^8.5.0 + commander: ^2.20.0 + source-map-support: ~0.5.20 + bin: + terser: bin/terser + checksum: 30acd72a0b9e6159396eb59138eb99e812a9f279813aaec1287128380ce3b7420bb51a261f2b682eeadb732517149dbcb8a7c79c4c7f9aa95ffadfb5f61d994c + languageName: node + linkType: hard + +"test-exclude@npm:^6.0.0": + version: 6.0.0 + resolution: "test-exclude@npm:6.0.0" + dependencies: + "@istanbuljs/schema": ^0.1.2 + glob: ^7.1.4 + minimatch: ^3.0.4 + checksum: 019d33d81adff3f9f1bfcff18125fb2d3c65564f437d9be539270ee74b994986abb8260c7c2ce90e8f30162178b09dbbce33c6389273afac4f36069c48521f57 + languageName: node + linkType: hard + +"text-hex@npm:1.0.x": + version: 1.0.0 + resolution: "text-hex@npm:1.0.0" + checksum: 57d8d320d92c79d7c03ffb8339b825bb9637c2cbccf14304309f51d8950015c44464b6fd1b6820a3d4821241c68825634f09f5a2d9d501e84f7c6fd14376860d + languageName: node + linkType: hard + +"text-segmentation@npm:^1.0.3": + version: 1.0.3 + resolution: "text-segmentation@npm:1.0.3" + dependencies: + utrie: ^1.0.2 + checksum: 8b9ae8524e3a332371060d0ca62f10ad49a13e954719ea689a6c3a8b8c15c8a56365ede2bb91c322fb0d44b6533785f0da603e066b7554d052999967fb72d600 + languageName: node + linkType: hard + +"text-table@npm:^0.2.0, text-table@npm:~0.2.0": + version: 0.2.0 + resolution: "text-table@npm:0.2.0" + checksum: 02805740c12851ea5982686810702e2f14369a5f4c5c40a836821e3eefc65ffeec3131ba324692a37608294b0fd8c1e55a2dd571ffed4909822787668ddbee5c + languageName: node + linkType: hard + +"throttleit@npm:^1.0.0": + version: 1.0.0 + resolution: "throttleit@npm:1.0.0" + checksum: e7c82628e5d7e3bf377878481203702a735e4310bb0c35f563a18c10ba291562332a6b61c57120c6445da1e17e7b0ff52f014b9dd310793843d4d92fa92baf2c + languageName: node + linkType: hard + +"through2@npm:^2.0.0": + version: 2.0.5 + resolution: "through2@npm:2.0.5" + dependencies: + readable-stream: ~2.3.6 + xtend: ~4.0.1 + checksum: cbfe5b57943fa12b4f8c043658c2a00476216d79c014895cef1ac7a1d9a8b31f6b438d0e53eecbb81054b93128324a82ecd59ec1a4f91f01f7ac113dcb14eade + languageName: node + linkType: hard + +"through2@npm:^3.0.1": + version: 3.0.2 + resolution: "through2@npm:3.0.2" + dependencies: + inherits: ^2.0.4 + readable-stream: 2 || 3 + checksum: 8ea17efa2ce5b78ef5c52d08e29d0dbdad9c321c2add5192bba3434cae25b2319bf9cdac1c54c3bfbd721438a30565ca6f3f19eb79f62341dafc5a12429d2ccc + languageName: node + linkType: hard + +"through@npm:>=2.2.7 <3, through@npm:^2.3.8": + version: 2.3.8 + resolution: "through@npm:2.3.8" + checksum: 4b09f3774099de0d4df26d95c5821a62faee32c7e96fb1f4ebd54a2d7c11c57fe88b0a0d49cf375de5fee5ae6bf4eb56dbbf29d07366864e2ee805349970d3cc + languageName: node + linkType: hard + +"thunky@npm:^1.0.2": + version: 1.1.0 + resolution: "thunky@npm:1.1.0" + checksum: 369764f39de1ce1de2ba2fa922db4a3f92e9c7f33bcc9a713241bc1f4a5238b484c17e0d36d1d533c625efb00e9e82c3e45f80b47586945557b45abb890156d2 + languageName: node + linkType: hard + +"timed-out@npm:^4.0.0": + version: 4.0.1 + resolution: "timed-out@npm:4.0.1" + checksum: 86f03ffce5b80c5a066e02e59e411d3fbbfcf242b19290ba76817b4180abd1b85558489586b6022b798fb1cf26fc644c0ce0efb9c271d67ec83fada4b9542a56 + languageName: node + linkType: hard + +"timers-browserify@npm:^2.0.4": + version: 2.0.12 + resolution: "timers-browserify@npm:2.0.12" + dependencies: + setimmediate: ^1.0.4 + checksum: 98e84db1a685bc8827c117a8bc62aac811ad56a995d07938fc7ed8cdc5bf3777bfe2d4e5da868847194e771aac3749a20f6cdd22091300fe889a76fe214a4641 + languageName: node + linkType: hard + +"tiny-invariant@npm:^1.0.2": + version: 1.3.1 + resolution: "tiny-invariant@npm:1.3.1" + checksum: 5b87c1d52847d9452b60d0dcb77011b459044e0361ca8253bfe7b43d6288106e12af926adb709a6fc28900e3864349b91dad9a4ac93c39aa15f360b26c2ff4db + languageName: node + linkType: hard + +"tiny-relative-date@npm:^1.3.0": + version: 1.3.0 + resolution: "tiny-relative-date@npm:1.3.0" + checksum: 70a0818793bd00345771a4ddfa9e339c102f891766c5ebce6a011905a1a20e30212851c9ffb11b52b79e2445be32bc21d164c4c6d317aef730766b2a61008f30 + languageName: node + linkType: hard + +"tiny-warning@npm:^1.0.0": + version: 1.0.3 + resolution: "tiny-warning@npm:1.0.3" + checksum: ef8531f581b30342f29670cb41ca248001c6fd7975ce22122bd59b8d62b4fc84ad4207ee7faa95cde982fa3357cd8f4be650142abc22805538c3b1392d7084fa + languageName: node + linkType: hard + +"tmp@npm:~0.2.1": + version: 0.2.1 + resolution: "tmp@npm:0.2.1" + dependencies: + rimraf: ^3.0.0 + checksum: 67607aa012059c9ce697bee820ee51bc0f39b29a8766def4f92d3f764d67c7cf9205d537d24e0cb1ce9685c40d4c628ead010910118ea18348666b5c46ed9123 + languageName: node + linkType: hard + +"tmpl@npm:1.0.5": + version: 1.0.5 + resolution: "tmpl@npm:1.0.5" + checksum: f935537799c2d1922cb5d6d3805f594388f75338fe7a4a9dac41504dd539704ca4db45b883b52e7b0aa5b2fd5ddadb1452bf95cd23a69da2f793a843f9451cc9 + languageName: node + linkType: hard + +"to-arraybuffer@npm:^1.0.0": + version: 1.0.1 + resolution: "to-arraybuffer@npm:1.0.1" + checksum: 2460bd95524f4845a751e4f8bf9937f9f3dcd1651f104e1512868782f858f8302c1cf25bbc30794bc1b3ff65c4e135158377302f2abaff43a2d8e3c38dfe098c + languageName: node + linkType: hard + +"to-fast-properties@npm:^1.0.3": + version: 1.0.3 + resolution: "to-fast-properties@npm:1.0.3" + checksum: 78974a4f4528700d18e4c2bbf0b1fb1b19862dcc20a18dc5ed659843dea2dff4f933d167a11d3819865c1191042003aea65f7f035791af9e65d070f2e05af787 + languageName: node + linkType: hard + +"to-fast-properties@npm:^2.0.0": + version: 2.0.0 + resolution: "to-fast-properties@npm:2.0.0" + checksum: b214d21dbfb4bce3452b6244b336806ffea9c05297148d32ebb428d5c43ce7545bdfc65a1ceb58c9ef4376a65c0cb2854d645f33961658b3e3b4f84910ddcdd7 + languageName: node + linkType: hard + +"to-object-path@npm:^0.3.0": + version: 0.3.0 + resolution: "to-object-path@npm:0.3.0" + dependencies: + kind-of: ^3.0.2 + checksum: 731832a977614c03a770363ad2bd9e9c82f233261861724a8e612bb90c705b94b1a290a19f52958e8e179180bb9b71121ed65e245691a421467726f06d1d7fc3 + languageName: node + linkType: hard + +"to-regex-range@npm:^2.1.0": + version: 2.1.1 + resolution: "to-regex-range@npm:2.1.1" + dependencies: + is-number: ^3.0.0 + repeat-string: ^1.6.1 + checksum: 440d82dbfe0b2e24f36dd8a9467240406ad1499fc8b2b0f547372c22ed1d092ace2a3eb522bb09bfd9c2f39bf1ca42eb78035cf6d2b8c9f5c78da3abc96cd949 + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: ^7.0.0 + checksum: 487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 + languageName: node + linkType: hard + +"to-regex@npm:^3.0.1, to-regex@npm:^3.0.2": + version: 3.0.2 + resolution: "to-regex@npm:3.0.2" + dependencies: + define-property: ^2.0.2 + extend-shallow: ^3.0.2 + regex-not: ^1.0.2 + safe-regex: ^1.1.0 + checksum: 99d0b8ef397b3f7abed4bac757b0f0bb9f52bfd39167eb7105b144becfaa9a03756892352d01ac6a911f0c1ceef9f81db68c46899521a3eed054082042796120 + languageName: node + linkType: hard + +"toggle-selection@npm:^1.0.6": + version: 1.0.6 + resolution: "toggle-selection@npm:1.0.6" + checksum: f2cf1f2c70f374fd87b0cdc8007453ba9e981c4305a8bf4eac10a30e62ecdfd28bca7d18f8f15b15a506bf8a7bfb20dbe3539f0fcf2a2c8396c1a78d53e1f179 + languageName: node + linkType: hard + +"toidentifier@npm:1.0.1": + version: 1.0.1 + resolution: "toidentifier@npm:1.0.1" + checksum: 93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 + languageName: node + linkType: hard + +"totalist@npm:^1.0.0": + version: 1.1.0 + resolution: "totalist@npm:1.1.0" + checksum: 2adbd4501c8290c2a96617a83dc67dfdd02bcbd360032017e27ccf27bbb09649bbe8dad1c45d97be6874281178aca5b3f62ed059d1eeda77c479cfb8eb3a9266 + languageName: node + linkType: hard + +"tough-cookie@npm:~2.5.0": + version: 2.5.0 + resolution: "tough-cookie@npm:2.5.0" + dependencies: + psl: ^1.1.28 + punycode: ^2.1.1 + checksum: e1cadfb24d40d64ca16de05fa8192bc097b66aeeb2704199b055ff12f450e4f30c927ce250f53d01f39baad18e1c11d66f65e545c5c6269de4c366fafa4c0543 + languageName: node + linkType: hard + +"tr46@npm:^1.0.1": + version: 1.0.1 + resolution: "tr46@npm:1.0.1" + dependencies: + punycode: ^2.1.0 + checksum: 41525c2ccce86e3ef30af6fa5e1464e6d8bb4286a58ea8db09228f598889581ef62347153f6636cd41553dc41685bdfad0a9d032ef58df9fbb0792b3447d0f04 + languageName: node + linkType: hard + +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11 + languageName: node + linkType: hard + +"trim-newlines@npm:^1.0.0": + version: 1.0.0 + resolution: "trim-newlines@npm:1.0.0" + checksum: ae859c83d0dbcbde32245509f7c51a4bc9696d56e080bc19acc95c4188381e34fba05a4b2fefb47b4ee4537150a11d57a0fd3cd1179837c06210795d7f62e795 + languageName: node + linkType: hard + +"trim-trailing-lines@npm:^1.0.0": + version: 1.1.4 + resolution: "trim-trailing-lines@npm:1.1.4" + checksum: 95c35ece5fc806e626e7a93a2135c52932d1dee584963138dbefb1df6cb7adcb7a7c68e2c63f05c536f0681c9260e1d5262cb2e234242d23b9a31617b2c1d53c + languageName: node + linkType: hard + +"trim@npm:0.0.1": + version: 0.0.1 + resolution: "trim@npm:0.0.1" + checksum: d974971fc8b8629d13286f20ec6ccc48f480494ca9df358d452beb1fd7eea1b802be41cc7ee157be4abbdf1b3ca79cc6d04c34b14a7026037d437e8de9dacecb + languageName: node + linkType: hard + +"triple-beam@npm:^1.3.0": + version: 1.3.0 + resolution: "triple-beam@npm:1.3.0" + checksum: a6da96495f25b6c04b3629df5161c7eb84760927943f16665fd8dcd3a643daadf73d69eee78306b4b68d606937f22f8703afe763bc8d3723632ffb1f3a798493 + languageName: node + linkType: hard + +"trough@npm:^1.0.0": + version: 1.0.5 + resolution: "trough@npm:1.0.5" + checksum: f036d0d7f9bc7cfe5ee650d70b57bb1f048f3292adf6c81bb9b228e546b2b2e5b74ea04a060d21472108a8cda05ec4814bbe86f87ee35c182c50cb41b5c1810a + languageName: node + linkType: hard + +"ts-dedent@npm:^2.0.0, ts-dedent@npm:^2.2.0": + version: 2.2.0 + resolution: "ts-dedent@npm:2.2.0" + checksum: 175adea838468cc2ff7d5e97f970dcb798bbcb623f29c6088cb21aa2880d207c5784be81ab1741f56b9ac37840cbaba0c0d79f7f8b67ffe61c02634cafa5c303 + languageName: node + linkType: hard + +"ts-jest@npm:^29.0.5": + version: 29.0.5 + resolution: "ts-jest@npm:29.0.5" + dependencies: + bs-logger: 0.x + fast-json-stable-stringify: 2.x + jest-util: ^29.0.0 + json5: ^2.2.3 + lodash.memoize: 4.x + make-error: 1.x + semver: 7.x + yargs-parser: ^21.0.1 + peerDependencies: + "@babel/core": ">=7.0.0-beta.0 <8" + "@jest/types": ^29.0.0 + babel-jest: ^29.0.0 + jest: ^29.0.0 + typescript: ">=4.3" + peerDependenciesMeta: + "@babel/core": + optional: true + "@jest/types": + optional: true + babel-jest: + optional: true + esbuild: + optional: true + bin: + ts-jest: cli.js + checksum: 8f8b3654964b1afe6b7d4384b371ddec445c2584ba939df64ddf8d900ae791ed41bb340f3cc0b05366e716dd4c38d8a90fa0e28ba02575d0f2b7fcb221964350 + languageName: node + linkType: hard + +"ts-node@npm:^10.7.0": + version: 10.9.1 + resolution: "ts-node@npm:10.9.1" + dependencies: + "@cspotcode/source-map-support": ^0.8.0 + "@tsconfig/node10": ^1.0.7 + "@tsconfig/node12": ^1.0.7 + "@tsconfig/node14": ^1.0.0 + "@tsconfig/node16": ^1.0.2 + acorn: ^8.4.1 + acorn-walk: ^8.1.1 + arg: ^4.1.0 + create-require: ^1.1.0 + diff: ^4.0.1 + make-error: ^1.1.1 + v8-compile-cache-lib: ^3.0.1 + yn: 3.1.1 + peerDependencies: + "@swc/core": ">=1.2.50" + "@swc/wasm": ">=1.2.50" + "@types/node": "*" + typescript: ">=2.7" + peerDependenciesMeta: + "@swc/core": + optional: true + "@swc/wasm": + optional: true + bin: + ts-node: dist/bin.js + ts-node-cwd: dist/bin-cwd.js + ts-node-esm: dist/bin-esm.js + ts-node-script: dist/bin-script.js + ts-node-transpile-only: dist/bin-transpile.js + ts-script: dist/bin-script-deprecated.js + checksum: 95187932fb83f3901e22546bd2feeac7d2feb4f412f42ac3a595f049a23e8dcf70516dffb51866391228ea2dbcfaea039e250fb2bb334d48a86ab2b6aea0ae2d + languageName: node + linkType: hard + +"ts-pnp@npm:^1.1.6": + version: 1.2.0 + resolution: "ts-pnp@npm:1.2.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: ff32b4f810f9d99f676d70fe2c0e327cb6c812214bd4fc7135870b039f9e85a85b2c20f8fe030d9bd36e9598a12faa391f10aecb95df624b92f1af6bd47dc397 + languageName: node + linkType: hard + +"tslib@npm:^1.8.1, tslib@npm:^1.9.3": + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: 69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 + languageName: node + linkType: hard + +"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0": + version: 2.4.1 + resolution: "tslib@npm:2.4.1" + checksum: 9ac0e4fd1033861f0b4f0d848dc3009ebcc3aa4757a06e8602a2d8a7aed252810e3540e54e70709f06c0f95311faa8584f769bcbede48aff785eb7e4d399b9ec + languageName: node + linkType: hard + +"tsutils@npm:^3.21.0": + version: 3.21.0 + resolution: "tsutils@npm:3.21.0" + dependencies: + tslib: ^1.8.1 + 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" + checksum: 02f19e458ec78ead8fffbf711f834ad8ecd2cc6ade4ec0320790713dccc0a412b99e7fd907c4cda2a1dc602c75db6f12e0108e87a5afad4b2f9e90a24cabd5a2 + languageName: node + linkType: hard + +"tty-browserify@npm:0.0.0": + version: 0.0.0 + resolution: "tty-browserify@npm:0.0.0" + checksum: c0c68206565f1372e924d5cdeeff1a0d9cc729833f1da98c03d78be8f939e5f61a107bd0ab77d1ef6a47d62bb0e48b1081fbea273acf404959e22fd3891439c5 + languageName: node + linkType: hard + +"tunnel-agent@npm:^0.6.0": + version: 0.6.0 + resolution: "tunnel-agent@npm:0.6.0" + dependencies: + safe-buffer: ^5.0.1 + checksum: 4c7a1b813e7beae66fdbf567a65ec6d46313643753d0beefb3c7973d66fcec3a1e7f39759f0a0b4465883499c6dc8b0750ab8b287399af2e583823e40410a17a + languageName: node + linkType: hard + +"tweetnacl@npm:^0.14.3, tweetnacl@npm:~0.14.0": + version: 0.14.5 + resolution: "tweetnacl@npm:0.14.5" + checksum: 4612772653512c7bc19e61923fbf42903f5e0389ec76a4a1f17195859d114671ea4aa3b734c2029ce7e1fa7e5cc8b80580f67b071ecf0b46b5636d030a0102a2 + languageName: node + linkType: hard + +"type-check@npm:^0.4.0, type-check@npm:~0.4.0": + version: 0.4.0 + resolution: "type-check@npm:0.4.0" + dependencies: + prelude-ls: ^1.2.1 + checksum: 7b3fd0ed43891e2080bf0c5c504b418fbb3e5c7b9708d3d015037ba2e6323a28152ec163bcb65212741fa5d2022e3075ac3c76440dbd344c9035f818e8ecee58 + languageName: node + linkType: hard + +"type-check@npm:~0.3.2": + version: 0.3.2 + resolution: "type-check@npm:0.3.2" + dependencies: + prelude-ls: ~1.1.2 + checksum: 776217116b2b4e50e368c7ee0c22c0a85e982881c16965b90d52f216bc296d6a52ef74f9202d22158caacc092a7645b0b8d5fe529a96e3fe35d0fb393966c875 + languageName: node + linkType: hard + +"type-detect@npm:4.0.8": + version: 4.0.8 + resolution: "type-detect@npm:4.0.8" + checksum: 8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd + languageName: node + linkType: hard + +"type-fest@npm:^0.16.0": + version: 0.16.0 + resolution: "type-fest@npm:0.16.0" + checksum: 6b4d846534e7bcb49a6160b068ffaed2b62570d989d909ac3f29df5ef1e993859f890a4242eebe023c9e923f96adbcb3b3e88a198c35a1ee9a731e147a6839c3 + languageName: node + linkType: hard + +"type-fest@npm:^0.20.2": + version: 0.20.2 + resolution: "type-fest@npm:0.20.2" + checksum: dea9df45ea1f0aaa4e2d3bed3f9a0bfe9e5b2592bddb92eb1bf06e50bcf98dbb78189668cd8bc31a0511d3fc25539b4cd5c704497e53e93e2d40ca764b10bfc3 + languageName: node + linkType: hard + +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: 902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 + languageName: node + linkType: hard + +"type-fest@npm:^0.6.0": + version: 0.6.0 + resolution: "type-fest@npm:0.6.0" + checksum: 0c585c26416fce9ecb5691873a1301b5aff54673c7999b6f925691ed01f5b9232db408cdbb0bd003d19f5ae284322523f44092d1f81ca0a48f11f7cf0be8cd38 + languageName: node + linkType: hard + +"type-fest@npm:^0.8.1": + version: 0.8.1 + resolution: "type-fest@npm:0.8.1" + checksum: dffbb99329da2aa840f506d376c863bd55f5636f4741ad6e65e82f5ce47e6914108f44f340a0b74009b0cb5d09d6752ae83203e53e98b1192cf80ecee5651636 + languageName: node + linkType: hard + +"type-is@npm:~1.6.18": + version: 1.6.18 + resolution: "type-is@npm:1.6.18" + dependencies: + media-typer: 0.3.0 + mime-types: ~2.1.24 + checksum: a23daeb538591b7efbd61ecf06b6feb2501b683ffdc9a19c74ef5baba362b4347e42f1b4ed81f5882a8c96a3bfff7f93ce3ffaf0cbbc879b532b04c97a55db9d + languageName: node + linkType: hard + +"type@npm:^1.0.1": + version: 1.2.0 + resolution: "type@npm:1.2.0" + checksum: 444660849aaebef8cbb9bc43b28ec2068952064cfce6a646f88db97aaa2e2d6570c5629cd79238b71ba23aa3f75146a0b96e24e198210ee0089715a6f8889bf7 + languageName: node + linkType: hard + +"type@npm:^2.7.2": + version: 2.7.2 + resolution: "type@npm:2.7.2" + checksum: 84c2382788fe24e0bc3d64c0c181820048f672b0f06316aa9c7bdb373f8a09f8b5404f4e856bc4539fb931f2f08f2adc4c53f6c08c9c0314505d70c29a1289e1 + languageName: node + linkType: hard + +"typed-array-length@npm:^1.0.4": + version: 1.0.4 + resolution: "typed-array-length@npm:1.0.4" + dependencies: + call-bind: ^1.0.2 + for-each: ^0.3.3 + is-typed-array: ^1.1.9 + checksum: c5163c0103d07fefc8a2ad0fc151f9ca9a1f6422098c00f695d55f9896e4d63614cd62cf8d8a031c6cee5f418e8980a533796597174da4edff075b3d275a7e23 + languageName: node + linkType: hard + +"typedarray-to-buffer@npm:^3.1.5": + version: 3.1.5 + resolution: "typedarray-to-buffer@npm:3.1.5" + dependencies: + is-typedarray: ^1.0.0 + checksum: 4ac5b7a93d604edabf3ac58d3a2f7e07487e9f6e98195a080e81dbffdc4127817f470f219d794a843b87052cedef102b53ac9b539855380b8c2172054b7d5027 + languageName: node + linkType: hard + +"typedarray@npm:^0.0.6": + version: 0.0.6 + resolution: "typedarray@npm:0.0.6" + checksum: 6005cb31df50eef8b1f3c780eb71a17925f3038a100d82f9406ac2ad1de5eb59f8e6decbdc145b3a1f8e5836e17b0c0002fb698b9fe2516b8f9f9ff602d36412 + languageName: node + linkType: hard + +"typescript@npm:^4.6.4": + version: 4.9.4 + resolution: "typescript@npm:4.9.4" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 5008b97a2a3afdbe57ea70e504ebc8ec98f18d888059dfb7932a41f566a1360a28afc8de2a440fd6143b4014cc6d2616079931dc690c7513c2d21fa53957e0ec + languageName: node + linkType: hard + +"typescript@patch:typescript@^4.6.4#~builtin<compat/typescript>": + version: 4.9.4 + resolution: "typescript@patch:typescript@npm%3A4.9.4#~builtin<compat/typescript>::version=4.9.4&hash=7ad353" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: b8cba6ab3ee30218578663352bcd74220ef20c42544fd1cc1382f24dee385002e784702b176c451da9c14c39965c035815902ef43b06eeefd5faae18ee700cea + languageName: node + linkType: hard + +"ua-parser-js@npm:^0.7.30": + version: 0.7.32 + resolution: "ua-parser-js@npm:0.7.32" + checksum: 5311835284fada204adf1d1eed88b291abeb1e1d65e08a57b348cfe7e7647db7f0d024cf400d2182298b77c81aa9e486c81d1faa8299ed7d88bc322354c7e9cd + languageName: node + linkType: hard + +"uglify-js@npm:^3.1.4": + version: 3.17.4 + resolution: "uglify-js@npm:3.17.4" + bin: + uglifyjs: bin/uglifyjs + checksum: 8b7fcdca69deb284fed7d2025b73eb747ce37f9aca6af53422844f46427152d5440601b6e2a033e77856a2f0591e4167153d5a21b68674ad11f662034ec13ced + languageName: node + linkType: hard + +"uid-number@npm:0.0.6": + version: 0.0.6 + resolution: "uid-number@npm:0.0.6" + checksum: 0b9922962d24df7a67d05b60e9b8647c217f2bcb8880da24132945715abe4b2b0cf16089f8f11ac4e60d6db0da6ca0af24d3f8bae7df53ca098ac00954841235 + languageName: node + linkType: hard + +"umask@npm:^1.1.0, umask@npm:~1.1.0": + version: 1.1.0 + resolution: "umask@npm:1.1.0" + checksum: 22f308853eb94f919c18d5be155ad5182416841e9e95921d8f056d70df023fa02ec861dd23687243eeaf16bb5aea09a4690539fe50ed9562bb818432f554c638 + languageName: node + linkType: hard + +"unbox-primitive@npm:^1.0.2": + version: 1.0.2 + resolution: "unbox-primitive@npm:1.0.2" + dependencies: + call-bind: ^1.0.2 + has-bigints: ^1.0.2 + has-symbols: ^1.0.3 + which-boxed-primitive: ^1.0.2 + checksum: 81ca2e81134167cc8f75fa79fbcc8a94379d6c61de67090986a2273850989dd3bae8440c163121b77434b68263e34787a675cbdcb34bb2f764c6b9c843a11b66 + languageName: node + linkType: hard + +"underscore@npm:>1.4.4": + version: 1.13.6 + resolution: "underscore@npm:1.13.6" + checksum: 5f57047f47273044c045fddeb8b141dafa703aa487afd84b319c2495de2e685cecd0b74abec098292320d518b267c0c4598e45aa47d4c3628d0d4020966ba521 + languageName: node + linkType: hard + +"unfetch@npm:^4.2.0": + version: 4.2.0 + resolution: "unfetch@npm:4.2.0" + checksum: a5c0a896a6f09f278b868075aea65652ad185db30e827cb7df45826fe5ab850124bf9c44c4dafca4bf0c55a0844b17031e8243467fcc38dd7a7d435007151f1b + languageName: node + linkType: hard + +"unherit@npm:^1.0.4": + version: 1.1.3 + resolution: "unherit@npm:1.1.3" + dependencies: + inherits: ^2.0.0 + xtend: ^4.0.0 + checksum: f953b548e56ef347b14c0897484ff22187acfeeb599afe2994cfdbfaddffe8731b999029e243fd40966b597bdffd541f3b5a54254797b98aebb760bb39dd8456 + languageName: node + linkType: hard + +"unicode-canonical-property-names-ecmascript@npm:^2.0.0": + version: 2.0.0 + resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.0" + checksum: 0fe812641bcfa3ae433025178a64afb5d9afebc21a922dafa7cba971deebb5e4a37350423890750132a85c936c290fb988146d0b1bd86838ad4897f4fc5bd0de + languageName: node + linkType: hard + +"unicode-match-property-ecmascript@npm:^2.0.0": + version: 2.0.0 + resolution: "unicode-match-property-ecmascript@npm:2.0.0" + dependencies: + unicode-canonical-property-names-ecmascript: ^2.0.0 + unicode-property-aliases-ecmascript: ^2.0.0 + checksum: 4d05252cecaf5c8e36d78dc5332e03b334c6242faf7cf16b3658525441386c0a03b5f603d42cbec0f09bb63b9fd25c9b3b09667aee75463cac3efadae2cd17ec + languageName: node + linkType: hard + +"unicode-match-property-value-ecmascript@npm:^2.1.0": + version: 2.1.0 + resolution: "unicode-match-property-value-ecmascript@npm:2.1.0" + checksum: f5b9499b9e0ffdc6027b744d528f17ec27dd7c15da03254ed06851feec47e0531f20d410910c8a49af4a6a190f4978413794c8d75ce112950b56d583b5d5c7f2 + languageName: node + linkType: hard + +"unicode-property-aliases-ecmascript@npm:^2.0.0": + version: 2.1.0 + resolution: "unicode-property-aliases-ecmascript@npm:2.1.0" + checksum: 50ded3f8c963c7785e48c510a3b7c6bc4e08a579551489aa0349680a35b1ceceec122e33b2b6c1b579d0be2250f34bb163ac35f5f8695fe10bbc67fb757f0af8 + languageName: node + linkType: hard + +"unified@npm:9.2.0": + version: 9.2.0 + resolution: "unified@npm:9.2.0" + dependencies: + bail: ^1.0.0 + extend: ^3.0.0 + is-buffer: ^2.0.0 + is-plain-obj: ^2.0.0 + trough: ^1.0.0 + vfile: ^4.0.0 + checksum: 53aedb794b0ada002b72593d74633f45742e3dfe771a8091c0f51b59119f74f3f1bba0a24c5d72a35629793f992cf9e1debf21aa4689dc718482ffec3a633623 + languageName: node + linkType: hard + +"union-value@npm:^1.0.0": + version: 1.0.1 + resolution: "union-value@npm:1.0.1" + dependencies: + arr-union: ^3.1.0 + get-value: ^2.0.6 + is-extendable: ^0.1.1 + set-value: ^2.0.1 + checksum: 8758d880cb9545f62ce9cfb9b791b2b7a206e0ff5cc4b9d7cd6581da2c6839837fbb45e639cf1fd8eef3cae08c0201b614b7c06dd9f5f70d9dbe7c5fe2fbf592 + languageName: node + linkType: hard + +"unique-filename@npm:^1.1.1": + version: 1.1.1 + resolution: "unique-filename@npm:1.1.1" + dependencies: + unique-slug: ^2.0.0 + checksum: d005bdfaae6894da8407c4de2b52f38b3c58ec86e79fc2ee19939da3085374413b073478ec54e721dc8e32b102cf9e50d0481b8331abdc62202e774b789ea874 + languageName: node + linkType: hard + +"unique-filename@npm:^2.0.0": + version: 2.0.1 + resolution: "unique-filename@npm:2.0.1" + dependencies: + unique-slug: ^3.0.0 + checksum: 55d95cd670c4a86117ebc34d394936d712d43b56db6bc511f9ca00f666373818bf9f075fb0ab76bcbfaf134592ef26bb75aad20786c1ff1ceba4457eaba90fb8 + languageName: node + linkType: hard + +"unique-slug@npm:^2.0.0": + version: 2.0.2 + resolution: "unique-slug@npm:2.0.2" + dependencies: + imurmurhash: ^0.1.4 + checksum: 9eabc51680cf0b8b197811a48857e41f1364b25362300c1ff636c0eca5ec543a92a38786f59cf0697e62c6f814b11ecbe64e8093db71246468a1f03b80c83970 + languageName: node + linkType: hard + +"unique-slug@npm:^3.0.0": + version: 3.0.0 + resolution: "unique-slug@npm:3.0.0" + dependencies: + imurmurhash: ^0.1.4 + checksum: 617240eb921af803b47d322d75a71a363dacf2e56c29ae5d1404fad85f64f4ec81ef10ee4fd79215d0202cbe1e5a653edb0558d59c9c81d3bd538c2d58e4c026 + languageName: node + linkType: hard + +"unique-string@npm:^1.0.0": + version: 1.0.0 + resolution: "unique-string@npm:1.0.0" + dependencies: + crypto-random-string: ^1.0.0 + checksum: 79cc2a6515a51e6350c74f65c92246511966c47528f1119318cbe8d68a508842f4e5a2a81857a65f3919629397a525f820505116dd89cac425294598e35ca12c + languageName: node + linkType: hard + +"unique-string@npm:^2.0.0": + version: 2.0.0 + resolution: "unique-string@npm:2.0.0" + dependencies: + crypto-random-string: ^2.0.0 + checksum: 11820db0a4ba069d174bedfa96c588fc2c96b083066fafa186851e563951d0de78181ac79c744c1ed28b51f9d82ac5b8196ff3e4560d0178046ef455d8c2244b + languageName: node + linkType: hard + +"unist-builder@npm:2.0.3, unist-builder@npm:^2.0.0": + version: 2.0.3 + resolution: "unist-builder@npm:2.0.3" + checksum: d8b13ffd774bfe6175ca988d63cbaf6d85882a0701d6158597134ce1c3acf665a09421461a4036704f77edb8a6a2792d09eb55382428c2a9a60488b44909eeae + languageName: node + linkType: hard + +"unist-util-generated@npm:^1.0.0": + version: 1.1.6 + resolution: "unist-util-generated@npm:1.1.6" + checksum: ee04a58a6711145ec5c8c6f10dfd3335ac93d9039dc35e7410ffc1299d6f3671b27d9b7aa486f826bd66ec15807ad6d0bf9348b34a1046440e1617abcf42903f + languageName: node + linkType: hard + +"unist-util-is@npm:^4.0.0": + version: 4.1.0 + resolution: "unist-util-is@npm:4.1.0" + checksum: 21ca3d7bacc88853b880b19cb1b133a056c501617d7f9b8cce969cd8b430ed7e1bc416a3a11b02540d5de6fb86807e169d00596108a459d034cf5faec97c055e + languageName: node + linkType: hard + +"unist-util-position@npm:^3.0.0": + version: 3.1.0 + resolution: "unist-util-position@npm:3.1.0" + checksum: a89d4095560f01e0ddfdab3deae6abd250ee6b91c3b23922de05297227a4aede076d96cb0e22e9962d0e85f54d11f719d1e11388233d0936631b8527485a02a8 + languageName: node + linkType: hard + +"unist-util-remove-position@npm:^2.0.0": + version: 2.0.1 + resolution: "unist-util-remove-position@npm:2.0.1" + dependencies: + unist-util-visit: ^2.0.0 + checksum: 9aadc8e9fafc4eeb04462454ab084184b84b397a367cab3787c59411b16c8f03d13e80e9ffd6bdae68bf8e5175f42008f410288a041a6ee53bcac8ced45a12ed + languageName: node + linkType: hard + +"unist-util-remove@npm:^2.0.0": + version: 2.1.0 + resolution: "unist-util-remove@npm:2.1.0" + dependencies: + unist-util-is: ^4.0.0 + checksum: f7dea56fb720ddab5e406af12ce37453b028273e23a7cc3e4c9f3f1ec85e1f72c6943a1ebb907120c9be0b1d08b209d7b8c7d2191a5012e16081056edf638df9 + languageName: node + linkType: hard + +"unist-util-stringify-position@npm:^2.0.0": + version: 2.0.3 + resolution: "unist-util-stringify-position@npm:2.0.3" + dependencies: + "@types/unist": ^2.0.2 + checksum: 46fa03f840df173b7f032cbfffdb502fb05b79b3fb5451681c796cf4985d9087a537833f5afb75d55e79b46bbbe4b3d81dd75a1062f9289091c526aebe201d5d + languageName: node + linkType: hard + +"unist-util-visit-parents@npm:^3.0.0": + version: 3.1.1 + resolution: "unist-util-visit-parents@npm:3.1.1" + dependencies: + "@types/unist": ^2.0.0 + unist-util-is: ^4.0.0 + checksum: 231c80c5ba8e79263956fcaa25ed2a11ad7fe77ac5ba0d322e9d51bbc4238501e3bb52f405e518bcdc5471e27b33eff520db0aa4a3b1feb9fb6e2de6ae385d49 + languageName: node + linkType: hard + +"unist-util-visit@npm:2.0.3, unist-util-visit@npm:^2.0.0": + version: 2.0.3 + resolution: "unist-util-visit@npm:2.0.3" + dependencies: + "@types/unist": ^2.0.0 + unist-util-is: ^4.0.0 + unist-util-visit-parents: ^3.0.0 + checksum: 7b11303d82271ca53a2ced2d56c87a689dd518596c99ff4a11cdff750f5cc5c0e4b64b146bd2363557cb29443c98713bfd1e8dc6d1c3f9d474b9eb1f23a60888 + languageName: node + linkType: hard + +"universalify@npm:^0.1.0": + version: 0.1.2 + resolution: "universalify@npm:0.1.2" + checksum: e70e0339f6b36f34c9816f6bf9662372bd241714dc77508d231d08386d94f2c4aa1ba1318614f92015f40d45aae1b9075cd30bd490efbe39387b60a76ca3f045 + languageName: node + linkType: hard + +"universalify@npm:^2.0.0": + version: 2.0.0 + resolution: "universalify@npm:2.0.0" + checksum: 07092b9f46df61b823d8ab5e57f0ee5120c178b39609a95e4a15a98c42f6b0b8e834e66fbb47ff92831786193be42f1fd36347169b88ce8639d0f9670af24a71 + languageName: node + linkType: hard + +"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": + version: 1.0.0 + resolution: "unpipe@npm:1.0.0" + checksum: 193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c + languageName: node + linkType: hard + +"unset-value@npm:^1.0.0": + version: 1.0.0 + resolution: "unset-value@npm:1.0.0" + dependencies: + has-value: ^0.3.1 + isobject: ^3.0.0 + checksum: 68a796dde4a373afdbf017de64f08490a3573ebee549136da0b3a2245299e7f65f647ef70dc13c4ac7f47b12fba4de1646fa0967a365638578fedce02b9c0b1f + languageName: node + linkType: hard + +"untildify@npm:^2.0.0": + version: 2.1.0 + resolution: "untildify@npm:2.1.0" + dependencies: + os-homedir: ^1.0.0 + checksum: 8a8a8766fcac7a796104d2124d1951af87889e95fd73d5704c78000fb0612fe21041eb33eadb7b4dedc44dd99db3c4adba04775454990baf7c4b728076ab2bc2 + languageName: node + linkType: hard + +"untildify@npm:^4.0.0": + version: 4.0.0 + resolution: "untildify@npm:4.0.0" + checksum: d758e624c707d49f76f7511d75d09a8eda7f2020d231ec52b67ff4896bcf7013be3f9522d8375f57e586e9a2e827f5641c7e06ee46ab9c435fc2b2b2e9de517a + languageName: node + linkType: hard + +"unzip-response@npm:^2.0.1": + version: 2.0.1 + resolution: "unzip-response@npm:2.0.1" + checksum: 8df383f28e87bcc1e0810343c435a524d62063841ace6cb507edeade4d74e78be76d32a84e35e7fa270a1b697ba86fd3e3c153224228a6db88e728576fa7e4f3 + languageName: node + linkType: hard + +"upath@npm:^1.1.1, upath@npm:^1.2.0": + version: 1.2.0 + resolution: "upath@npm:1.2.0" + checksum: 3746f24099bf69dbf8234cecb671e1016e1f6b26bd306de4ff8966fb0bc463fa1014ffc48646b375de1ab573660e3a0256f6f2a87218b2dfa1779a84ef6992fa + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.0.9": + version: 1.0.10 + resolution: "update-browserslist-db@npm:1.0.10" + dependencies: + escalade: ^3.1.1 + picocolors: ^1.0.0 + peerDependencies: + browserslist: ">= 4.21.0" + bin: + browserslist-lint: cli.js + checksum: e6fa55b515a674cc3b6c045d1f37f72780ddbbbb48b3094391fb2e43357b859ca5cee4c7d3055fd654d442ef032777d0972494a9a2e6c30d3660ee57b7138ae9 + languageName: node + linkType: hard + +"update-notifier@npm:^2.3.0, update-notifier@npm:^2.5.0": + version: 2.5.0 + resolution: "update-notifier@npm:2.5.0" + dependencies: + boxen: ^1.2.1 + chalk: ^2.0.1 + configstore: ^3.0.0 + import-lazy: ^2.1.0 + is-ci: ^1.0.10 + is-installed-globally: ^0.1.0 + is-npm: ^1.0.0 + latest-version: ^3.0.0 + semver-diff: ^2.0.0 + xdg-basedir: ^3.0.0 + checksum: f6b82e655f115b67e829a9f10fe75d2c3afae84a51941478791bfa49023e2c35386790deb8f53d7149fa7e323d6cec1ab9580d92c84e99c7aca3f00e1cfe5efe + languageName: node + linkType: hard + +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: ^2.1.0 + checksum: 4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c + languageName: node + linkType: hard + +"urix@npm:^0.1.0": + version: 0.1.0 + resolution: "urix@npm:0.1.0" + checksum: 264f1b29360c33c0aec5fb9819d7e28f15d1a3b83175d2bcc9131efe8583f459f07364957ae3527f1478659ec5b2d0f1ad401dfb625f73e4d424b3ae35fc5fc0 + languageName: node + linkType: hard + +"url-loader@npm:^4.1.1": + version: 4.1.1 + resolution: "url-loader@npm:4.1.1" + dependencies: + loader-utils: ^2.0.0 + mime-types: ^2.1.27 + schema-utils: ^3.0.0 + peerDependencies: + file-loader: "*" + webpack: ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + file-loader: + optional: true + checksum: 71b6300e02ce26c70625eae1a2297c0737635038c62691bb3007ac33e85c0130efc74bfb444baf5c6b3bad5953491159d31d66498967d1417865d0c7e7cd1a64 + languageName: node + linkType: hard + +"url-parse-lax@npm:^1.0.0": + version: 1.0.0 + resolution: "url-parse-lax@npm:1.0.0" + dependencies: + prepend-http: ^1.0.1 + checksum: 7578d90d18297c0896ab3c98350b61b59be56211baad543ea55eb570dfbd403b0987e499a817abb55d755df6f47ec2e748a49bd09f8d39766066a6871853cea1 + languageName: node + linkType: hard + +"url@npm:0.10.3": + version: 0.10.3 + resolution: "url@npm:0.10.3" + dependencies: + punycode: 1.3.2 + querystring: 0.2.0 + checksum: f0a1c7d99ac35dd68a8962bc7b3dd38f08d457387fc686f0669ff881b00a68eabd9cb3aded09dfbe25401d7b632fc4a9c074cb373f6a4bd1d8b5324d1d442a0d + languageName: node + linkType: hard + +"url@npm:^0.11.0": + version: 0.11.0 + resolution: "url@npm:0.11.0" + dependencies: + punycode: 1.3.2 + querystring: 0.2.0 + checksum: bbe05f9f570ec5c06421c50ca63f287e61279092eed0891db69a9619323703ccd3987e6eed234c468794cf25680c599680d5c1f58d26090f1956c8e9ed8346a2 + languageName: node + linkType: hard + +"use-composed-ref@npm:^1.3.0": + version: 1.3.0 + resolution: "use-composed-ref@npm:1.3.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: e64ce52f4b18c020407636784192726807404a2552609acf7497b66a2b7070674fb5d2b950d426c4aa85f353e2bbecb02ebf9c5b865cd06797938c70bcbf5d26 + languageName: node + linkType: hard + +"use-isomorphic-layout-effect@npm:^1.1.1, use-isomorphic-layout-effect@npm:^1.1.2": + version: 1.1.2 + resolution: "use-isomorphic-layout-effect@npm:1.1.2" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: d8deea8b85e55ac6daba237a889630bfdbf0ebf60e9e22b6a78a78c26fabe6025e04ada7abef1e444e6786227d921e648b2707db8b3564daf757264a148a6e23 + languageName: node + linkType: hard + +"use-latest@npm:^1.2.1": + version: 1.2.1 + resolution: "use-latest@npm:1.2.1" + dependencies: + use-isomorphic-layout-effect: ^1.1.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 1958886fc35262d973f5cd4ce16acd6ce3a66707a72761c93abd1b5ae64e1a11efa83f68e6c8c9bf1647628037980ce59df64cba50adb36bd4071851e70527d2 + languageName: node + linkType: hard + +"use@npm:^3.1.0": + version: 3.1.1 + resolution: "use@npm:3.1.1" + checksum: 75b48673ab80d5139c76922630d5a8a44e72ed58dbaf54dee1b88352d10e1c1c1fc332066c782d8ae9a56503b85d3dc67ff6d2ffbd9821120466d1280ebb6d6e + languageName: node + linkType: hard + +"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 + languageName: node + linkType: hard + +"util-extend@npm:^1.0.1": + version: 1.0.3 + resolution: "util-extend@npm:1.0.3" + checksum: 93d5a4faec6ce92d6976fc421ed716888054291602fed8067134867a37226738a557e4785f47b8a5ecde60837e0c694da73895f66badbd4bc8d57ec9d4799bfd + languageName: node + linkType: hard + +"util-promisify@npm:^2.1.0": + version: 2.1.0 + resolution: "util-promisify@npm:2.1.0" + dependencies: + object.getownpropertydescriptors: ^2.0.3 + checksum: 00783d459e83b64943eecccf3dedc9a704b929032d65c4d835acd1f9ba81a2c3da0bda28b03635b6f10406f7a4febbe9a3be305bad67e53c6108fa3e48877e3f + languageName: node + linkType: hard + +"util.promisify@npm:1.0.0": + version: 1.0.0 + resolution: "util.promisify@npm:1.0.0" + dependencies: + define-properties: ^1.1.2 + object.getownpropertydescriptors: ^2.0.3 + checksum: af9df9d111b1464586e4fa414ccf6de61c3a14c0664a66a497438a0507d47f65389f5e025c048ef7e2bf6dba73e95adc3d0c56111a0952ae0282817fc4dd83b2 + languageName: node + linkType: hard + +"util@npm:0.10.3": + version: 0.10.3 + resolution: "util@npm:0.10.3" + dependencies: + inherits: 2.0.1 + checksum: 88bb58fec3b1f5f43dea27795f61f24b3b505bbba6f3ad6e91b32db0cd0928b2acb54ebe21603a75743c6e21a52f954cd2ffb6cddafed5a01169dd1287db3ff3 + languageName: node + linkType: hard + +"util@npm:^0.11.0": + version: 0.11.1 + resolution: "util@npm:0.11.1" + dependencies: + inherits: 2.0.3 + checksum: 8e9d1a85e661c8a8d9883d821aedbff3f8d9c3accd85357020905386ada5653b20389fc3591901e2a0bde64f8dc86b28c3f990114aa5a38eaaf30b455fa3cdf6 + languageName: node + linkType: hard + +"util@npm:^0.12.3, util@npm:^0.12.4": + version: 0.12.5 + resolution: "util@npm:0.12.5" + 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 + checksum: c27054de2cea2229a66c09522d0fa1415fb12d861d08523a8846bf2e4cbf0079d4c3f725f09dcb87493549bcbf05f5798dce1688b53c6c17201a45759e7253f3 + languageName: node + linkType: hard + +"utila@npm:~0.4": + version: 0.4.0 + resolution: "utila@npm:0.4.0" + checksum: 2791604e09ca4f77ae314df83e80d1805f867eb5c7e13e7413caee01273c278cf2c9a3670d8d25c889a877f7b149d892fe61b0181a81654b425e9622ab23d42e + languageName: node + linkType: hard + +"utils-merge@npm:1.0.1": + version: 1.0.1 + resolution: "utils-merge@npm:1.0.1" + checksum: 02ba649de1b7ca8854bfe20a82f1dfbdda3fb57a22ab4a8972a63a34553cf7aa51bc9081cf7e001b035b88186d23689d69e71b510e610a09a4c66f68aa95b672 + languageName: node + linkType: hard + +"utrie@npm:^1.0.2": + version: 1.0.2 + resolution: "utrie@npm:1.0.2" + dependencies: + base64-arraybuffer: ^1.0.2 + checksum: eaffe645bd81a39e4bc3abb23df5895e9961dbdd49748ef3b173529e8b06ce9dd1163e9705d5309a1c61ee41ffcb825e2043bc0fd1659845ffbdf4b1515dfdb4 + languageName: node + linkType: hard + +"uuid-browser@npm:^3.1.0": + version: 3.1.0 + resolution: "uuid-browser@npm:3.1.0" + checksum: bfb6bcc8cc75c1adf776370c4f86d00ee5682f7315c8bccb99938e53dafae189ef6a4dc125e67abd2a2cdfaad6020690fe4cb67dbd5b39f32d3ba75fb713d807 + languageName: node + linkType: hard + +"uuid@npm:8.0.0": + version: 8.0.0 + resolution: "uuid@npm:8.0.0" + bin: + uuid: dist/bin/uuid + checksum: e62301a1c6102da5ce9a147b492a4b5cfa14d2e8fdf4a6ebfda7929cb72d186f84173815ec18fa4160a03bf9724b16ece3737b3ac6701815bc965f8fa4279298 + languageName: node + linkType: hard + +"uuid@npm:^3.3.2, uuid@npm:^3.4.0": + version: 3.4.0 + resolution: "uuid@npm:3.4.0" + bin: + uuid: ./bin/uuid + checksum: 1c13950df865c4f506ebfe0a24023571fa80edf2e62364297a537c80af09c618299797bbf2dbac6b1f8ae5ad182ba474b89db61e0e85839683991f7e08795347 + languageName: node + linkType: hard + +"uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54 + languageName: node + linkType: hard + +"v8-compile-cache-lib@npm:^3.0.1": + version: 3.0.1 + resolution: "v8-compile-cache-lib@npm:3.0.1" + checksum: bdc36fb8095d3b41df197f5fb6f11e3a26adf4059df3213e3baa93810d8f0cc76f9a74aaefc18b73e91fe7e19154ed6f134eda6fded2e0f1c8d2272ed2d2d391 + languageName: node + linkType: hard + +"v8-to-istanbul@npm:^9.0.0, v8-to-istanbul@npm:^9.0.1": + version: 9.0.1 + resolution: "v8-to-istanbul@npm:9.0.1" + dependencies: + "@jridgewell/trace-mapping": ^0.3.12 + "@types/istanbul-lib-coverage": ^2.0.1 + convert-source-map: ^1.6.0 + checksum: aaa6491ee0505010a818a98bd7abdb30c0136a93eac12106b836e1afb519759ea4da795cceaf7fe871d26ed6cb669e46fd48533d6f8107a23213d723a028f805 + languageName: node + linkType: hard + +"v8flags@npm:^3.1.1": + version: 3.2.0 + resolution: "v8flags@npm:3.2.0" + dependencies: + homedir-polyfill: ^1.0.1 + checksum: aa0149384c1b75eee60f9e4339dbcc891d5a2154f51dbe41feb35a2227e88c0f30701234676c47b7887414c6a95bce23783931eeed52126842b7ba3a75984da7 + languageName: node + linkType: hard + +"validate-npm-package-license@npm:^3.0.1, validate-npm-package-license@npm:^3.0.4": + version: 3.0.4 + resolution: "validate-npm-package-license@npm:3.0.4" + dependencies: + spdx-correct: ^3.0.0 + spdx-expression-parse: ^3.0.0 + checksum: 7b91e455a8de9a0beaa9fe961e536b677da7f48c9a493edf4d4d4a87fd80a7a10267d438723364e432c2fcd00b5650b5378275cded362383ef570276e6312f4f + languageName: node + linkType: hard + +"validate-npm-package-name@npm:^3.0.0, validate-npm-package-name@npm:~3.0.0": + version: 3.0.0 + resolution: "validate-npm-package-name@npm:3.0.0" + dependencies: + builtins: ^1.0.3 + checksum: 064f21f59aefae6cc286dd4a50b15d14adb0227e0facab4316197dfb8d06801669e997af5081966c15f7828a5e6ff1957bd20886aeb6b9d0fa430e4cb5db9c4a + languageName: node + linkType: hard + +"value-equal@npm:^1.0.1": + version: 1.0.1 + resolution: "value-equal@npm:1.0.1" + checksum: 79068098355483ef29f4d3753999ad880875b87625d7e9055cad9346ea4b7662aad3a66f87976801b0dd7a6f828ba973d28b1669ebcd37eaf88cc5f687c1a691 + languageName: node + linkType: hard + +"vary@npm:~1.1.2": + version: 1.1.2 + resolution: "vary@npm:1.1.2" + checksum: f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f + languageName: node + linkType: hard + +"verror@npm:1.10.0": + version: 1.10.0 + resolution: "verror@npm:1.10.0" + dependencies: + assert-plus: ^1.0.0 + core-util-is: 1.0.2 + extsprintf: ^1.2.0 + checksum: 37ccdf8542b5863c525128908ac80f2b476eed36a32cb944de930ca1e2e78584cc435c4b9b4c68d0fc13a47b45ff364b4be43aa74f8804f9050140f660fb660d + languageName: node + linkType: hard + +"vfile-location@npm:^3.0.0, vfile-location@npm:^3.2.0": + version: 3.2.0 + resolution: "vfile-location@npm:3.2.0" + checksum: d9513c738fcac26388f4ee04337663514434df718201309088377b53be3fdcfdb01a4a8f02f5a21ebf33690a670f31229e4c7c3991fb7af63f549fda3ec36836 + languageName: node + linkType: hard + +"vfile-message@npm:^2.0.0": + version: 2.0.4 + resolution: "vfile-message@npm:2.0.4" + dependencies: + "@types/unist": ^2.0.0 + unist-util-stringify-position: ^2.0.0 + checksum: ce50d90e0e5dc8f995f39602dd2404f1756388a54209c983d259b17c15e6f262a53546977a638065bc487d0657799fa96f4c1ba6b2915d9724a4968e9c7ff1c8 + languageName: node + linkType: hard + +"vfile@npm:^4.0.0": + version: 4.2.1 + resolution: "vfile@npm:4.2.1" + dependencies: + "@types/unist": ^2.0.0 + is-buffer: ^2.0.0 + unist-util-stringify-position: ^2.0.0 + vfile-message: ^2.0.0 + checksum: 4816aecfedc794ba4d3131abff2032ef0e825632cfa8cd20dd9d83819ef260589924f4f3e8fa30e06da2d8e60d7ec8ef7d0af93e0483df62890738258daf098a + languageName: node + linkType: hard + +"victory-vendor@npm:^36.6.8": + version: 36.6.8 + resolution: "victory-vendor@npm:36.6.8" + dependencies: + "@types/d3-array": ^3.0.3 + "@types/d3-ease": ^3.0.0 + "@types/d3-interpolate": ^3.0.1 + "@types/d3-scale": ^4.0.2 + "@types/d3-shape": ^3.1.0 + "@types/d3-time": ^3.0.0 + "@types/d3-timer": ^3.0.0 + d3-array: ^3.1.6 + d3-ease: ^3.0.1 + d3-interpolate: ^3.0.1 + d3-scale: ^4.0.2 + d3-shape: ^3.1.0 + d3-time: ^3.0.0 + d3-timer: ^3.0.1 + checksum: 72dd8355f706879eb6a01a8ecba7bde8d99aa3acb748111ccf498dc71a7258a03154b5c2ae72f36fb4ec775e7554fabeb3e3dd1c4c3487126b09654f7f038423 + languageName: node + linkType: hard + +"vm-browserify@npm:^1.0.1": + version: 1.1.2 + resolution: "vm-browserify@npm:1.1.2" + checksum: 0cc1af6e0d880deb58bc974921320c187f9e0a94f25570fca6b1bd64e798ce454ab87dfd797551b1b0cc1849307421aae0193cedf5f06bdb5680476780ee344b + languageName: node + linkType: hard + +"walker@npm:^1.0.7, walker@npm:^1.0.8, walker@npm:~1.0.5": + version: 1.0.8 + resolution: "walker@npm:1.0.8" + dependencies: + makeerror: 1.0.12 + checksum: a17e037bccd3ca8a25a80cb850903facdfed0de4864bd8728f1782370715d679fa72e0a0f5da7c1c1379365159901e5935f35be531229da53bbfc0efdabdb48e + languageName: node + linkType: hard + +"warning@npm:^4.0.2": + version: 4.0.3 + resolution: "warning@npm:4.0.3" + dependencies: + loose-envify: ^1.0.0 + checksum: aebab445129f3e104c271f1637fa38e55eb25f968593e3825bd2f7a12bd58dc3738bb70dc8ec85826621d80b4acfed5a29ebc9da17397c6125864d72301b937e + languageName: node + linkType: hard + +"watchpack-chokidar2@npm:^2.0.1": + version: 2.0.1 + resolution: "watchpack-chokidar2@npm:2.0.1" + dependencies: + chokidar: ^2.1.8 + checksum: 9b8d880ae2543dd4f26a69f6b7f881119494f6b772b7431027a06a5cf963e0ebc1cac91a3ef479365c358b693c65fa80a1f8297427fa11fd4c080c3d6408c372 + languageName: node + linkType: hard + +"watchpack@npm:^1.7.4": + version: 1.7.5 + resolution: "watchpack@npm:1.7.5" + dependencies: + chokidar: ^3.4.1 + graceful-fs: ^4.1.2 + neo-async: ^2.5.0 + watchpack-chokidar2: ^2.0.1 + dependenciesMeta: + chokidar: + optional: true + watchpack-chokidar2: + optional: true + checksum: 53e3b112064f5de9edbb2a14973fb3901d9697b24cc70f8531a143eaace2353a273ca25c0ba21def8d3803cfedb8f6861ca1e49e9782257e40d5b5f8f5365c86 + languageName: node + linkType: hard + +"watchpack@npm:^2.2.0, watchpack@npm:^2.4.0": + version: 2.4.0 + resolution: "watchpack@npm:2.4.0" + dependencies: + glob-to-regexp: ^0.4.1 + graceful-fs: ^4.1.2 + checksum: c5e35f9fb9338d31d2141d9835643c0f49b5f9c521440bb648181059e5940d93dd8ed856aa8a33fbcdd4e121dad63c7e8c15c063cf485429cd9d427be197fe62 + languageName: node + linkType: hard + +"wbuf@npm:^1.1.0, wbuf@npm:^1.7.3": + version: 1.7.3 + resolution: "wbuf@npm:1.7.3" + dependencies: + minimalistic-assert: ^1.0.0 + checksum: 56edcc5ef2b3d30913ba8f1f5cccc364d180670b24d5f3f8849c1e6fb514e5c7e3a87548ae61227a82859eba6269c11393ae24ce12a2ea1ecb9b465718ddced7 + languageName: node + linkType: hard + +"wcwidth@npm:^1.0.0": + version: 1.0.1 + resolution: "wcwidth@npm:1.0.1" + dependencies: + defaults: ^1.0.3 + checksum: 5b61ca583a95e2dd85d7078400190efd452e05751a64accb8c06ce4db65d7e0b0cde9917d705e826a2e05cc2548f61efde115ffa374c3e436d04be45c889e5b4 + languageName: node + linkType: hard + +"web-encoding@npm:^1.1.5": + version: 1.1.5 + resolution: "web-encoding@npm:1.1.5" + dependencies: + "@zxing/text-encoding": 0.9.0 + util: ^0.12.3 + dependenciesMeta: + "@zxing/text-encoding": + optional: true + checksum: 59d5413338ec0894c690006f5d8508b0c88cae1d8c78606c3f326e351c672196461ed808b849fe08d0900fa56a61fcacb9ff576499068d2ead0a7bc04afa7d34 + languageName: node + linkType: hard + +"web-namespaces@npm:^1.0.0": + version: 1.1.4 + resolution: "web-namespaces@npm:1.1.4" + checksum: 05b5782c32a33ef94fa7a412afdebc9d0d3cc7b59db31d2cc7bd80de3e237d4b6309cb5f156d06e3a837b9826c9414448c25111ec1d4407d2025ffeb7bea4f62 + languageName: node + linkType: hard + +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: 5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db + languageName: node + linkType: hard + +"webidl-conversions@npm:^4.0.2": + version: 4.0.2 + resolution: "webidl-conversions@npm:4.0.2" + checksum: def5c5ac3479286dffcb604547628b2e6b46c5c5b8a8cfaa8c71dc3bafc85859bde5fbe89467ff861f571ab38987cf6ab3d6e7c80b39b999e50e803c12f3164f + languageName: node + linkType: hard + +"webpack-bundle-analyzer@npm:^4.5.0": + version: 4.7.0 + resolution: "webpack-bundle-analyzer@npm:4.7.0" + dependencies: + acorn: ^8.0.4 + acorn-walk: ^8.0.0 + chalk: ^4.1.0 + commander: ^7.2.0 + gzip-size: ^6.0.0 + lodash: ^4.17.20 + opener: ^1.5.2 + sirv: ^1.0.7 + ws: ^7.3.1 + bin: + webpack-bundle-analyzer: lib/bin/analyzer.js + checksum: 7ca9546dae1271ecd4524646bf45fad32dc69f2293c1ade5f9a0d581d44ab4a12af94294c5ecfe0ed12c5b9327f827a3adfc733b77c162ebe3e0411371f8788e + languageName: node + linkType: hard + +"webpack-cli@npm:^4.9.2": + version: 4.10.0 + resolution: "webpack-cli@npm:4.10.0" + dependencies: + "@discoveryjs/json-ext": ^0.5.0 + "@webpack-cli/configtest": ^1.2.0 + "@webpack-cli/info": ^1.5.0 + "@webpack-cli/serve": ^1.7.0 + colorette: ^2.0.14 + commander: ^7.0.0 + cross-spawn: ^7.0.3 + fastest-levenshtein: ^1.0.12 + import-local: ^3.0.2 + interpret: ^2.2.0 + rechoir: ^0.7.0 + webpack-merge: ^5.7.3 + peerDependencies: + webpack: 4.x.x || 5.x.x + peerDependenciesMeta: + "@webpack-cli/generators": + optional: true + "@webpack-cli/migrate": + optional: true + webpack-bundle-analyzer: + optional: true + webpack-dev-server: + optional: true + bin: + webpack-cli: bin/cli.js + checksum: e144821a3eaf8c2598e80d6bc8b1b4035e6f5cb0046b3090ad0f858f87480f007127d5c5efa83c79436df3f31e0c0d6033fd9ea93526395984ef986ba5d72aa3 + languageName: node + linkType: hard + +"webpack-dev-middleware@npm:^3.7.3": + version: 3.7.3 + resolution: "webpack-dev-middleware@npm:3.7.3" + dependencies: + memory-fs: ^0.4.1 + mime: ^2.4.4 + mkdirp: ^0.5.1 + range-parser: ^1.2.1 + webpack-log: ^2.0.0 + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + checksum: f9bd8318c6f356d006dc99e3e46ef8870d67640e43f26cfcd2bb36c9e7eaf64015513f43498e92b532896f7fbd8f32c0710d4489fc81d7a45ea328d7e4cf3085 + languageName: node + linkType: hard + +"webpack-dev-middleware@npm:^4.1.0": + version: 4.3.0 + resolution: "webpack-dev-middleware@npm:4.3.0" + dependencies: + colorette: ^1.2.2 + mem: ^8.1.1 + memfs: ^3.2.2 + mime-types: ^2.1.30 + range-parser: ^1.2.1 + schema-utils: ^3.0.0 + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + checksum: e60685e6958d8bb12762cf393e27b06ee94c453fa0fb7ea90892458ceb05e20ede2ded678019b56c5c80ee646ec7f09ab647a19ab6a2ff8e51b6bb758bacd1d9 + languageName: node + linkType: hard + +"webpack-dev-middleware@npm:^5.3.1": + version: 5.3.3 + resolution: "webpack-dev-middleware@npm:5.3.3" + dependencies: + colorette: ^2.0.10 + memfs: ^3.4.3 + mime-types: ^2.1.31 + range-parser: ^1.2.1 + schema-utils: ^4.0.0 + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + checksum: 378ceed430b61c0b0eccdbb55a97173aa36231bb88e20ad12bafb3d553e542708fa31f08474b9c68d4ac95174a047def9e426e193b7134be3736afa66a0d1708 + languageName: node + linkType: hard + +"webpack-dev-server@npm:^4.9.0": + version: 4.11.1 + resolution: "webpack-dev-server@npm:4.11.1" + dependencies: + "@types/bonjour": ^3.5.9 + "@types/connect-history-api-fallback": ^1.3.5 + "@types/express": ^4.17.13 + "@types/serve-index": ^1.9.1 + "@types/serve-static": ^1.13.10 + "@types/sockjs": ^0.3.33 + "@types/ws": ^8.5.1 + ansi-html-community: ^0.0.8 + bonjour-service: ^1.0.11 + chokidar: ^3.5.3 + colorette: ^2.0.10 + compression: ^1.7.4 + connect-history-api-fallback: ^2.0.0 + default-gateway: ^6.0.3 + express: ^4.17.3 + graceful-fs: ^4.2.6 + html-entities: ^2.3.2 + http-proxy-middleware: ^2.0.3 + ipaddr.js: ^2.0.1 + open: ^8.0.9 + p-retry: ^4.5.0 + rimraf: ^3.0.2 + schema-utils: ^4.0.0 + selfsigned: ^2.1.1 + serve-index: ^1.9.1 + sockjs: ^0.3.24 + spdy: ^4.0.2 + webpack-dev-middleware: ^5.3.1 + ws: ^8.4.2 + peerDependencies: + webpack: ^4.37.0 || ^5.0.0 + peerDependenciesMeta: + webpack-cli: + optional: true + bin: + webpack-dev-server: bin/webpack-dev-server.js + checksum: 31cf2d80efd3e7a3843e4382f4e10a2c9446574d67b190eda6f4cbd761cc3a5e5be5f3c3ad4d67963b03b3c90485dd80527408c5f0dacb2de6710ecb73ed9e7d + languageName: node + linkType: hard + +"webpack-filter-warnings-plugin@npm:^1.2.1": + version: 1.2.1 + resolution: "webpack-filter-warnings-plugin@npm:1.2.1" + peerDependencies: + webpack: ^2.0.0 || ^3.0.0 || ^4.0.0 + checksum: 0a30b2b7725e4d4de96701d3a76b10d1e6a6e502a26e64177d8bcb1ba16a34dc87ab82f588b5b46620f105ac471c3b76e5f2810244b373efbdf6d3dc193553da + languageName: node + linkType: hard + +"webpack-hot-middleware@npm:^2.25.1": + version: 2.25.3 + resolution: "webpack-hot-middleware@npm:2.25.3" + dependencies: + ansi-html-community: 0.0.8 + html-entities: ^2.1.0 + strip-ansi: ^6.0.0 + checksum: dca26d6d32eb684469830b4074dcd11c298036fe3ec8276ea5a69bc14bc8a53aab2494e02871ada146ec0881965d7018d559683cbcc73bcafcfa4da1184e92b5 + languageName: node + linkType: hard + +"webpack-log@npm:^2.0.0": + version: 2.0.0 + resolution: "webpack-log@npm:2.0.0" + dependencies: + ansi-colors: ^3.0.0 + uuid: ^3.3.2 + checksum: 515b800433da1c0b5722317baaeb05fc185da5a1fde5e39d25bed0b05c13ee3a544aa13844db8590696274a3c5dc04fd5abdd39f38f8c46a4084b74ff0dc9c60 + languageName: node + linkType: hard + +"webpack-merge@npm:^5.7.3": + version: 5.8.0 + resolution: "webpack-merge@npm:5.8.0" + dependencies: + clone-deep: ^4.0.1 + wildcard: ^2.0.0 + checksum: 400eaaba69d2240d51dc7a4427dde37849a8f2fdf93731be6a8aad34d70d55bb38cb10c5001c7b339fc91f8c8547e782ecbd79eff24ad861e21e6a4c5dc959fb + languageName: node + linkType: hard + +"webpack-sources@npm:^1.4.0, webpack-sources@npm:^1.4.1, webpack-sources@npm:^1.4.3": + version: 1.4.3 + resolution: "webpack-sources@npm:1.4.3" + dependencies: + source-list-map: ^2.0.0 + source-map: ~0.6.1 + checksum: 78dafb3e1e297d3f4eb6204311e8c64d28cd028f82887ba33aaf03fffc82482d8e1fdf6de25a60f4dde621d3565f4c3b1bfb350f09add8f4e54e00279ff3db5e + languageName: node + linkType: hard + +"webpack-sources@npm:^3.2.3": + version: 3.2.3 + resolution: "webpack-sources@npm:3.2.3" + checksum: 2ef63d77c4fad39de4a6db17323d75eb92897b32674e97d76f0a1e87c003882fc038571266ad0ef581ac734cbe20952912aaa26155f1905e96ce251adbb1eb4e + languageName: node + linkType: hard + +"webpack-virtual-modules@npm:^0.2.2": + version: 0.2.2 + resolution: "webpack-virtual-modules@npm:0.2.2" + dependencies: + debug: ^3.0.0 + checksum: 1e4156cbc7d64fde1a4531c3a2f92ccbe5702f16c34e3379ea302f7917b8c6c52f91328b893b615a34531a69c1e5079ec3b2edb7479f9908bd8243006437daa3 + languageName: node + linkType: hard + +"webpack-virtual-modules@npm:^0.4.1": + version: 0.4.6 + resolution: "webpack-virtual-modules@npm:0.4.6" + checksum: d3ecd680289e04f6fac70f09a682385b176303cfdc69ad08f11fce6fa031f9c054b3e728cb54967da48f051cd2ebe3f0d0d02bf78d3dfc8a3a9be91ea7544bbb + languageName: node + linkType: hard + +"webpack@npm:4": + version: 4.46.0 + resolution: "webpack@npm:4.46.0" + dependencies: + "@webassemblyjs/ast": 1.9.0 + "@webassemblyjs/helper-module-context": 1.9.0 + "@webassemblyjs/wasm-edit": 1.9.0 + "@webassemblyjs/wasm-parser": 1.9.0 + acorn: ^6.4.1 + ajv: ^6.10.2 + ajv-keywords: ^3.4.1 + chrome-trace-event: ^1.0.2 + enhanced-resolve: ^4.5.0 + eslint-scope: ^4.0.3 + json-parse-better-errors: ^1.0.2 + loader-runner: ^2.4.0 + loader-utils: ^1.2.3 + memory-fs: ^0.4.1 + micromatch: ^3.1.10 + mkdirp: ^0.5.3 + neo-async: ^2.6.1 + node-libs-browser: ^2.2.1 + schema-utils: ^1.0.0 + tapable: ^1.1.3 + terser-webpack-plugin: ^1.4.3 + watchpack: ^1.7.4 + webpack-sources: ^1.4.1 + peerDependenciesMeta: + webpack-cli: + optional: true + webpack-command: + optional: true + bin: + webpack: bin/webpack.js + checksum: 3451b48b926d7c295a4eba65bb7ff9a7d2d49a848014ea0945f446ebf4c1ca5bdd15681b444f5dfd8bbc4856afda55211d30a173ae721b8108f229792e6fb509 + languageName: node + linkType: hard + +"webpack@npm:>=4.43.0 <6.0.0, webpack@npm:^5.72.1, webpack@npm:^5.9.0": + version: 5.75.0 + resolution: "webpack@npm:5.75.0" + dependencies: + "@types/eslint-scope": ^3.7.3 + "@types/estree": ^0.0.51 + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/wasm-edit": 1.11.1 + "@webassemblyjs/wasm-parser": 1.11.1 + acorn: ^8.7.1 + acorn-import-assertions: ^1.7.6 + browserslist: ^4.14.5 + chrome-trace-event: ^1.0.2 + enhanced-resolve: ^5.10.0 + es-module-lexer: ^0.9.0 + eslint-scope: 5.1.1 + events: ^3.2.0 + glob-to-regexp: ^0.4.1 + graceful-fs: ^4.2.9 + json-parse-even-better-errors: ^2.3.1 + loader-runner: ^4.2.0 + mime-types: ^2.1.27 + neo-async: ^2.6.2 + schema-utils: ^3.1.0 + tapable: ^2.1.1 + terser-webpack-plugin: ^5.1.3 + watchpack: ^2.4.0 + webpack-sources: ^3.2.3 + peerDependenciesMeta: + webpack-cli: + optional: true + bin: + webpack: bin/webpack.js + checksum: 0160331d6255bdb8027f2589458514709a4a6555e2868adb6356a309d3f7b2212cb129a00f343fe0f94f54a31b4677507a3adf9ae73badc1216105ac548681ea + languageName: node + linkType: hard + +"webrtc-adapter@npm:^7.7.1": + version: 7.7.1 + resolution: "webrtc-adapter@npm:7.7.1" + dependencies: + rtcpeerconnection-shim: ^1.2.15 + sdp: ^2.12.0 + checksum: f3432a5d6247896dd61458f7c4b2c15177aaab7aa42c4d32855735bcd948fc646c0471afe95d542edf45e170b2e6405353e8020752e8db4a74c36be524303767 + languageName: node + linkType: hard + +"websocket-driver@npm:>=0.5.1, websocket-driver@npm:^0.7.4": + version: 0.7.4 + resolution: "websocket-driver@npm:0.7.4" + dependencies: + http-parser-js: ">=0.5.1" + safe-buffer: ">=5.1.0" + websocket-extensions: ">=0.1.1" + checksum: 5f09547912b27bdc57bac17b7b6527d8993aa4ac8a2d10588bb74aebaf785fdcf64fea034aae0c359b7adff2044dd66f3d03866e4685571f81b13e548f9021f1 + languageName: node + linkType: hard + +"websocket-extensions@npm:>=0.1.1": + version: 0.1.4 + resolution: "websocket-extensions@npm:0.1.4" + checksum: bbc8c233388a0eb8a40786ee2e30d35935cacbfe26ab188b3e020987e85d519c2009fe07cfc37b7f718b85afdba7e54654c9153e6697301f72561bfe429177e0 + languageName: node + linkType: hard + +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: ~0.0.3 + webidl-conversions: ^3.0.0 + checksum: 1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5 + languageName: node + linkType: hard + +"whatwg-url@npm:^7.0.0": + version: 7.1.0 + resolution: "whatwg-url@npm:7.1.0" + dependencies: + lodash.sortby: ^4.7.0 + tr46: ^1.0.1 + webidl-conversions: ^4.0.2 + checksum: 2785fe4647690e5a0225a79509ba5e21fdf4a71f9de3eabdba1192483fe006fc79961198e0b99f82751557309f17fc5a07d4d83c251aa5b2f85ba71e674cbee9 + languageName: node + linkType: hard + +"which-boxed-primitive@npm:^1.0.2": + version: 1.0.2 + resolution: "which-boxed-primitive@npm:1.0.2" + 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 + checksum: 0a62a03c00c91dd4fb1035b2f0733c341d805753b027eebd3a304b9cb70e8ce33e25317add2fe9b5fea6f53a175c0633ae701ff812e604410ddd049777cd435e + languageName: node + linkType: hard + +"which-collection@npm:^1.0.1": + version: 1.0.1 + resolution: "which-collection@npm:1.0.1" + dependencies: + is-map: ^2.0.1 + is-set: ^2.0.1 + is-weakmap: ^2.0.1 + is-weakset: ^2.0.1 + checksum: 249f913e1758ed2f06f00706007d87dc22090a80591a56917376e70ecf8fc9ab6c41d98e1c87208bb9648676f65d4b09c0e4d23c56c7afb0f0a73a27d701df5d + languageName: node + linkType: hard + +"which-module@npm:^2.0.0": + version: 2.0.0 + resolution: "which-module@npm:2.0.0" + checksum: 946ffdbcd6f0cf517638f8f2319c6d51e528c3b41bc2c0f5dc3dc46047347abd7326aea5cdf5def0a8b32bdca313ac87a32ce5a76b943fe1ca876c4557e6b716 + languageName: node + linkType: hard + +"which-typed-array@npm:^1.1.2, which-typed-array@npm:^1.1.9": + version: 1.1.9 + resolution: "which-typed-array@npm:1.1.9" + 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 + is-typed-array: ^1.1.10 + checksum: 7edb12cfd04bfe2e2d3ec3e6046417c59e6a8c72209e4fe41fe1a1a40a3b196626c2ca63dac2a0fa2491d5c37c065dfabd2fcf7c0c15f1d19f5640fef88f6368 + languageName: node + linkType: hard + +"which@npm:^1.2.9, which@npm:^1.3.0, which@npm:^1.3.1": + version: 1.3.1 + resolution: "which@npm:1.3.1" + dependencies: + isexe: ^2.0.0 + bin: + which: ./bin/which + checksum: e945a8b6bbf6821aaaef7f6e0c309d4b615ef35699576d5489b4261da9539f70393c6b2ce700ee4321c18f914ebe5644bc4631b15466ffbaad37d83151f6af59 + languageName: node + linkType: hard + +"which@npm:^2.0.1, which@npm:^2.0.2": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: ^2.0.0 + bin: + node-which: ./bin/node-which + checksum: 66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"wide-align@npm:^1.1.0, wide-align@npm:^1.1.2, wide-align@npm:^1.1.5": + version: 1.1.5 + resolution: "wide-align@npm:1.1.5" + dependencies: + string-width: ^1.0.2 || 2 || 3 || 4 + checksum: 1d9c2a3e36dfb09832f38e2e699c367ef190f96b82c71f809bc0822c306f5379df87bab47bed27ea99106d86447e50eb972d3c516c2f95782807a9d082fbea95 + languageName: node + linkType: hard + +"widest-line@npm:^2.0.0": + version: 2.0.1 + resolution: "widest-line@npm:2.0.1" + dependencies: + string-width: ^2.1.1 + checksum: c8c908c737b522a8cf46254bc85b3b0c9e6934a054840d9b01ea7f36442aa11c8393579fa57bc9ef9a5c5ea69f49e78783ce27b8a8ebab4f855ca044efa584fa + languageName: node + linkType: hard + +"widest-line@npm:^3.1.0": + version: 3.1.0 + resolution: "widest-line@npm:3.1.0" + dependencies: + string-width: ^4.0.0 + checksum: b1e623adcfb9df35350dd7fc61295d6d4a1eaa65a406ba39c4b8360045b614af95ad10e05abf704936ed022569be438c4bfa02d6d031863c4166a238c301119f + languageName: node + linkType: hard + +"wildcard@npm:^2.0.0": + version: 2.0.0 + resolution: "wildcard@npm:2.0.0" + checksum: 4e22a45f4fa7f0f0d3e11860ee9ce9225246d41af6ec507e6a7d64c2692afb40d695b92c8f801deda8d3536007c2ec07981079fd0c8bb38b8521de072b33ab7a + languageName: node + linkType: hard + +"winston-transport@npm:^4.5.0": + version: 4.5.0 + resolution: "winston-transport@npm:4.5.0" + dependencies: + logform: ^2.3.2 + readable-stream: ^3.6.0 + triple-beam: ^1.3.0 + checksum: 110a47c5acc87c3aa0f101741c0a992e52a86802272838c18aede8178d2b5e80254d2433dcac3439cefbc2777d9e22e65f84e9cee3130681c58e4ae5d58f50c3 + languageName: node + linkType: hard + +"winston@npm:3": + version: 3.8.2 + resolution: "winston@npm:3.8.2" + dependencies: + "@colors/colors": 1.5.0 + "@dabh/diagnostics": ^2.0.2 + async: ^3.2.3 + is-stream: ^2.0.0 + logform: ^2.4.0 + one-time: ^1.0.0 + readable-stream: ^3.4.0 + safe-stable-stringify: ^2.3.1 + stack-trace: 0.0.x + triple-beam: ^1.3.0 + winston-transport: ^4.5.0 + checksum: 8d302d992e53e46c8f0355fae515356190d2dc9feaa8ae99afa5bba227f1fe2343ca9e367e5949a9b711f5f982e5b9280393d16be99fcd432660fd9597749954 + languageName: node + linkType: hard + +"word-wrap@npm:^1.2.3, word-wrap@npm:~1.2.3": + version: 1.2.3 + resolution: "word-wrap@npm:1.2.3" + checksum: 1cb6558996deb22c909330db1f01d672feee41d7f0664492912de3de282da3f28ba2d49e87b723024e99d56ba2dac2f3ab28f8db07ac199f5e5d5e2e437833de + languageName: node + linkType: hard + +"wordwrap@npm:^1.0.0": + version: 1.0.0 + resolution: "wordwrap@npm:1.0.0" + checksum: 7ed2e44f3c33c5c3e3771134d2b0aee4314c9e49c749e37f464bf69f2bcdf0cbf9419ca638098e2717cff4875c47f56a007532f6111c3319f557a2ca91278e92 + languageName: node + linkType: hard + +"workbox-background-sync@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-background-sync@npm:6.5.4" + dependencies: + idb: ^7.0.1 + workbox-core: 6.5.4 + checksum: 4d1431c19fb1bb07f5ce31e286f0d62de908b813857f3d2e5048c21cafb318a41a1ae0cf135545bc6053f6d4c487951f4c232241c47caab37908a70e87d3fa30 + languageName: node + linkType: hard + +"workbox-broadcast-update@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-broadcast-update@npm:6.5.4" + dependencies: + workbox-core: 6.5.4 + checksum: fa7164d4b11ce2ff7922a84fef836b432147b49dd04b9afa543908d035245c394707d87a15faf2ea2c28c76b5d6ae9db6600b01faa734a2d29b3aa4543a8baab + languageName: node + linkType: hard + +"workbox-build@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-build@npm:6.5.4" + dependencies: + "@apideck/better-ajv-errors": ^0.3.1 + "@babel/core": ^7.11.1 + "@babel/preset-env": ^7.11.0 + "@babel/runtime": ^7.11.2 + "@rollup/plugin-babel": ^5.2.0 + "@rollup/plugin-node-resolve": ^11.2.1 + "@rollup/plugin-replace": ^2.4.1 + "@surma/rollup-plugin-off-main-thread": ^2.2.3 + ajv: ^8.6.0 + common-tags: ^1.8.0 + fast-json-stable-stringify: ^2.1.0 + fs-extra: ^9.0.1 + glob: ^7.1.6 + lodash: ^4.17.20 + pretty-bytes: ^5.3.0 + rollup: ^2.43.1 + rollup-plugin-terser: ^7.0.0 + source-map: ^0.8.0-beta.0 + stringify-object: ^3.3.0 + strip-comments: ^2.0.1 + tempy: ^0.6.0 + upath: ^1.2.0 + workbox-background-sync: 6.5.4 + workbox-broadcast-update: 6.5.4 + workbox-cacheable-response: 6.5.4 + workbox-core: 6.5.4 + workbox-expiration: 6.5.4 + workbox-google-analytics: 6.5.4 + workbox-navigation-preload: 6.5.4 + workbox-precaching: 6.5.4 + workbox-range-requests: 6.5.4 + workbox-recipes: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + workbox-streams: 6.5.4 + workbox-sw: 6.5.4 + workbox-window: 6.5.4 + checksum: aaded320ecb1f2f40439accbe7445a31fe72185ee976bbb7d79cc4ef9339407ec137b8a13a927394206783f5548d2b2e1bed597ca1af4b00d5e19d632871ead4 + languageName: node + linkType: hard + +"workbox-cacheable-response@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-cacheable-response@npm:6.5.4" + dependencies: + workbox-core: 6.5.4 + checksum: 5a464881f254b6ebea1055412e4b90861e9d987ed7167b9fcdebfbd79af72d678979a2dcf0c62a3db2afcabb320c23ba691db25031ac057e3badd6cc4f80a6e9 + languageName: node + linkType: hard + +"workbox-core@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-core@npm:6.5.4" + checksum: dac894674219035316e3354955343cf7eec4b014a58712cb6139c3c97e4b7c40265403b15d339f8a318304193340485d9a0757255c88cb23d2e6a59b55224a41 + languageName: node + linkType: hard + +"workbox-expiration@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-expiration@npm:6.5.4" + dependencies: + idb: ^7.0.1 + workbox-core: 6.5.4 + checksum: f8cf8e694e44d5b8ac2b25f224c716c2d277b54775d6b92323aeb74de36adee80fb46a0140431d6a1adc36b33791f6737eaaf54996ea995def70aff9c1d64492 + languageName: node + linkType: hard + +"workbox-google-analytics@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-google-analytics@npm:6.5.4" + dependencies: + workbox-background-sync: 6.5.4 + workbox-core: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + checksum: 88640d5efb12d697eb1cd8d04caa9c23b5a48308138aeea24d5726b02b47ece6efe0bd07154d1243f6322b918ed55bf05ed927298fc3bb755545627e8644019f + languageName: node + linkType: hard + +"workbox-navigation-preload@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-navigation-preload@npm:6.5.4" + dependencies: + workbox-core: 6.5.4 + checksum: ec9bda6ef4e7031fb3544d9ca187588b1c53b89e16ab2efa55ec6a179b1cce0638d23612d70e71a0767489cb5a3edfc168713285157b832f64da16ab6d195f5e + languageName: node + linkType: hard + +"workbox-precaching@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-precaching@npm:6.5.4" + dependencies: + workbox-core: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + checksum: 25913c0adc8d48827e53677593e01e7ce9daa9cd8b340c36eb013dbbf617ab1ba5789612309c3e94d297a30afcb86688b0f6f21757dd2832a67df1fef0ef3e61 + languageName: node + linkType: hard + +"workbox-range-requests@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-range-requests@npm:6.5.4" + dependencies: + workbox-core: 6.5.4 + checksum: 603b1379d3222666e36bda4fc5094d0e13ea3d5b7eb43bc4f8ab3711c525a3f0e65bd0a90fc8a1aa9af1a9a363f25e8c863f2caabbbf21e056f9492395474d47 + languageName: node + linkType: hard + +"workbox-recipes@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-recipes@npm:6.5.4" + dependencies: + workbox-cacheable-response: 6.5.4 + workbox-core: 6.5.4 + workbox-expiration: 6.5.4 + workbox-precaching: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + checksum: 541efdb3f9f6cea485eb3938d5147b697b8ede0eb002ccdcec26ed132c35b9a0b1677d44b9f66f05ae2788bb59892002eb81c2a151788a12831292a393c9b08c + languageName: node + linkType: hard + +"workbox-routing@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-routing@npm:6.5.4" + dependencies: + workbox-core: 6.5.4 + checksum: a6b75c287408b1db457bbc8971bc4edb96c1a89141ecbd2ad241c7142d230bae31d7257b267a1a9d2a608d72fc8167d65af114d8e3ac0b4744785110408a1710 + languageName: node + linkType: hard + +"workbox-strategies@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-strategies@npm:6.5.4" + dependencies: + workbox-core: 6.5.4 + checksum: a57b69ae7fc14f172d4b9d5e2835ce36d345a8bac149cf6b8bafb60c1fd20fc46f511ef7b1322c0474628947875251f43d3331f8627fd6950f1c3ec262f5c98c + languageName: node + linkType: hard + +"workbox-streams@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-streams@npm:6.5.4" + dependencies: + workbox-core: 6.5.4 + workbox-routing: 6.5.4 + checksum: d69ad8aad0e6aefb660aa9e5ac32b80abd5fe145b8bc878d3419873dc48702b61f9de49130a2e0e1e5f51998f53df27bc4c05ae29597b45fc8ad6bb424fceaea + languageName: node + linkType: hard + +"workbox-sw@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-sw@npm:6.5.4" + checksum: 2d276e8436ab98d8edd94683f4f5bd62aff97403499a8489132f2bb724812cdecfe8fd0d826116737c2a675eacdccc1c01d963e7bb451ee87b1af60ae39ed816 + languageName: node + linkType: hard + +"workbox-webpack-plugin@npm:^6.5.1": + version: 6.5.4 + resolution: "workbox-webpack-plugin@npm:6.5.4" + dependencies: + fast-json-stable-stringify: ^2.1.0 + pretty-bytes: ^5.4.1 + upath: ^1.2.0 + webpack-sources: ^1.4.3 + workbox-build: 6.5.4 + peerDependencies: + webpack: ^4.4.0 || ^5.9.0 + checksum: f0faee823267386b3b7643b2e281beb576f64456c573d6f01a4da8a9295c8a6008a7b46d60d09d5271f61cdb8eaf67a2b63f92e8c994497f1535ccfe69a70457 + languageName: node + linkType: hard + +"workbox-window@npm:6.5.4": + version: 6.5.4 + resolution: "workbox-window@npm:6.5.4" + dependencies: + "@types/trusted-types": ^2.0.2 + workbox-core: 6.5.4 + checksum: fd5ed15563025d76fe70a6d62413e380de160fd8c033c22d6827e5256edaac7362b2955ce42f2188cde100f881727840be78d057c533b8dd3696c6a7c7b36031 + languageName: node + linkType: hard + +"worker-farm@npm:^1.6.0, worker-farm@npm:^1.7.0": + version: 1.7.0 + resolution: "worker-farm@npm:1.7.0" + dependencies: + errno: ~0.1.7 + checksum: 069a032f9198a07273a7608dc0c23d7288c1c25256b66008e1ae95838cda6fa2c7aefb3b7ba760f975c8d18120ca54eb193afb66d7237b2a05e5da12c1c961f7 + languageName: node + linkType: hard + +"worker-rpc@npm:^0.1.0": + version: 0.1.1 + resolution: "worker-rpc@npm:0.1.1" + dependencies: + microevent.ts: ~0.1.1 + checksum: 986406dbed4a2fd25d21e05e5a16a2db5051735df4011848366bb9488ecf19f44b43a90072171e509580fafd0565a7759543966496b8c18322efa5440dea1e6c + languageName: node + linkType: hard + +"wrap-ansi@npm:^5.1.0": + version: 5.1.0 + resolution: "wrap-ansi@npm:5.1.0" + dependencies: + ansi-styles: ^3.2.0 + string-width: ^3.0.0 + strip-ansi: ^5.0.0 + checksum: fcd0b39b7453df512f2fe8c714a1c1b147fe3e6a4b5a2e4de6cadc3af47212f335eceaffe588e98322d6345e72672137e2c0b834d8a662e73a32296c1c8216bb + languageName: node + linkType: hard + +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: baad244e6e33335ea24e86e51868fe6823626e3a3c88d9a6674642afff1d34d9a154c917e74af8d845fd25d170c4ea9cf69a47133c3f3656e1252b3d462d9f6c + languageName: node + linkType: hard + +"wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 + languageName: node + linkType: hard + +"write-file-atomic@npm:^2.0.0, write-file-atomic@npm:^2.3.0, write-file-atomic@npm:^2.4.3": + version: 2.4.3 + resolution: "write-file-atomic@npm:2.4.3" + dependencies: + graceful-fs: ^4.1.11 + imurmurhash: ^0.1.4 + signal-exit: ^3.0.2 + checksum: 8cb4bba0c1ab814a9b127844da0db4fb8c5e06ddbe6317b8b319377c73b283673036c8b9360120062898508b9428d81611cf7fa97584504a00bc179b2a580b92 + languageName: node + linkType: hard + +"write-file-atomic@npm:^3.0.0": + version: 3.0.3 + resolution: "write-file-atomic@npm:3.0.3" + dependencies: + imurmurhash: ^0.1.4 + is-typedarray: ^1.0.0 + signal-exit: ^3.0.2 + typedarray-to-buffer: ^3.1.5 + checksum: 7fb67affd811c7a1221bed0c905c26e28f0041e138fb19ccf02db57a0ef93ea69220959af3906b920f9b0411d1914474cdd90b93a96e5cd9e8368d9777caac0e + languageName: node + linkType: hard + +"write-file-atomic@npm:^4.0.1": + version: 4.0.2 + resolution: "write-file-atomic@npm:4.0.2" + dependencies: + imurmurhash: ^0.1.4 + signal-exit: ^3.0.7 + checksum: a2c282c95ef5d8e1c27b335ae897b5eca00e85590d92a3fd69a437919b7b93ff36a69ea04145da55829d2164e724bc62202cdb5f4b208b425aba0807889375c7 + languageName: node + linkType: hard + +"ws@npm:^7.3.1": + version: 7.5.9 + resolution: "ws@npm:7.5.9" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: aec4ef4eb65821a7dde7b44790f8699cfafb7978c9b080f6d7a98a7f8fc0ce674c027073a78574c94786ba7112cc90fa2cc94fc224ceba4d4b1030cff9662494 + languageName: node + linkType: hard + +"ws@npm:^8.2.3, ws@npm:^8.4.2": + version: 8.12.0 + resolution: "ws@npm:8.12.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 2a84d769be015f3644a99a33c1b4c1c268b97315a8387067c242f26ab7ac1f655640220c23ddcbd2f7911649cd00478aaafbb4dff073f0b75f3531ebabd7cced + languageName: node + linkType: hard + +"ws@npm:~8.2.3": + version: 8.2.3 + resolution: "ws@npm:8.2.3" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 5ef0f81cc5b8776fb5dd5504c83b4f49be5aa610f9319ff774158bba7db495127e69763d73085288223061e7a5d104d022e2e264346b36b046322f50057e7945 + languageName: node + linkType: hard + +"x-default-browser@npm:^0.4.0": + version: 0.4.0 + resolution: "x-default-browser@npm:0.4.0" + dependencies: + default-browser-id: ^1.0.4 + dependenciesMeta: + default-browser-id: + optional: true + bin: + x-default-browser: bin/x-default-browser.js + checksum: a19e42ffeab19560ea05a423561f5b3b82bb3a5878dc932cfd0847fadc5890b8b685d6b39e2356c8304b3943f5a7120ba4b233365d686ff8f9bf2499ce11f052 + languageName: node + linkType: hard + +"xdg-basedir@npm:^3.0.0": + version: 3.0.0 + resolution: "xdg-basedir@npm:3.0.0" + checksum: c3be36400d8a69c9154ce6ccff98832dae0d04f8bacda806f609d3955beb23dc7c9dde724438b81e6128bf253d440a2bfe0239dd37d70333ab625c4e170b77b2 + languageName: node + linkType: hard + +"xml2js@npm:0.4.19": + version: 0.4.19 + resolution: "xml2js@npm:0.4.19" + dependencies: + sax: ">=0.6.0" + xmlbuilder: ~9.0.1 + checksum: a50178608fae952ddbdd30c9fde61a2a3b9a42edacacd8059e69b6177304e2f3362e214cd324b7555d3087ed64234e59bb70f75c4699231c6840c4c60a72c2d2 + languageName: node + linkType: hard + +"xml2js@npm:^0.4.15": + version: 0.4.23 + resolution: "xml2js@npm:0.4.23" + dependencies: + sax: ">=0.6.0" + xmlbuilder: ~11.0.0 + checksum: a3f41c9afc46d5bd0bea4070e5108777b605fd5ce2ebb978a68fd4c75513978ad5939f8135664ffea6f1adb342f391b1ba1584ed7955123b036e9ab8a1d26566 + languageName: node + linkType: hard + +"xml@npm:^1.0.0": + version: 1.0.1 + resolution: "xml@npm:1.0.1" + checksum: 04bcc9b8b5e7b49392072fbd9c6b0f0958bd8e8f8606fee460318e43991349a68cbc5384038d179ff15aef7d222285f69ca0f067f53d071084eb14c7fdb30411 + languageName: node + linkType: hard + +"xmlbuilder@npm:^15.1.1": + version: 15.1.1 + resolution: "xmlbuilder@npm:15.1.1" + checksum: 665266a8916498ff8d82b3d46d3993913477a254b98149ff7cff060d9b7cc0db7cf5a3dae99aed92355254a808c0e2e3ec74ad1b04aa1061bdb8dfbea26c18b8 + languageName: node + linkType: hard + +"xmlbuilder@npm:~11.0.0": + version: 11.0.1 + resolution: "xmlbuilder@npm:11.0.1" + checksum: 74b979f89a0a129926bc786b913459bdbcefa809afaa551c5ab83f89b1915bdaea14c11c759284bb9b931e3b53004dbc2181e21d3ca9553eeb0b2a7b4e40c35b + languageName: node + linkType: hard + +"xmlbuilder@npm:~9.0.1": + version: 9.0.7 + resolution: "xmlbuilder@npm:9.0.7" + checksum: aa3c644a13e561abd50e4971ab6963261de703cc0405994777db9129c40d76dba9c0a2c6fa04a7de474a8428f7b329e6f85fcf84990f9cb4ceb2c345a57a4eef + languageName: node + linkType: hard + +"xmlhttprequest-ssl@npm:~2.0.0": + version: 2.0.0 + resolution: "xmlhttprequest-ssl@npm:2.0.0" + checksum: b64ab371459bd5e3a4827e3c7535759047d285fd310aea6fd028973d547133f3be0d473c1fdae9f14d89bf509267759198ae1fbe89802079a7e217ddd990d734 + languageName: node + linkType: hard + +"xtend@npm:^4.0.0, xtend@npm:^4.0.1, xtend@npm:^4.0.2, xtend@npm:~4.0.1": + version: 4.0.2 + resolution: "xtend@npm:4.0.2" + checksum: 366ae4783eec6100f8a02dff02ac907bf29f9a00b82ac0264b4d8b832ead18306797e283cf19de776538babfdcb2101375ec5646b59f08c52128ac4ab812ed0e + languageName: node + linkType: hard + +"y18n@npm:^4.0.0": + version: 4.0.3 + resolution: "y18n@npm:4.0.3" + checksum: 308a2efd7cc296ab2c0f3b9284fd4827be01cfeb647b3ba18230e3a416eb1bc887ac050de9f8c4fd9e7856b2e8246e05d190b53c96c5ad8d8cb56dffb6f81024 + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 + languageName: node + linkType: hard + +"yallist@npm:^2.1.2": + version: 2.1.2 + resolution: "yallist@npm:2.1.2" + checksum: 0b9e25aa00adf19e01d2bcd4b208aee2b0db643d9927131797b7af5ff69480fc80f1c3db738cbf3946f0bddf39d8f2d0a5709c644fd42d4aa3a4e6e786c087b5 + languageName: node + linkType: hard + +"yallist@npm:^3.0.0, yallist@npm:^3.0.2, yallist@npm:^3.1.1": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yaml@npm:^1.10.0, yaml@npm:^1.10.2, yaml@npm:^1.7.2": + version: 1.10.2 + resolution: "yaml@npm:1.10.2" + checksum: 5c28b9eb7adc46544f28d9a8d20c5b3cb1215a886609a2fd41f51628d8aaa5878ccd628b755dbcd29f6bb4921bd04ffbc6dcc370689bb96e594e2f9813d2605f + languageName: node + linkType: hard + +"yargs-parser@npm:^15.0.1": + version: 15.0.3 + resolution: "yargs-parser@npm:15.0.3" + dependencies: + camelcase: ^5.0.0 + decamelize: ^1.2.0 + checksum: 396bba6fd8cbe568ea64c85583c6814d886719980ebadce03f34dd5f6b339e0260a364ab65a3e3b97db93ba2ecf0f544aac13b389f682b16170fabbfd2a20b1b + languageName: node + linkType: hard + +"yargs-parser@npm:^20.2.2, yargs-parser@npm:^20.2.9": + version: 20.2.9 + resolution: "yargs-parser@npm:20.2.9" + checksum: 0685a8e58bbfb57fab6aefe03c6da904a59769bd803a722bb098bd5b0f29d274a1357762c7258fb487512811b8063fb5d2824a3415a0a4540598335b3b086c72 + languageName: node + linkType: hard + +"yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard + +"yargs@npm:17, yargs@npm:^17.3.1": + version: 17.6.2 + resolution: "yargs@npm:17.6.2" + 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 + checksum: dd5c89aa8186d2a18625b26b68beb635df648617089135e9661107a92561056427bbd41dbfa228db5a7d968ea1043d96c036c2eb978acf7b61a0ae48bf3be206 + languageName: node + linkType: hard + +"yargs@npm:^14.2.3": + version: 14.2.3 + resolution: "yargs@npm:14.2.3" + dependencies: + cliui: ^5.0.0 + decamelize: ^1.2.0 + find-up: ^3.0.0 + get-caller-file: ^2.0.1 + require-directory: ^2.1.1 + require-main-filename: ^2.0.0 + set-blocking: ^2.0.0 + string-width: ^3.0.0 + which-module: ^2.0.0 + y18n: ^4.0.0 + yargs-parser: ^15.0.1 + checksum: dceba0f167f182dfea9ab7e8924e3c0eff7197746e2c2776bf1ca5a342ccdf27d8ee995616670f558388502b2a19b9d809f88a4192da67cc0a83f01acc2f7c74 + languageName: node + linkType: hard + +"yargs@npm:^16.2.0": + version: 16.2.0 + resolution: "yargs@npm:16.2.0" + dependencies: + cliui: ^7.0.2 + escalade: ^3.1.1 + get-caller-file: ^2.0.5 + require-directory: ^2.1.1 + string-width: ^4.2.0 + y18n: ^5.0.5 + yargs-parser: ^20.2.2 + checksum: b1dbfefa679848442454b60053a6c95d62f2d2e21dd28def92b647587f415969173c6e99a0f3bab4f1b67ee8283bf735ebe3544013f09491186ba9e8a9a2b651 + languageName: node + linkType: hard + +"yauzl@npm:^2.10.0": + version: 2.10.0 + resolution: "yauzl@npm:2.10.0" + dependencies: + buffer-crc32: ~0.2.3 + fd-slicer: ~1.1.0 + checksum: f265002af7541b9ec3589a27f5fb8f11cf348b53cc15e2751272e3c062cd73f3e715bc72d43257de71bbaecae446c3f1b14af7559e8ab0261625375541816422 + languageName: node + linkType: hard + +"yn@npm:3.1.1": + version: 3.1.1 + resolution: "yn@npm:3.1.1" + checksum: 0732468dd7622ed8a274f640f191f3eaf1f39d5349a1b72836df484998d7d9807fbea094e2f5486d6b0cd2414aad5775972df0e68f8604db89a239f0f4bf7443 + languageName: node + linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f + languageName: node + linkType: hard + +"zwitch@npm:^1.0.0": + version: 1.0.5 + resolution: "zwitch@npm:1.0.5" + checksum: 26dc7d32e5596824b565db1da9650d00d32659c1211195bef50c25c60820f9c942aa7abefe678fc1ed0b97c1755036ac1bde5f97881d7d0e73e04e02aca56957 + languageName: node + linkType: hard diff --git a/mobs/messages.rb b/mobs/messages.rb index 60bde0626..ef36ebfa7 100644 --- a/mobs/messages.rb +++ b/mobs/messages.rb @@ -1,27 +1,4 @@ -# Special one for Batch Metadata. Message id could define the version - -# Depricated since tracker 3.6.0 in favor of BatchMetadata -message 80, 'BatchMeta', :replayer => false, :tracker => false do - uint 'PageNo' - uint 'FirstIndex' - int 'Timestamp' -end - -# since tracker 3.6.0 TODO: for webworker only -message 81, 'BatchMetadata', :replayer => false do - uint 'Version' - uint 'PageNo' - uint 'FirstIndex' - int 'Timestamp' - string 'Location' -end - -# since tracker 3.6.0 -message 82, 'PartitionedMessage', :replayer => false do - uint 'PartNo' - uint 'PartTotal' -end - +# OpenReplay messages definition message 0, 'Timestamp' do uint 'Timestamp' @@ -46,6 +23,8 @@ message 1, 'SessionStart', :tracker => false, :replayer => false do end ## message 2, 'CreateDocument', do # end + +# DEPRECATED; backend only (TODO: remove in the next release) message 3, 'SessionEndDeprecated', :tracker => false, :replayer => false do uint 'Timestamp' end @@ -62,7 +41,7 @@ message 6, 'SetViewportScroll' do int 'X' int 'Y' end -# (should be) Depricated sinse tracker ?.?.? in favor of CreateDocument(id=2) +# (should be) Deprecated sinse tracker ?.?.? in favor of CreateDocument(id=2) # in order to use Document as a default root node instead of the documentElement message 7, 'CreateDocument' do end @@ -125,7 +104,16 @@ message 20, 'MouseMove' do uint 'X' uint 'Y' end -# 21 +message 21, 'NetworkRequest', :replayer => :devtools do + string 'Type' # fetch/xhr/anythingElse(axios,gql,fonts,image?) + string 'Method' + string 'URL' + string 'Request' + string 'Response' + uint 'Status' + uint 'Timestamp' + uint 'Duration' +end message 22, 'ConsoleLog', :replayer => :devtools do string 'Level' string 'Value' @@ -146,8 +134,8 @@ message 24, 'PageRenderTiming', :replayer => false do uint 'VisuallyComplete' uint 'TimeToInteractive' end -# deprecated since 4.1.6 / 1.8.2 in favor of #78 -message 25, 'JSExceptionDeprecated', :replayer => false do +# DEPRECATED since 4.1.6 / 1.8.2 in favor of #78 +message 25, 'JSExceptionDeprecated', :replayer => false, :tracker => false do string 'Name' string 'Message' string 'Payload' @@ -159,7 +147,7 @@ message 26, 'IntegrationEvent', :tracker => false, :replayer => false do string 'Message' string 'Payload' end -message 27, 'RawCustomEvent', :replayer => false do +message 27, 'CustomEvent', :replayer => false do string 'Name' string 'Payload' end @@ -206,14 +194,7 @@ message 33, 'ClickEvent', :tracker => false, :replayer => false do string 'Label' string 'Selector' end -# message 34, 'ErrorEvent', :tracker => false, :replayer => false do -# uint 'MessageID' -# uint 'Timestamp' -# string 'Source' -# string 'Name' -# string 'Message' -# string 'Payload' -# end +## 34 message 35, 'ResourceEvent', :tracker => false, :replayer => false do uint 'MessageID' uint 'Timestamp' @@ -228,24 +209,21 @@ message 35, 'ResourceEvent', :tracker => false, :replayer => false do string 'Method' uint 'Status' end -message 36, 'CustomEvent', :tracker => false, :replayer => false do - uint 'MessageID' - uint 'Timestamp' - string 'Name' - string 'Payload' -end -# deprecated since 4.0.2 in favor of AdoptedSSInsertRule + AdoptedSSAddOwner +#36 + +# DEPRECATED since 4.0.2 in favor of AdoptedSSInsertRule + AdoptedSSAddOwner message 37, 'CSSInsertRule' do uint 'ID' string 'Rule' uint 'Index' end -# deprecated since 4.0.2 +# DEPRECATED since 4.0.2 message 38, 'CSSDeleteRule' do uint 'ID' uint 'Index' end +# DEPRECATED since 4.1.10 in favor of NetworkRequest message 39, 'Fetch', :replayer => :devtools do string 'Method' string 'URL' @@ -265,15 +243,10 @@ message 41, 'OTable', :replayer => :devtools do string 'Key' string 'Value' end -# Do we use that? message 42, 'StateAction', :replayer => false do string 'Type' end -message 43, 'StateActionEvent', :tracker => false, :replayer => false do - uint 'MessageID' - uint 'Timestamp' - string 'Type' -end +## 43 message 44, 'Redux', :replayer => :devtools do string 'Action' string 'State' @@ -304,29 +277,24 @@ message 49, 'PerformanceTrack' do #, :replayer => :devtools --> requires player uint 'TotalJSHeapSize' uint 'UsedJSHeapSize' end -# next 2 should be removed after refactoring backend/pkg/handlers/custom/eventMapper.go (move "wrapping" logic to pg connector insertion) -message 50, 'GraphQLEvent', :tracker => false, :replayer => false do - uint 'MessageID' - uint 'Timestamp' - string 'OperationKind' - string 'OperationName' - string 'Variables' - string 'Response' +# since 4.1.9 +message 50, "StringDict" do + uint "Key" + string "Value" end -message 51, 'FetchEvent', :tracker => false, :replayer => false do - uint 'MessageID' - uint 'Timestamp' - string 'Method' - string 'URL' - string 'Request' - string 'Response' - uint 'Status' - uint 'Duration' +# since 4.1.9 +message 51, "SetNodeAttributeDict" do + uint 'ID' + uint 'NameKey' + uint 'ValueKey' end + +## 50,51 +# Doesn't work properly. TODO: Make proper detections in tracker message 52, 'DOMDrop', :tracker => false, :replayer => false do uint 'Timestamp' end -message 53, 'ResourceTiming', :replayer => false do +message 53, 'ResourceTiming', :replayer => :devtools do uint 'Timestamp' uint 'Duration' uint 'TTFB' @@ -372,7 +340,7 @@ message 58, 'SetNodeFocus' do int 'ID' end -#Depricated (since 3.0.?) +#DEPRECATED (since 3.0.?) message 59, 'LongTask' do uint 'Timestamp' uint 'Duration' @@ -394,7 +362,8 @@ message 61, 'SetCSSDataURLBased' do string 'Data' string 'BaseURL' end -message 62, 'IssueEvent', :replayer => false, :tracker => false do +# DEPRECATED; backend only (TODO: remove in the next release) +message 62, 'IssueEventDeprecated', :replayer => false, :tracker => false do uint 'MessageID' uint 'Timestamp' string 'Type' @@ -468,18 +437,55 @@ message 77, 'AdoptedSSRemoveOwner' do uint 'SheetID' uint 'ID' end -message 79, 'Zustand', :replayer => :devtools do - string 'Mutation' - string 'State' -end message 78, 'JSException', :replayer => false do string 'Name' string 'Message' string 'Payload' string 'Metadata' end +message 79, 'Zustand', :replayer => :devtools do + string 'Mutation' + string 'State' +end +# 80 -- 90 reserved + +# Special one for Batch Metadata. Message id could define the version + +# DEPRECATED since tracker 3.6.0 in favor of BatchMetadata +message 80, 'BatchMeta', :replayer => false, :tracker => false do + uint 'PageNo' + uint 'FirstIndex' + int 'Timestamp' +end + +# since tracker 3.6.0 TODO: for webworker only +message 81, 'BatchMetadata', :replayer => false do + uint 'Version' + uint 'PageNo' + uint 'FirstIndex' + int 'Timestamp' + string 'Location' +end + +# since tracker 3.6.0 +message 82, 'PartitionedMessage', :replayer => false do + uint 'PartNo' + uint 'PartTotal' +end + + +## Backend-only +message 125, 'IssueEvent', :replayer => false, :tracker => false do + uint 'MessageID' + uint 'Timestamp' + string 'Type' + string 'ContextString' + string 'Context' + string 'Payload' + string 'URL' +end message 126, 'SessionEnd', :tracker => false, :replayer => false do uint 'Timestamp' string 'EncryptionKey' @@ -488,6 +494,3 @@ message 127, 'SessionSearch', :tracker => false, :replayer => false do uint 'Timestamp' uint 'Partition' end - - -# 80 -- 90 reserved diff --git a/mobs/primitives/primitives.go b/mobs/primitives/primitives.go index 939fbda9c..3e47a3943 100644 --- a/mobs/primitives/primitives.go +++ b/mobs/primitives/primitives.go @@ -2,38 +2,21 @@ package messages import ( "errors" + "fmt" "io" ) +var ( + one = []byte{0} + three = []byte{0, 0, 0} +) + func ReadByte(reader io.Reader) (byte, error) { - p := make([]byte, 1) - _, err := io.ReadFull(reader, p) + _, err := io.ReadFull(reader, one) if err != nil { return 0, err } - return p[0], nil -} - -func SkipBytes(reader io.ReadSeeker) error { - n, err := ReadUint(reader) - if err != nil { - return err - } - _, err = reader.Seek(int64(n), io.SeekCurrent) - return err -} - -func ReadData(reader io.Reader) ([]byte, error) { - n, err := ReadUint(reader) - if err != nil { - return nil, err - } - p := make([]byte, n) - _, err = io.ReadFull(reader, p) - if err != nil { - return nil, err - } - return p, nil + return one[0], nil } func ReadUint(reader io.Reader) (uint64, error) { @@ -47,7 +30,7 @@ func ReadUint(reader io.Reader) (uint64, error) { } if b < 0x80 { if i > 9 || i == 9 && b > 1 { - return x, errors.New("overflow") + return x, errors.New("uint overflow") } return x | uint64(b)<<s, nil } @@ -57,6 +40,16 @@ func ReadUint(reader io.Reader) (uint64, error) { } } +func WriteUint(v uint64, buf []byte, p int) int { + for v >= 0x80 { + buf[p] = byte(v) | 0x80 + v >>= 7 + p++ + } + buf[p] = byte(v) + return p + 1 +} + func ReadInt(reader io.Reader) (int64, error) { ux, err := ReadUint(reader) x := int64(ux >> 1) @@ -69,6 +62,14 @@ func ReadInt(reader io.Reader) (int64, error) { return x, err } +func WriteInt(v int64, buf []byte, p int) int { + uv := uint64(v) << 1 + if v < 0 { + uv = ^uv + } + return WriteUint(uv, buf, p) +} + func ReadBoolean(reader io.Reader) (bool, error) { p := make([]byte, 1) _, err := io.ReadFull(reader, p) @@ -78,37 +79,6 @@ func ReadBoolean(reader io.Reader) (bool, error) { return p[0] == 1, nil } -func ReadString(reader io.Reader) (string, error) { - l, err := ReadUint(reader) - if err != nil { - return "", err - } - buf := make([]byte, l) - _, err = io.ReadFull(reader, buf) - if err != nil { - return "", err - } - return string(buf), nil -} - -func WriteUint(v uint64, buf []byte, p int) int { - for v >= 0x80 { - buf[p] = byte(v) | 0x80 - v >>= 7 - p++ - } - buf[p] = byte(v) - return p + 1 -} - -func WriteInt(v int64, buf []byte, p int) int { - uv := uint64(v) << 1 - if v < 0 { - uv = ^uv - } - return WriteUint(uv, buf, p) -} - func WriteBoolean(v bool, buf []byte, p int) int { if v { buf[p] = 1 @@ -118,7 +88,38 @@ func WriteBoolean(v bool, buf []byte, p int) int { return p + 1 } +func ReadString(reader io.Reader) (string, error) { + l, err := ReadUint(reader) + if err != nil { + return "", err + } + if l > 10e6 { + return "", errors.New("Too long string") + } + buf := make([]byte, l) + _, err = io.ReadFull(reader, buf) + if err != nil { + return "", err + } + return string(buf), nil +} + func WriteString(str string, buf []byte, p int) int { p = WriteUint(uint64(len(str)), buf, p) return p + copy(buf[p:], str) } + +func ReadSize(reader io.Reader) (uint64, error) { + n, err := io.ReadFull(reader, three) + if err != nil { + return 0, err + } + if n != 3 { + return 0, fmt.Errorf("read only %d of 3 size bytes", n) + } + var size uint64 + for i, b := range three { + size += uint64(b) << (8 * i) + } + return size, nil +} diff --git a/mobs/run.rb b/mobs/run.rb index 31adfcba1..8d481e3b3 100644 --- a/mobs/run.rb +++ b/mobs/run.rb @@ -7,7 +7,7 @@ class String self.sub('Id', 'ID').sub('Url', 'URL') end - # pascal_case + # PascalCase def pascal_case return self if self !~ /_/ && self =~ /[A-Z]+.*/ split('_').map{|e| e.capitalize}.join.upperize_abbreviations diff --git a/mobs/templates/backend~pkg~messages~messages.go.erb b/mobs/templates/backend~pkg~messages~messages.go.erb index 26a3ec4c0..f186470e8 100644 --- a/mobs/templates/backend~pkg~messages~messages.go.erb +++ b/mobs/templates/backend~pkg~messages~messages.go.erb @@ -1,12 +1,8 @@ // Auto-generated, do not edit package messages -import "encoding/binary" - -const ( -<% $messages.each do |msg| %> - Msg<%= msg.name %> = <%= msg.id %> -<% end %> +const (<% $messages.each do |msg| %> + Msg<%= msg.name %> = <%= msg.id %><% end %> ) <% $messages.each do |msg| %> @@ -25,17 +21,6 @@ func (msg *<%= msg.name %>) Encode() []byte { return buf[:p] } -func (msg *<%= msg.name %>) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - func (msg *<%= msg.name %>) Decode() Message { return msg } diff --git a/mobs/templates/backend~pkg~messages~read-message.go.erb b/mobs/templates/backend~pkg~messages~read-message.go.erb index b123f87a7..f871f1cea 100644 --- a/mobs/templates/backend~pkg~messages~read-message.go.erb +++ b/mobs/templates/backend~pkg~messages~read-message.go.erb @@ -3,28 +3,22 @@ package messages import ( "fmt" - "io" ) - <% $messages.each do |msg| %> -func Decode<%= msg.name %>(reader io.Reader) (Message, error) { +func Decode<%= msg.name %>(reader BytesReader) (Message, error) { var err error = nil msg := &<%= msg.name %>{} <%= msg.attributes.map { |attr| - " if msg.#{attr.name}, err = Read#{attr.type.to_s.pascal_case}(reader); err != nil { - return nil, err - }" }.join "\n" %> - return msg, err + " if msg.#{attr.name}, err = reader.Read#{attr.type.to_s.pascal_case}(); err != nil { + return nil, err + }" }.join "\n" %> + return msg, err } - <% end %> - -func ReadMessage(t uint64, reader io.Reader) (Message, error) { - switch t { -<% $messages.each do |msg| %> +func ReadMessage(t uint64, reader BytesReader) (Message, error) { + switch t {<% $messages.each do |msg| %> case <%= msg.id %>: - return Decode<%= msg.name %>(reader) -<% end %> + return Decode<%= msg.name %>(reader)<% end %> } return nil, fmt.Errorf("Unknown message code: %v", t) } diff --git a/mobs/templates/frontend~app~player~MessageDistributor~messages~tracker-legacy.ts.erb b/mobs/templates/frontend~app~player~MessageDistributor~messages~tracker-legacy.ts.erb deleted file mode 100644 index 28f1d64d7..000000000 --- a/mobs/templates/frontend~app~player~MessageDistributor~messages~tracker-legacy.ts.erb +++ /dev/null @@ -1,7 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ -// Auto-generated, do not edit - -export const TP_MAP = { -<%= $messages.select { |msg| msg.tracker || msg.replayer != false }.map { |msg| " #{msg.id}: \"#{msg.name.snake_case}\"," }.join "\n" %> -} as const diff --git a/mobs/templates/frontend~app~player~MessageDistributor~messages~RawMessageReader.ts.erb b/mobs/templates/frontend~app~player~web~messages~RawMessageReader.gen.ts.erb similarity index 87% rename from mobs/templates/frontend~app~player~MessageDistributor~messages~RawMessageReader.ts.erb rename to mobs/templates/frontend~app~player~web~messages~RawMessageReader.gen.ts.erb index 336fd957e..3c9268366 100644 --- a/mobs/templates/frontend~app~player~MessageDistributor~messages~RawMessageReader.ts.erb +++ b/mobs/templates/frontend~app~player~web~messages~RawMessageReader.gen.ts.erb @@ -2,7 +2,8 @@ /* eslint-disable */ import PrimitiveReader from './PrimitiveReader' -import type { RawMessage } from './raw' +import { MType } from './raw.gen' +import type { RawMessage } from './raw.gen' export default class RawMessageReader extends PrimitiveReader { @@ -22,7 +23,7 @@ export default class RawMessageReader extends PrimitiveReader { <%= msg.attributes.map { |attr| " const #{attr.name.camel_case} = this.read#{attr.type.to_s.pascal_case}(); if (#{attr.name.camel_case} === null) { return resetPointer() }" }.join "\n" %> return { - tp: "<%= msg.name.snake_case %>", + tp: MType.<%= msg.name.snake_case.pascal_case %>, <%= msg.attributes.map { |attr| " #{attr.name.camel_case}," }.join "\n" %> }; diff --git a/mobs/templates/frontend~app~player~web~messages~filters.gen.ts.erb b/mobs/templates/frontend~app~player~web~messages~filters.gen.ts.erb new file mode 100644 index 000000000..370d27798 --- /dev/null +++ b/mobs/templates/frontend~app~player~web~messages~filters.gen.ts.erb @@ -0,0 +1,9 @@ +// Auto-generated, do not edit +/* eslint-disable */ + +import { MType } from './raw.gen' + +const DOM_TYPES = [<%= $messages.select { |msg| msg.replayer == true }.map { |msg| "#{msg.id}" }.join "," %>] +export function isDOMType(t: MType) { + return DOM_TYPES.includes(t) +} \ No newline at end of file diff --git a/mobs/templates/frontend~app~player~MessageDistributor~messages~message.ts.erb b/mobs/templates/frontend~app~player~web~messages~message.gen.ts.erb similarity index 87% rename from mobs/templates/frontend~app~player~MessageDistributor~messages~message.ts.erb rename to mobs/templates/frontend~app~player~web~messages~message.gen.ts.erb index 666b0f740..40cd7a968 100644 --- a/mobs/templates/frontend~app~player~MessageDistributor~messages~message.ts.erb +++ b/mobs/templates/frontend~app~player~web~messages~message.gen.ts.erb @@ -2,10 +2,10 @@ /* eslint-disable */ import type { Timed } from './timed' -import type { RawMessage } from './raw' +import type { RawMessage } from './raw.gen' import type { <%= $messages.select { |msg| msg.replayer != false }.map { |msg| " Raw#{msg.name.snake_case.pascal_case}," }.join "\n" %> -} from './raw' +} from './raw.gen' export type Message = RawMessage & Timed diff --git a/mobs/templates/frontend~app~player~MessageDistributor~messages~raw.ts.erb b/mobs/templates/frontend~app~player~web~messages~raw.gen.ts.erb similarity index 66% rename from mobs/templates/frontend~app~player~MessageDistributor~messages~raw.ts.erb rename to mobs/templates/frontend~app~player~web~messages~raw.gen.ts.erb index 3099f39ff..fbfdc9492 100644 --- a/mobs/templates/frontend~app~player~MessageDistributor~messages~raw.ts.erb +++ b/mobs/templates/frontend~app~player~web~messages~raw.gen.ts.erb @@ -1,9 +1,16 @@ // Auto-generated, do not edit /* eslint-disable */ +export const enum MType { +<%= $messages.select { |msg| msg.replayer != false } + .map { |msg| " #{msg.name.snake_case.pascal_case} = #{msg.id}," } + .join "\n" +%> +} + <% $messages.select { |msg| msg.replayer != false }.each do |msg| %> export interface Raw<%= msg.name.snake_case.pascal_case %> { - tp: "<%= msg.name.snake_case %>", + tp: MType.<%= msg.name.snake_case.pascal_case %>, <%= msg.attributes.map { |attr| " #{attr.name.camel_case}: #{attr.type_js}," }.join "\n" %> } <% end %> diff --git a/mobs/templates/frontend~app~player~web~messages~tracker-legacy.gen.ts.erb b/mobs/templates/frontend~app~player~web~messages~tracker-legacy.gen.ts.erb new file mode 100644 index 000000000..37b9609b2 --- /dev/null +++ b/mobs/templates/frontend~app~player~web~messages~tracker-legacy.gen.ts.erb @@ -0,0 +1,7 @@ +// Auto-generated, do not edit + +import { MType } from './raw.gen' + +export const TP_MAP = { +<%= $messages.select { |msg| msg.replayer != false }.map { |msg| " #{msg.id}: MType.#{msg.name.snake_case.pascal_case}," }.join "\n" %> +} as const diff --git a/mobs/templates/frontend~app~player~MessageDistributor~messages~tracker.ts.erb b/mobs/templates/frontend~app~player~web~messages~tracker.gen.ts.erb similarity index 85% rename from mobs/templates/frontend~app~player~MessageDistributor~messages~tracker.ts.erb rename to mobs/templates/frontend~app~player~web~messages~tracker.gen.ts.erb index 8c5874de6..679ac9284 100644 --- a/mobs/templates/frontend~app~player~MessageDistributor~messages~tracker.ts.erb +++ b/mobs/templates/frontend~app~player~web~messages~tracker.gen.ts.erb @@ -1,7 +1,8 @@ // Auto-generated, do not edit /* eslint-disable */ -import type { RawMessage } from './raw' +import type { RawMessage } from './raw.gen' +import { MType } from './raw.gen' <% $messages.select { |msg| msg.tracker }.each do |msg| %> type Tr<%= msg.name %> = [ @@ -17,7 +18,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null { <% $messages.select { |msg| msg.replayer != false && msg.tracker }.each do |msg| %> case <%= msg.id %>: { return { - tp: "<%= msg.name.snake_case %>", + tp: MType.<%= msg.name.snake_case.pascal_case %>, <%= msg.attributes.map.with_index { |attr, i| "#{attr.name.camel_case}: tMsg[#{i+1}]," }.join "\n " %> } } diff --git a/mobs/templates/tracker~tracker~src~main~app~messages.gen.ts.erb b/mobs/templates/tracker~tracker~src~main~app~messages.gen.ts.erb index d4c132f8a..40aec9ce0 100644 --- a/mobs/templates/tracker~tracker~src~main~app~messages.gen.ts.erb +++ b/mobs/templates/tracker~tracker~src~main~app~messages.gen.ts.erb @@ -2,7 +2,7 @@ /* eslint-disable */ import * as Messages from '../../common/messages.gen.js' -export { default } from '../../common/messages.gen.js' +export { default, Type } from '../../common/messages.gen.js' <% $messages.select { |msg| msg.tracker }.each do |msg| %> export function <%= msg.name %>( diff --git a/mobs/templates/tracker~tracker~src~webworker~MessageEncoder.gen.ts.erb b/mobs/templates/tracker~tracker~src~webworker~MessageEncoder.gen.ts.erb index 503865443..337fc914e 100644 --- a/mobs/templates/tracker~tracker~src~webworker~MessageEncoder.gen.ts.erb +++ b/mobs/templates/tracker~tracker~src~webworker~MessageEncoder.gen.ts.erb @@ -9,11 +9,11 @@ import PrimitiveEncoder from './PrimitiveEncoder.js' export default class MessageEncoder extends PrimitiveEncoder { encode(msg: Message): boolean { switch(msg[0]) { - <% $messages.select { |msg| msg.tracker }.each do |msg| %> +<% $messages.select { |msg| msg.tracker }.each do |msg| %> case Messages.Type.<%= msg.name %>: - return <% if msg.attributes.size == 0 %> true <% else %> <%= msg.attributes.map.with_index { |attr, index| "this.#{attr.type}(msg[#{index+1}])" }.join " && " %> <% end %> + return <% if msg.attributes.size == 0 %> true <% else %> <%= msg.attributes.map.with_index { |attr, index| "this.#{attr.type}(msg[#{index+1}])" }.join " && " %><% end %> break - <% end %> +<% end %> } } diff --git a/peers/Dockerfile b/peers/Dockerfile index bfabf6bea..2c8254482 100644 --- a/peers/Dockerfile +++ b/peers/Dockerfile @@ -1,8 +1,11 @@ FROM node:18-alpine LABEL Maintainer="KRAIEM Taha Yassine<tahayk2@gmail.com>" +ARG GIT_SHA +LABEL GIT_SHA=$GIT_SHA RUN apk add --no-cache tini ARG envarg ENV PRIVATE_ENDPOINTS=false \ + GIT_SHA=$GIT_SHA \ ENTERPRISE_BUILD=${envarg} WORKDIR /work diff --git a/peers/build.sh b/peers/build.sh index e84b942ec..45cc97892 100644 --- a/peers/build.sh +++ b/peers/build.sh @@ -6,7 +6,8 @@ # Usage: IMAGE_TAG=latest DOCKER_REPO=myDockerHubID bash build.sh <ee> -git_sha1=${IMAGE_TAG:-$(git rev-parse HEAD)} +git_sha=$(git rev-parse --short HEAD) +image_tag=${IMAGE_TAG:-git_sha} check_prereq() { which docker || { echo "Docker not installed, please install docker." @@ -26,14 +27,17 @@ function build_api(){ [[ $1 == "ee" ]] && { cp -rf ../ee/peers/* ./ } - docker build -f ./Dockerfile -t ${DOCKER_REPO:-'local'}/peers:${git_sha1} . + docker build -f ./Dockerfile --build-arg GIT_SHA=$git_sha -t ${DOCKER_REPO:-'local'}/peers:${image_tag} . cd ../peers rm -rf ../${destination} [[ $PUSH_IMAGE -eq 1 ]] && { - docker push ${DOCKER_REPO:-'local'}/peers:${git_sha1} - docker tag ${DOCKER_REPO:-'local'}/peers:${git_sha1} ${DOCKER_REPO:-'local'}/peers:latest + docker push ${DOCKER_REPO:-'local'}/peers:${image_tag} + docker tag ${DOCKER_REPO:-'local'}/peers:${image_tag} ${DOCKER_REPO:-'local'}/peers:latest docker push ${DOCKER_REPO:-'local'}/peers:latest } + [[ $SIGN_IMAGE -eq 1 ]] && { + cosign sign --key $SIGN_KEY ${DOCKER_REPO:-'local'}/peers:${image_tag} + } echo "peer docker build complted" } diff --git a/peers/package-lock.json b/peers/package-lock.json index f0e0e864a..da9b72ca1 100644 --- a/peers/package-lock.json +++ b/peers/package-lock.json @@ -9,8 +9,83 @@ "version": "1.0.0", "license": "Elastic License 2.0 (ELv2)", "dependencies": { - "express": "^4.18.1", - "peer": "^v1.0.0-rc.4" + "express": "^4.18.2", + "peer": "^v1.0.0-rc.9" + } + }, + "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==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "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==", + "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.33", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", + "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" + }, + "node_modules/@types/node": { + "version": "18.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", + "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==" + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "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==" + }, + "node_modules/@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dependencies": { + "@types/node": "*" } }, "node_modules/accepts": { @@ -95,22 +170,17 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "engines": { - "node": ">=6" - } - }, "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "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==", "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/color-convert": { @@ -141,9 +211,9 @@ } }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { "node": ">= 0.6" } @@ -181,14 +251,6 @@ "ms": "2.0.0" } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -224,6 +286,14 @@ "node": ">= 0.8" } }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -295,18 +365,6 @@ "node": ">= 0.8" } }, - "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==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -337,9 +395,9 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -418,17 +476,6 @@ "node": ">=8" } }, - "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==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -502,9 +549,9 @@ } }, "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "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" } @@ -520,39 +567,6 @@ "node": ">= 0.8" } }, - "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==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "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==", - "engines": { - "node": ">=6" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -561,34 +575,32 @@ "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==", - "engines": { - "node": ">=8" - } - }, "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==" }, "node_modules/peer": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmjs.org/peer/-/peer-1.0.0-rc.4.tgz", - "integrity": "sha512-xaNIDm3yWR5m8cuijK7jEFAMOWqNJDGSVJ0+Y3qKW5XTNYsNWEdqtg/Btq9eznGxTTeqQZGNw/SxwyrCVdmmDg==", + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/peer/-/peer-1.0.0-rc.9.tgz", + "integrity": "sha512-wjt3fWMKxM/lH/1uD5Qs9qinQ1x/aa9br1eZEQuJ2wuBBQrjAcCT85MUuY9PYcyoW5ymyABsDKC3H/q9KmZ3PA==", "dependencies": { + "@types/express": "^4.17.3", + "@types/ws": "^7.2.3 || ^8.0.0", "cors": "^2.8.5", "express": "^4.17.1", - "ws": "^7.2.3", - "yargs": "^15.3.1" + "ws": "^7.2.3 || ^8.0.0", + "yargs": "^17.6.2" }, "bin": { - "peerjs": "bin/peerjs" + "peerjs": "dist/bin/peerjs.js" }, "engines": { "node": ">=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/peer" } }, "node_modules/proxy-addr": { @@ -647,11 +659,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -718,11 +725,6 @@ "node": ">= 0.8.0" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -817,34 +819,32 @@ "node": ">= 0.8" } }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==" - }, "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz", + "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -856,41 +856,36 @@ } }, "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } }, "node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", + "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=8" + "node": ">=12" } }, "node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "engines": { - "node": ">=6" + "node": ">=12" } } } diff --git a/peers/package.json b/peers/package.json index a38ad3343..400274ffc 100644 --- a/peers/package.json +++ b/peers/package.json @@ -18,7 +18,7 @@ }, "homepage": "https://github.com/openreplay/openreplay#readme", "dependencies": { - "express": "^4.18.1", - "peer": "^v1.0.0-rc.4" + "express": "^4.18.2", + "peer": "^v1.0.0-rc.9" } } diff --git a/scripts/helmcharts/build_deploy.sh b/scripts/helmcharts/build_deploy.sh index e5714a1b2..c922878d4 100644 --- a/scripts/helmcharts/build_deploy.sh +++ b/scripts/helmcharts/build_deploy.sh @@ -8,22 +8,31 @@ set -e # Removing local alpine:latest image docker rmi alpine || true +# Signing image +# cosign sign --key awskms:///alias/openreplay-container-sign image_url:tag +export SIGN_IMAGE=1 +export PUSH_IMAGE=1 +export AWS_DEFAULT_REGION="eu-central-1" +export SIGN_KEY="awskms:///alias/openreplay-container-sign" + echo $DOCKER_REPO -[[ -z DOCKER_REPO ]] && { +[[ -z $DOCKER_REPO ]] && { echo Set DOCKER_REPO="your docker registry" exit 1 } || { docker login $DOCKER_REPO cd ../../backend - PUSH_IMAGE=1 bash build.sh $@ + bash build.sh $@ cd ../utilities - PUSH_IMAGE=1 bash build.sh $@ + bash build.sh $@ cd ../peers - PUSH_IMAGE=1 bash build.sh $@ + bash build.sh $@ cd ../frontend - PUSH_IMAGE=1 bash build.sh $@ + bash build.sh $@ cd ../sourcemap-reader - PUSH_IMAGE=1 bash build.sh $@ + bash build.sh $@ cd ../api - PUSH_IMAGE=1 bash build.sh $@ + bash build.sh $@ + bash build_alerts.sh $@ + bash build_crons.sh $@ } diff --git a/scripts/helmcharts/build_deploy_parallel.sh b/scripts/helmcharts/build_deploy_parallel.sh index 76ab9a766..268811a34 100644 --- a/scripts/helmcharts/build_deploy_parallel.sh +++ b/scripts/helmcharts/build_deploy_parallel.sh @@ -8,6 +8,12 @@ set -e # Removing local alpine:latest image docker rmi alpine || true +# Signing image +# cosign sign --key awskms:///alias/openreplay-container-sign image_url:tag +export SIGN_IMAGE=1 +export PUSH_IMAGE=1 +export AWS_DEFAULT_REGION="eu-central-1" +export SIGN_KEY="awskms:///alias/openreplay-container-sign" echo $DOCKER_REPO [[ -z DOCKER_REPO ]] && { echo Set DOCKER_REPO="your docker registry" @@ -22,7 +28,9 @@ echo $DOCKER_REPO tmux split-window "cd ../../frontend && IMAGE_TAG=$IMAGE_TAG DOCKER_REPO=$DOCKER_REPO PUSH_IMAGE=1 bash build.sh $@" tmux select-layout tiled tmux split-window "cd ../../sourcemap-reader && IMAGE_TAG=$IMAGE_TAG DOCKER_REPO=$DOCKER_REPO PUSH_IMAGE=1 bash build.sh $@" - tmux split-window "cd ../../api && IMAGE_TAG=$IMAGE_TAG DOCKER_REPO=$DOCKER_REPO PUSH_IMAGE=1 bash build.sh $@" + tmux split-window "cd ../../api && IMAGE_TAG=$IMAGE_TAG DOCKER_REPO=$DOCKER_REPO PUSH_IMAGE=1 bash build.sh $@ + && IMAGE_TAG=$IMAGE_TAG DOCKER_REPO=$DOCKER_REPO PUSH_IMAGE=1 bash build_alerts.sh $@ + && IMAGE_TAG=$IMAGE_TAG DOCKER_REPO=$DOCKER_REPO PUSH_IMAGE=1 bash build_crons.sh $@" tmux select-layout tiled } diff --git a/scripts/helmcharts/databases/charts/clickhouse/templates/cron.yaml b/scripts/helmcharts/databases/charts/clickhouse/templates/cron.yaml index a6e489f1a..d0a8b05ca 100644 --- a/scripts/helmcharts/databases/charts/clickhouse/templates/cron.yaml +++ b/scripts/helmcharts/databases/charts/clickhouse/templates/cron.yaml @@ -1,5 +1,10 @@ # https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/ +{{- $kubeTargetVersion := .Capabilities.KubeVersion.GitVersion }} +{{- if semverCompare ">=1.22.0-0" $kubeTargetVersion }} +apiVersion: batch/v1 +{{- else }} apiVersion: batch/v1beta1 +{{- end }} kind: CronJob metadata: name: clickhouse-backup diff --git a/scripts/helmcharts/databases/charts/clickhouse/templates/statefulset.yaml b/scripts/helmcharts/databases/charts/clickhouse/templates/statefulset.yaml index 4c8e8c04a..5fa48fcf8 100644 --- a/scripts/helmcharts/databases/charts/clickhouse/templates/statefulset.yaml +++ b/scripts/helmcharts/databases/charts/clickhouse/templates/statefulset.yaml @@ -29,6 +29,10 @@ spec: containers: - name: {{ .Chart.Name }}-backup env: + - name: CLICKHOUSE_USER + value: "{{ .Values.username }}" + - name: CLICKHOUSE_PASSWORD + value: "{{ .Values.password }}" - name: API_LISTEN value: "0.0.0.0:7171" {{- range $key, $value := .Values.backupEnv }} @@ -49,6 +53,12 @@ spec: mountPath: /var/lib/clickhouse - name: {{ .Chart.Name }} env: + - name: CLICKHOUSE_USER + value: "{{ .Values.username }}" + - name: CLICKHOUSE_PASSWORD + value: "{{ .Values.password }}" + - name: CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT + value: "1" {{- range $key, $value := .Values.env }} - name: "{{ $key }}" value: "{{ $value }}" diff --git a/scripts/helmcharts/databases/charts/clickhouse/values.yaml b/scripts/helmcharts/databases/charts/clickhouse/values.yaml index cb7000070..e87942fb5 100644 --- a/scripts/helmcharts/databases/charts/clickhouse/values.yaml +++ b/scripts/helmcharts/databases/charts/clickhouse/values.yaml @@ -15,6 +15,9 @@ backupImage: pullPolicy: IfNotPresent tag: "latest" +username: default +password: "" + imagePullSecrets: [] nameOverride: "" fullnameOverride: "" diff --git a/scripts/helmcharts/databases/charts/kafka/.helmignore b/scripts/helmcharts/databases/charts/kafka/.helmignore old mode 100755 new mode 100644 diff --git a/scripts/helmcharts/databases/charts/kafka/Chart.lock b/scripts/helmcharts/databases/charts/kafka/Chart.lock new file mode 100644 index 000000000..39d54db13 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: zookeeper + repository: https://charts.bitnami.com/bitnami + version: 11.1.0 +- name: common + repository: https://charts.bitnami.com/bitnami + version: 2.2.2 +digest: sha256:da099b68bc1deabb4998fd87b4141440f26dba1a14801a507c402247830e75ee +generated: "2023-01-24T02:09:12.655952782Z" diff --git a/scripts/helmcharts/databases/charts/kafka/Chart.yaml b/scripts/helmcharts/databases/charts/kafka/Chart.yaml old mode 100755 new mode 100644 index 165e70d55..17192c343 --- a/scripts/helmcharts/databases/charts/kafka/Chart.yaml +++ b/scripts/helmcharts/databases/charts/kafka/Chart.yaml @@ -1,11 +1,23 @@ annotations: category: Infrastructure -apiVersion: v1 -appVersion: 2.6.0 -description: Apache Kafka is a distributed streaming platform. -engine: gotpl -home: https://github.com/bitnami/charts/tree/master/bitnami/kafka -icon: https://bitnami.com/assets/stacks/kafka/img/kafka-stack-110x117.png + licenses: Apache-2.0 +apiVersion: v2 +appVersion: 3.3.2 +dependencies: +- condition: zookeeper.enabled + name: zookeeper + repository: https://charts.bitnami.com/bitnami + version: 11.x.x +- name: common + repository: https://charts.bitnami.com/bitnami + tags: + - bitnami-common + version: 2.x.x +description: Apache Kafka is a distributed streaming platform designed to build real-time + pipelines and can be used as a message broker or as a replacement for a log aggregation + solution for big data applications. +home: https://github.com/bitnami/charts/tree/main/bitnami/kafka +icon: https://bitnami.com/assets/stacks/kafka/img/kafka-stack-220x234.png keywords: - kafka - zookeeper @@ -13,10 +25,10 @@ keywords: - producer - consumer maintainers: -- email: containers@bitnami.com - name: Bitnami +- name: Bitnami + url: https://github.com/bitnami/charts name: kafka sources: -- https://github.com/bitnami/bitnami-docker-kafka +- https://github.com/bitnami/containers/tree/main/bitnami/kafka - https://kafka.apache.org/ -version: 11.8.6 +version: 20.0.6 diff --git a/scripts/helmcharts/databases/charts/kafka/README.md b/scripts/helmcharts/databases/charts/kafka/README.md old mode 100755 new mode 100644 index 5584bd43d..9ee6f328b --- a/scripts/helmcharts/databases/charts/kafka/README.md +++ b/scripts/helmcharts/databases/charts/kafka/README.md @@ -1,24 +1,30 @@ -# Kafka +<!--- app-name: Apache Kafka --> -[Kafka](https://www.kafka.org/) is a distributed streaming platform used for building real-time data pipelines and streaming apps. It is horizontally scalable, fault-tolerant, wicked fast, and runs in production in thousands of companies. +# Apache Kafka packaged by Bitnami +Apache Kafka is a distributed streaming platform designed to build real-time pipelines and can be used as a message broker or as a replacement for a log aggregation solution for big data applications. + +[Overview of Apache Kafka](http://kafka.apache.org/) + +Trademarks: This software listing is packaged by Bitnami. The respective trademarks mentioned in the offering are owned by the respective companies, and use of them does not imply any affiliation or endorsement. + ## TL;DR ```console -helm repo add bitnami https://charts.bitnami.com/bitnami -helm install my-release bitnami/kafka +$ helm repo add my-repo https://charts.bitnami.com/bitnami +$ helm install my-release my-repo/kafka ``` ## Introduction -This chart bootstraps a [Kafka](https://github.com/bitnami/bitnami-docker-kafka) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. +This chart bootstraps a [Kafka](https://github.com/bitnami/containers/tree/main/bitnami/kafka) deployment on a [Kubernetes](https://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. -Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment and management of Helm Charts in clusters. This Helm chart has been tested on top of [Bitnami Kubernetes Production Runtime](https://kubeprod.io/) (BKPR). Deploy BKPR to get automated TLS certificates, logging and monitoring for your applications. +Bitnami charts can be used with [Kubeapps](https://kubeapps.dev/) for deployment and management of Helm Charts in clusters. ## Prerequisites -- Kubernetes 1.12+ -- Helm 2.12+ or Helm 3.0-beta3+ +- Kubernetes 1.19+ +- Helm 3.2.0+ - PV provisioner support in the underlying infrastructure ## Installing the Chart @@ -26,8 +32,8 @@ Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment To install the chart with the release name `my-release`: ```console -helm repo add bitnami https://charts.bitnami.com/bitnami -helm install my-release bitnami/kafka +$ helm repo add my-repo https://charts.bitnami.com/bitnami +$ helm install my-release my-repo/kafka ``` These commands deploy Kafka on the Kubernetes cluster in the default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. @@ -39,240 +45,460 @@ These commands deploy Kafka on the Kubernetes cluster in the default configurati To uninstall/delete the `my-release` deployment: ```console -helm delete my-release +$ helm delete my-release ``` The command removes all the Kubernetes components associated with the chart and deletes the release. ## Parameters -The following tables lists the configurable parameters of the Kafka chart and their default values per section/component: - ### Global parameters -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `global.imageRegistry` | Global Docker image registry | `nil` | -| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` (does not add image pull secrets to deployed pods) | -| `global.storageClass` | Global storage class for dynamic provisioning | `nil` | +| Name | Description | Value | +| ------------------------- | ----------------------------------------------- | ----- | +| `global.imageRegistry` | Global Docker image registry | `""` | +| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` | +| `global.storageClass` | Global StorageClass for Persistent Volume(s) | `""` | + ### Common parameters -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `nameOverride` | String to partially override kafka.fullname | `nil` | -| `fullnameOverride` | String to fully override kafka.fullname | `nil` | -| `clusterDomain` | Default Kubernetes cluster domain | `cluster.local` | -| `commonLabels` | Labels to add to all deployed objects | `{}` | -| `commonAnnotations` | Annotations to add to all deployed objects | `{}` | -| `extraDeploy` | Array of extra objects to deploy with the release | `nil` (evaluated as a template) | +| Name | Description | Value | +| ------------------------ | --------------------------------------------------------------------------------------- | --------------- | +| `kubeVersion` | Override Kubernetes version | `""` | +| `nameOverride` | String to partially override common.names.fullname | `""` | +| `fullnameOverride` | String to fully override common.names.fullname | `""` | +| `clusterDomain` | Default Kubernetes cluster domain | `cluster.local` | +| `commonLabels` | Labels to add to all deployed objects | `{}` | +| `commonAnnotations` | Annotations to add to all deployed objects | `{}` | +| `extraDeploy` | Array of extra objects to deploy with the release | `[]` | +| `diagnosticMode.enabled` | Enable diagnostic mode (all probes will be disabled and the command will be overridden) | `false` | +| `diagnosticMode.command` | Command to override all containers in the statefulset | `["sleep"]` | +| `diagnosticMode.args` | Args to override all containers in the statefulset | `["infinity"]` | + ### Kafka parameters -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `image.registry` | Kafka image registry | `docker.io` | -| `image.repository` | Kafka image name | `bitnami/kafka` | -| `image.tag` | Kafka image tag | `{TAG_NAME}` | -| `image.pullPolicy` | Kafka image pull policy | `IfNotPresent` | -| `image.pullSecrets` | Specify docker-registry secret names as an array | `[]` (does not add image pull secrets to deployed pods) | -| `image.debug` | Set to true if you would like to see extra information on logs | `false` | -| `config` | Configuration file for Kafka. Auto-generated based on other parameters when not specified | `nil` | -| `existingConfigmap` | Name of existing ConfigMap with Kafka configuration | `nil` | -| `log4j` | An optional log4j.properties file to overwrite the default of the Kafka brokers. | `nil` | -| `existingLog4jConfigMap` | The name of an existing ConfigMap containing a log4j.properties file. | `nil` | -| `heapOpts` | Kafka's Java Heap size | `-Xmx1024m -Xms1024m` | -| `deleteTopicEnable` | Switch to enable topic deletion or not | `false` | -| `autoCreateTopicsEnable` | Switch to enable auto creation of topics. Enabling auto creation of topics not recommended for production or similar environments | `false` | -| `logFlushIntervalMessages` | The number of messages to accept before forcing a flush of data to disk | `10000` | -| `logFlushIntervalMs` | The maximum amount of time a message can sit in a log before we force a flush | `1000` | -| `logRetentionBytes` | A size-based retention policy for logs | `_1073741824` | -| `logRetentionCheckIntervalMs` | The interval at which log segments are checked to see if they can be deleted | `300000` | -| `logRetentionHours` | The minimum age of a log file to be eligible for deletion due to age | `168` | -| `logSegmentBytes` | The maximum size of a log segment file. When this size is reached a new log segment will be created | `_1073741824` | -| `logsDirs` | A comma separated list of directories under which to store log files | `/bitnami/kafka/data` | -| `maxMessageBytes` | The largest record batch size allowed by Kafka | `1000012` | -| `defaultReplicationFactor` | Default replication factors for automatically created topics | `1` | -| `offsetsTopicReplicationFactor` | The replication factor for the offsets topic | `1` | -| `transactionStateLogReplicationFactor` | The replication factor for the transaction topic | `1` | -| `transactionStateLogMinIsr` | Overridden min.insync.replicas config for the transaction topic | `1` | -| `numIoThreads` | The number of threads doing disk I/O | `8` | -| `numNetworkThreads` | The number of threads handling network requests | `3` | -| `numPartitions` | The default number of log partitions per topic | `1` | -| `numRecoveryThreadsPerDataDir` | The number of threads per data directory to be used for log recovery at startup and flushing at shutdown | `1` | -| `socketReceiveBufferBytes` | The receive buffer (SO_RCVBUF) used by the socket server | `102400` | -| `socketRequestMaxBytes` | The maximum size of a request that the socket server will accept (protection against OOM) | `_104857600` | -| `socketSendBufferBytes` | The send buffer (SO_SNDBUF) used by the socket server | `102400` | -| `zookeeperConnectionTimeoutMs` | Timeout in ms for connecting to Zookeeper | `6000` | -| `extraEnvVars` | Extra environment variables to add to kafka pods | `[]` | -| `extraVolumes` | Extra volume(s) to add to Kafka statefulset | `[]` | -| `extraVolumeMounts` | Extra volumeMount(s) to add to Kafka containers | `[]` | -| `auth.clientProtocol` | Authentication protocol for communications with clients. Allowed protocols: `plaintext`, `tls`, `mtls`, `sasl` and `sasl_tls` | `plaintext` | -| `auth.interBrokerProtocol` | Authentication protocol for inter-broker communications. Allowed protocols: `plaintext`, `tls`, `mtls`, `sasl` and `sasl_tls` | `plaintext` | -| `auth.saslMechanisms` | SASL mechanisms when either `auth.interBrokerProtocol` or `auth.clientProtocol` are `sasl`. Allowed types: `plain`, `scram-sha-256`, `scram-sha-512` | `plain,scram-sha-256,scram-sha-512` | -| `auth.saslInterBrokerMechanism` | SASL mechanism to use as inter broker protocol, it must be included at `auth.saslMechanisms` | `plain` | -| `auth.jksSecret` | Name of the existing secret containing the truststore and one keystore per Kafka broker you have in the cluster | `nil` | -| `auth.jksPassword` | Password to access the JKS files when they are password-protected | `nil` | -| `auth.tlsEndpointIdentificationAlgorithm` | The endpoint identification algorithm to validate server hostname using server certificate | `https` | -| `auth.jaas.interBrokerUser` | Kafka inter broker communication user for SASL authentication | `admin` | -| `auth.jaas.interBrokerPassword` | Kafka inter broker communication password for SASL authentication | `nil` | -| `auth.jaas.zookeeperUser` | Kafka Zookeeper user for SASL authentication | `nil` | -| `auth.jaas.zookeeperPassword` | Kafka Zookeeper password for SASL authentication | `nil` | -| `auth.jaas.existingSecret` | Name of the existing secret containing credentials for brokerUser, interBrokerUser and zookeeperUser | `nil` | -| `auth.jaas.clientUsers` | List of Kafka client users to be created, separated by commas. This values will override `auth.jaas.clientUser` | `[]` | -| `auth.jaas.clientPasswords` | List of passwords for `auth.jaas.clientUsers`. It is mandatory to provide the passwords when using `auth.jaas.clientUsers` | `[]` | -| `listeners` | The address(es) the socket server listens on. Auto-calculated it's set to an empty array | `[]` | -| `advertisedListeners` | The address(es) (hostname:port) the broker will advertise to producers and consumers. Auto-calculated it's set to an empty array | `[]` | -| `listenerSecurityProtocolMap` | The protocol->listener mapping. Auto-calculated it's set to nil | `nil` | -| `allowPlaintextListener` | Allow to use the PLAINTEXT listener | `true` | -| `interBrokerListenerName` | The listener that the brokers should communicate on | `INTERNAL` | +| Name | Description | Value | +| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | +| `image.registry` | Kafka image registry | `docker.io` | +| `image.repository` | Kafka image repository | `bitnami/kafka` | +| `image.tag` | Kafka image tag (immutable tags are recommended) | `3.3.2-debian-11-r0` | +| `image.digest` | Kafka image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag | `""` | +| `image.pullPolicy` | Kafka image pull policy | `IfNotPresent` | +| `image.pullSecrets` | Specify docker-registry secret names as an array | `[]` | +| `image.debug` | Specify if debug values should be set | `false` | +| `config` | Configuration file for Kafka. Auto-generated based on other parameters when not specified | `""` | +| `existingConfigmap` | ConfigMap with Kafka Configuration | `""` | +| `log4j` | An optional log4j.properties file to overwrite the default of the Kafka brokers | `""` | +| `existingLog4jConfigMap` | The name of an existing ConfigMap containing a log4j.properties file | `""` | +| `heapOpts` | Kafka Java Heap size | `-Xmx1024m -Xms1024m` | +| `deleteTopicEnable` | Switch to enable topic deletion or not | `false` | +| `autoCreateTopicsEnable` | Switch to enable auto creation of topics. Enabling auto creation of topics not recommended for production or similar environments | `true` | +| `logFlushIntervalMessages` | The number of messages to accept before forcing a flush of data to disk | `_10000` | +| `logFlushIntervalMs` | The maximum amount of time a message can sit in a log before we force a flush | `1000` | +| `logRetentionBytes` | A size-based retention policy for logs | `_1073741824` | +| `logRetentionCheckIntervalMs` | The interval at which log segments are checked to see if they can be deleted | `300000` | +| `logRetentionHours` | The minimum age of a log file to be eligible for deletion due to age | `168` | +| `logSegmentBytes` | The maximum size of a log segment file. When this size is reached a new log segment will be created | `_1073741824` | +| `logsDirs` | A comma separated list of directories in which kafka's log data is kept | `/bitnami/kafka/data` | +| `maxMessageBytes` | The largest record batch size allowed by Kafka | `_1000012` | +| `defaultReplicationFactor` | Default replication factors for automatically created topics | `1` | +| `offsetsTopicReplicationFactor` | The replication factor for the offsets topic | `1` | +| `transactionStateLogReplicationFactor` | The replication factor for the transaction topic | `1` | +| `transactionStateLogMinIsr` | Overridden min.insync.replicas config for the transaction topic | `1` | +| `numIoThreads` | The number of threads doing disk I/O | `8` | +| `numNetworkThreads` | The number of threads handling network requests | `3` | +| `numPartitions` | The default number of log partitions per topic | `1` | +| `numRecoveryThreadsPerDataDir` | The number of threads per data directory to be used for log recovery at startup and flushing at shutdown | `1` | +| `socketReceiveBufferBytes` | The receive buffer (SO_RCVBUF) used by the socket server | `102400` | +| `socketRequestMaxBytes` | The maximum size of a request that the socket server will accept (protection against OOM) | `_104857600` | +| `socketSendBufferBytes` | The send buffer (SO_SNDBUF) used by the socket server | `102400` | +| `zookeeperConnectionTimeoutMs` | Timeout in ms for connecting to ZooKeeper | `6000` | +| `zookeeperChrootPath` | Path which puts data under some path in the global ZooKeeper namespace | `""` | +| `authorizerClassName` | The Authorizer is configured by setting authorizer.class.name=kafka.security.authorizer.AclAuthorizer in server.properties | `""` | +| `allowEveryoneIfNoAclFound` | By default, if a resource has no associated ACLs, then no one is allowed to access that resource except super users | `true` | +| `superUsers` | You can add super users in server.properties | `User:admin` | +| `auth.clientProtocol` | Authentication protocol for communications with clients. Allowed protocols: `plaintext`, `tls`, `mtls`, `sasl` and `sasl_tls` | `plaintext` | +| `auth.externalClientProtocol` | Authentication protocol for communications with external clients. Defaults to value of `auth.clientProtocol`. Allowed protocols: `plaintext`, `tls`, `mtls`, `sasl` and `sasl_tls` | `""` | +| `auth.interBrokerProtocol` | Authentication protocol for inter-broker communications. Allowed protocols: `plaintext`, `tls`, `mtls`, `sasl` and `sasl_tls` | `plaintext` | +| `auth.sasl.mechanisms` | SASL mechanisms when either `auth.interBrokerProtocol`, `auth.clientProtocol` or `auth.externalClientProtocol` are `sasl`. Allowed types: `plain`, `scram-sha-256`, `scram-sha-512` | `plain,scram-sha-256,scram-sha-512` | +| `auth.sasl.interBrokerMechanism` | SASL mechanism for inter broker communication. | `plain` | +| `auth.sasl.jaas.clientUsers` | Kafka client user list | `["user"]` | +| `auth.sasl.jaas.clientPasswords` | Kafka client passwords. This is mandatory if more than one user is specified in clientUsers | `[]` | +| `auth.sasl.jaas.interBrokerUser` | Kafka inter broker communication user for SASL authentication | `admin` | +| `auth.sasl.jaas.interBrokerPassword` | Kafka inter broker communication password for SASL authentication | `""` | +| `auth.sasl.jaas.zookeeperUser` | Kafka ZooKeeper user for SASL authentication | `""` | +| `auth.sasl.jaas.zookeeperPassword` | Kafka ZooKeeper password for SASL authentication | `""` | +| `auth.sasl.jaas.existingSecret` | Name of the existing secret containing credentials for clientUsers, interBrokerUser and zookeeperUser | `""` | +| `auth.tls.type` | Format to use for TLS certificates. Allowed types: `jks` and `pem` | `jks` | +| `auth.tls.pemChainIncluded` | Flag to denote that the Certificate Authority (CA) certificates are bundled with the endpoint cert. | `false` | +| `auth.tls.existingSecrets` | Array existing secrets containing the TLS certificates for the Kafka brokers | `[]` | +| `auth.tls.autoGenerated` | Generate automatically self-signed TLS certificates for Kafka brokers. Currently only supported if `auth.tls.type` is `pem` | `false` | +| `auth.tls.password` | Password to access the JKS files or PEM key when they are password-protected. | `""` | +| `auth.tls.existingSecret` | Name of the secret containing the password to access the JKS files or PEM key when they are password-protected. (`key`: `password`) | `""` | +| `auth.tls.jksTruststoreSecret` | Name of the existing secret containing your truststore if truststore not existing or different from the ones in the `auth.tls.existingSecrets` | `""` | +| `auth.tls.jksKeystoreSAN` | The secret key from the `auth.tls.existingSecrets` containing the keystore with a SAN certificate | `""` | +| `auth.tls.jksTruststore` | The secret key from the `auth.tls.existingSecrets` or `auth.tls.jksTruststoreSecret` containing the truststore | `""` | +| `auth.tls.endpointIdentificationAlgorithm` | The endpoint identification algorithm to validate server hostname using server certificate | `https` | +| `auth.zookeeper.tls.enabled` | Enable TLS for Zookeeper client connections. | `false` | +| `auth.zookeeper.tls.type` | Format to use for TLS certificates. Allowed types: `jks` and `pem`. | `jks` | +| `auth.zookeeper.tls.verifyHostname` | Hostname validation. | `true` | +| `auth.zookeeper.tls.existingSecret` | Name of the existing secret containing the TLS certificates for ZooKeeper client communications. | `""` | +| `auth.zookeeper.tls.existingSecretKeystoreKey` | The secret key from the auth.zookeeper.tls.existingSecret containing the Keystore. | `zookeeper.keystore.jks` | +| `auth.zookeeper.tls.existingSecretTruststoreKey` | The secret key from the auth.zookeeper.tls.existingSecret containing the Truststore. | `zookeeper.truststore.jks` | +| `auth.zookeeper.tls.passwordsSecret` | Existing secret containing Keystore and Truststore passwords. | `""` | +| `auth.zookeeper.tls.passwordsSecretKeystoreKey` | The secret key from the auth.zookeeper.tls.passwordsSecret containing the password for the Keystore. | `keystore-password` | +| `auth.zookeeper.tls.passwordsSecretTruststoreKey` | The secret key from the auth.zookeeper.tls.passwordsSecret containing the password for the Truststore. | `truststore-password` | +| `listeners` | The address(es) the socket server listens on. Auto-calculated it's set to an empty array | `[]` | +| `advertisedListeners` | The address(es) (hostname:port) the broker will advertise to producers and consumers. Auto-calculated it's set to an empty array | `[]` | +| `listenerSecurityProtocolMap` | The protocol->listener mapping. Auto-calculated it's set to nil | `""` | +| `allowPlaintextListener` | Allow to use the PLAINTEXT listener | `true` | +| `interBrokerListenerName` | The listener that the brokers should communicate on | `INTERNAL` | +| `command` | Override Kafka container command | `["/scripts/setup.sh"]` | +| `args` | Override Kafka container arguments | `[]` | +| `extraEnvVars` | Extra environment variables to add to Kafka pods | `[]` | +| `extraEnvVarsCM` | ConfigMap with extra environment variables | `""` | +| `extraEnvVarsSecret` | Secret with extra environment variables | `""` | + ### Statefulset parameters -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `replicaCount` | Number of Kafka nodes | `1` | -| `updateStrategy` | Update strategy for the stateful set | `RollingUpdate` | -| `rollingUpdatePartition` | Partition update strategy | `nil` | -| `podLabels` | Kafka pod labels | `{}` (evaluated as a template) | -| `podAnnotations` | Kafka Pod annotations | `{}` (evaluated as a template) | -| `affinity` | Affinity for pod assignment | `{}` (evaluated as a template) | -| `priorityClassName` | Name of the existing priority class to be used by kafka pods | `""` | -| `nodeSelector` | Node labels for pod assignment | `{}` (evaluated as a template) | -| `tolerations` | Tolerations for pod assignment | `[]` (evaluated as a template) | -| `podSecurityContext` | Kafka pods' Security Context | `{}` | -| `containerSecurityContext` | Kafka containers' Security Context | `{}` | -| `resources.limits` | The resources limits for Kafka containers | `{}` | -| `resources.requests` | The requested resources for Kafka containers | `{}` | -| `livenessProbe` | Liveness probe configuration for Kafka | `Check values.yaml file` | -| `readinessProbe` | Readiness probe configuration for Kafka | `Check values.yaml file` | -| `customLivenessProbe` | Custom Liveness probe configuration for Kafka | `{}` | -| `customReadinessProbe` | Custom Readiness probe configuration for Kafka | `{}` | -| `pdb.create` | Enable/disable a Pod Disruption Budget creation | `false` | -| `pdb.minAvailable` | Minimum number/percentage of pods that should remain scheduled | `nil` | -| `pdb.maxUnavailable` | Maximum number/percentage of pods that may be made unavailable | `1` | -| `command` | Override kafka container command | `['/scripts/setup.sh']` (evaluated as a template) | -| `args` | Override kafka container arguments | `[]` (evaluated as a template) | -| `sidecars` | Attach additional sidecar containers to the Kafka pod | `{}` | +| Name | Description | Value | +| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `replicaCount` | Number of Kafka nodes | `1` | +| `minBrokerId` | Minimal broker.id value, nodes increment their `broker.id` respectively | `0` | +| `brokerRackAssignment` | Set Broker Assignment for multi tenant environment Allowed values: `aws-az` | `""` | +| `containerPorts.client` | Kafka client container port | `9092` | +| `containerPorts.internal` | Kafka inter-broker container port | `9093` | +| `containerPorts.external` | Kafka external container port | `9094` | +| `livenessProbe.enabled` | Enable livenessProbe on Kafka containers | `true` | +| `livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `10` | +| `livenessProbe.periodSeconds` | Period seconds for livenessProbe | `10` | +| `livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `5` | +| `livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `3` | +| `livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` | +| `readinessProbe.enabled` | Enable readinessProbe on Kafka containers | `true` | +| `readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `5` | +| `readinessProbe.periodSeconds` | Period seconds for readinessProbe | `10` | +| `readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `5` | +| `readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `6` | +| `readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` | +| `startupProbe.enabled` | Enable startupProbe on Kafka containers | `false` | +| `startupProbe.initialDelaySeconds` | Initial delay seconds for startupProbe | `30` | +| `startupProbe.periodSeconds` | Period seconds for startupProbe | `10` | +| `startupProbe.timeoutSeconds` | Timeout seconds for startupProbe | `1` | +| `startupProbe.failureThreshold` | Failure threshold for startupProbe | `15` | +| `startupProbe.successThreshold` | Success threshold for startupProbe | `1` | +| `customLivenessProbe` | Custom livenessProbe that overrides the default one | `{}` | +| `customReadinessProbe` | Custom readinessProbe that overrides the default one | `{}` | +| `customStartupProbe` | Custom startupProbe that overrides the default one | `{}` | +| `lifecycleHooks` | lifecycleHooks for the Kafka container to automate configuration before or after startup | `{}` | +| `resources.limits` | The resources limits for the container | `{}` | +| `resources.requests` | The requested resources for the container | `{}` | +| `podSecurityContext.enabled` | Enable security context for the pods | `true` | +| `podSecurityContext.fsGroup` | Set Kafka pod's Security Context fsGroup | `1001` | +| `containerSecurityContext.enabled` | Enable Kafka containers' Security Context | `true` | +| `containerSecurityContext.runAsUser` | Set Kafka containers' Security Context runAsUser | `1001` | +| `containerSecurityContext.runAsNonRoot` | Set Kafka containers' Security Context runAsNonRoot | `true` | +| `containerSecurityContext.allowPrivilegeEscalation` | Force the child process to be run as nonprivilege | `false` | +| `hostAliases` | Kafka pods host aliases | `[]` | +| `hostNetwork` | Specify if host network should be enabled for Kafka pods | `false` | +| `hostIPC` | Specify if host IPC should be enabled for Kafka pods | `false` | +| `podLabels` | Extra labels for Kafka pods | `{}` | +| `podAnnotations` | Extra annotations for Kafka pods | `{}` | +| `podAffinityPreset` | Pod affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `podAntiAffinityPreset` | Pod anti-affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `nodeAffinityPreset.type` | Node affinity preset type. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `nodeAffinityPreset.key` | Node label key to match Ignored if `affinity` is set. | `""` | +| `nodeAffinityPreset.values` | Node label values to match. Ignored if `affinity` is set. | `[]` | +| `affinity` | Affinity for pod assignment | `{}` | +| `nodeSelector` | Node labels for pod assignment | `{}` | +| `tolerations` | Tolerations for pod assignment | `[]` | +| `topologySpreadConstraints` | Topology Spread Constraints for pod assignment spread across your cluster among failure-domains. Evaluated as a template | `[]` | +| `terminationGracePeriodSeconds` | Seconds the pod needs to gracefully terminate | `""` | +| `podManagementPolicy` | StatefulSet controller supports relax its ordering guarantees while preserving its uniqueness and identity guarantees. There are two valid pod management policies: OrderedReady and Parallel | `Parallel` | +| `priorityClassName` | Name of the existing priority class to be used by kafka pods | `""` | +| `schedulerName` | Name of the k8s scheduler (other than default) | `""` | +| `updateStrategy.type` | Kafka statefulset strategy type | `RollingUpdate` | +| `updateStrategy.rollingUpdate` | Kafka statefulset rolling update configuration parameters | `{}` | +| `extraVolumes` | Optionally specify extra list of additional volumes for the Kafka pod(s) | `[]` | +| `extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the Kafka container(s) | `[]` | +| `sidecars` | Add additional sidecar containers to the Kafka pod(s) | `[]` | +| `initContainers` | Add additional Add init containers to the Kafka pod(s) | `[]` | +| `pdb.create` | Deploy a pdb object for the Kafka pod | `false` | +| `pdb.minAvailable` | Maximum number/percentage of unavailable Kafka replicas | `""` | +| `pdb.maxUnavailable` | Maximum number/percentage of unavailable Kafka replicas | `1` | -### Exposure parameters -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `service.type` | Kubernetes Service type | `ClusterIP` | -| `service.port` | Kafka port for client connections | `9092` | -| `service.internalPort` | Kafka port for inter-broker connections | `9093` | -| `service.externalPort` | Kafka port for external connections | `9094` | -| `service.nodePorts.client` | Nodeport for client connections | `""` | -| `service.nodePorts.external` | Nodeport for external connections | `""` | -| `service.loadBalancerIP` | loadBalancerIP for Kafka Service | `nil` | -| `service.loadBalancerSourceRanges` | Address(es) that are allowed when service is LoadBalancer | `[]` | -| `service.annotations` | Service annotations | `{}`(evaluated as a template) | -| `externalAccess.enabled` | Enable Kubernetes external cluster access to Kafka brokers | `false` | -| `externalAccess.autoDiscovery.enabled` | Enable using an init container to auto-detect external IPs/ports by querying the K8s API | `false` | -| `externalAccess.autoDiscovery.image.registry` | Init container auto-discovery image registry (kubectl) | `docker.io` | -| `externalAccess.autoDiscovery.image.repository` | Init container auto-discovery image name (kubectl) | `bitnami/kubectl` | -| `externalAccess.autoDiscovery.image.tag` | Init container auto-discovery image tag (kubectl) | `{TAG_NAME}` | -| `externalAccess.autoDiscovery.image.pullPolicy` | Init container auto-discovery image pull policy (kubectl) | `Always` | -| `externalAccess.autoDiscovery.resources.limits` | Init container auto-discovery resource limits | `{}` | -| `externalAccess.autoDiscovery.resources.requests` | Init container auto-discovery resource requests | `{}` | -| `externalAccess.service.type` | Kubernetes Service type for external access. It can be NodePort or LoadBalancer | `LoadBalancer` | -| `externalAccess.service.port` | Kafka port used for external access when service type is LoadBalancer | `9094` | -| `externalAccess.service.loadBalancerIPs` | Array of load balancer IPs for Kafka brokers | `[]` | -| `externalAccess.service.loadBalancerSourceRanges` | Address(es) that are allowed when service is LoadBalancer | `[]` | -| `externalAccess.service.domain` | Domain or external ip used to configure Kafka external listener when service type is NodePort | `nil` | -| `externalAccess.service.nodePorts` | Array of node ports used to configure Kafka external listener when service type is NodePort | `[]` | -| `externalAccess.service.annotations` | Service annotations for external access | `{}`(evaluated as a template) | +### Traffic Exposure parameters + +| Name | Description | Value | +| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | --------------------- | +| `service.type` | Kubernetes Service type | `ClusterIP` | +| `service.ports.client` | Kafka svc port for client connections | `9092` | +| `service.ports.internal` | Kafka svc port for inter-broker connections | `9093` | +| `service.ports.external` | Kafka svc port for external connections | `9094` | +| `service.nodePorts.client` | Node port for the Kafka client connections | `""` | +| `service.nodePorts.external` | Node port for the Kafka external connections | `""` | +| `service.sessionAffinity` | Control where client requests go, to the same pod or round-robin | `None` | +| `service.sessionAffinityConfig` | Additional settings for the sessionAffinity | `{}` | +| `service.clusterIP` | Kafka service Cluster IP | `""` | +| `service.loadBalancerIP` | Kafka service Load Balancer IP | `""` | +| `service.loadBalancerSourceRanges` | Kafka service Load Balancer sources | `[]` | +| `service.externalTrafficPolicy` | Kafka service external traffic policy | `Cluster` | +| `service.annotations` | Additional custom annotations for Kafka service | `{}` | +| `service.headless.publishNotReadyAddresses` | Indicates that any agent which deals with endpoints for this Service should disregard any indications of ready/not-ready | `false` | +| `service.headless.annotations` | Annotations for the headless service. | `{}` | +| `service.headless.labels` | Labels for the headless service. | `{}` | +| `service.extraPorts` | Extra ports to expose in the Kafka service (normally used with the `sidecar` value) | `[]` | +| `externalAccess.enabled` | Enable Kubernetes external cluster access to Kafka brokers | `false` | +| `externalAccess.autoDiscovery.enabled` | Enable using an init container to auto-detect external IPs/ports by querying the K8s API | `false` | +| `externalAccess.autoDiscovery.image.registry` | Init container auto-discovery image registry | `docker.io` | +| `externalAccess.autoDiscovery.image.repository` | Init container auto-discovery image repository | `bitnami/kubectl` | +| `externalAccess.autoDiscovery.image.tag` | Init container auto-discovery image tag (immutable tags are recommended) | `1.25.6-debian-11-r1` | +| `externalAccess.autoDiscovery.image.digest` | Petete image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag | `""` | +| `externalAccess.autoDiscovery.image.pullPolicy` | Init container auto-discovery image pull policy | `IfNotPresent` | +| `externalAccess.autoDiscovery.image.pullSecrets` | Init container auto-discovery image pull secrets | `[]` | +| `externalAccess.autoDiscovery.resources.limits` | The resources limits for the auto-discovery init container | `{}` | +| `externalAccess.autoDiscovery.resources.requests` | The requested resources for the auto-discovery init container | `{}` | +| `externalAccess.service.type` | Kubernetes Service type for external access. It can be NodePort, LoadBalancer or ClusterIP | `LoadBalancer` | +| `externalAccess.service.ports.external` | Kafka port used for external access when service type is LoadBalancer | `9094` | +| `externalAccess.service.loadBalancerIPs` | Array of load balancer IPs for each Kafka broker. Length must be the same as replicaCount | `[]` | +| `externalAccess.service.loadBalancerNames` | Array of load balancer Names for each Kafka broker. Length must be the same as replicaCount | `[]` | +| `externalAccess.service.loadBalancerAnnotations` | Array of load balancer annotations for each Kafka broker. Length must be the same as replicaCount | `[]` | +| `externalAccess.service.loadBalancerSourceRanges` | Address(es) that are allowed when service is LoadBalancer | `[]` | +| `externalAccess.service.nodePorts` | Array of node ports used for each Kafka broker. Length must be the same as replicaCount | `[]` | +| `externalAccess.service.useHostIPs` | Use service host IPs to configure Kafka external listener when service type is NodePort | `false` | +| `externalAccess.service.usePodIPs` | using the MY_POD_IP address for external access. | `false` | +| `externalAccess.service.domain` | Domain or external ip used to configure Kafka external listener when service type is NodePort or ClusterIP | `""` | +| `externalAccess.service.publishNotReadyAddresses` | Indicates that any agent which deals with endpoints for this Service should disregard any indications of ready/not-ready | `false` | +| `externalAccess.service.labels` | Service labels for external access | `{}` | +| `externalAccess.service.annotations` | Service annotations for external access | `{}` | +| `externalAccess.service.extraPorts` | Extra ports to expose in the Kafka external service | `[]` | +| `networkPolicy.enabled` | Specifies whether a NetworkPolicy should be created | `false` | +| `networkPolicy.allowExternal` | Don't require client label for connections | `true` | +| `networkPolicy.explicitNamespacesSelector` | A Kubernetes LabelSelector to explicitly select namespaces from which traffic could be allowed | `{}` | +| `networkPolicy.externalAccess.from` | customize the from section for External Access on tcp-external port | `[]` | +| `networkPolicy.egressRules.customRules` | Custom network policy rule | `{}` | + ### Persistence parameters -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `persistence.enabled` | Enable Kafka data persistence using PVC, note that Zookeeper persistence is unaffected | `true` | -| `persistence.existingClaim` | Provide an existing `PersistentVolumeClaim`, the value is evaluated as a template | `nil` | -| `persistence.storageClass` | PVC Storage Class for Kafka data volume | `nil` | -| `persistence.accessMode` | PVC Access Mode for Kafka data volume | `ReadWriteOnce` | -| `persistence.size` | PVC Storage Request for Kafka data volume | `8Gi` | -| `persistence.annotations` | Annotations for the PVC | `{}`(evaluated as a template) | +| Name | Description | Value | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | +| `persistence.enabled` | Enable Kafka data persistence using PVC, note that ZooKeeper persistence is unaffected | `true` | +| `persistence.existingClaim` | A manually managed Persistent Volume and Claim | `""` | +| `persistence.storageClass` | PVC Storage Class for Kafka data volume | `""` | +| `persistence.accessModes` | Persistent Volume Access Modes | `["ReadWriteOnce"]` | +| `persistence.size` | PVC Storage Request for Kafka data volume | `8Gi` | +| `persistence.annotations` | Annotations for the PVC | `{}` | +| `persistence.labels` | Labels for the PVC | `{}` | +| `persistence.selector` | Selector to match an existing Persistent Volume for Kafka data PVC. If set, the PVC can't have a PV dynamically provisioned for it | `{}` | +| `persistence.mountPath` | Mount path of the Kafka data volume | `/bitnami/kafka` | +| `logPersistence.enabled` | Enable Kafka logs persistence using PVC, note that ZooKeeper persistence is unaffected | `false` | +| `logPersistence.existingClaim` | A manually managed Persistent Volume and Claim | `""` | +| `logPersistence.storageClass` | PVC Storage Class for Kafka logs volume | `""` | +| `logPersistence.accessModes` | Persistent Volume Access Modes | `["ReadWriteOnce"]` | +| `logPersistence.size` | PVC Storage Request for Kafka logs volume | `8Gi` | +| `logPersistence.annotations` | Annotations for the PVC | `{}` | +| `logPersistence.selector` | Selector to match an existing Persistent Volume for Kafka log data PVC. If set, the PVC can't have a PV dynamically provisioned for it | `{}` | +| `logPersistence.mountPath` | Mount path of the Kafka logs volume | `/opt/bitnami/kafka/logs` | -### RBAC parameters - -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `serviceAccount.create` | Enable creation of ServiceAccount for Kafka pods | `true` | -| `serviceAccount.name` | Name of the created serviceAccount | Generated using the `kafka.fullname` template | -| `rbac.create` | Weather to create & use RBAC resources or not | `false` | ### Volume Permissions parameters -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `volumePermissions.enabled` | Enable init container that changes the owner and group of the persistent volume(s) mountpoint to `runAsUser:fsGroup` | `false` | -| `volumePermissions.image.registry` | Init container volume-permissions image registry | `docker.io` | -| `volumePermissions.image.repository` | Init container volume-permissions image name | `bitnami/minideb` | -| `volumePermissions.image.tag` | Init container volume-permissions image tag | `buster` | -| `volumePermissions.image.pullPolicy` | Init container volume-permissions image pull policy | `Always` | -| `volumePermissions.image.pullSecrets` | Specify docker-registry secret names as an array | `[]` (does not add image pull secrets to deployed pods) | -| `volumePermissions.resources.limits` | Init container volume-permissions resource limits | `{}` | -| `volumePermissions.resources.requests` | Init container volume-permissions resource requests | `{}` | +| Name | Description | Value | +| ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `volumePermissions.enabled` | Enable init container that changes the owner and group of the persistent volume | `false` | +| `volumePermissions.image.registry` | Init container volume-permissions image registry | `docker.io` | +| `volumePermissions.image.repository` | Init container volume-permissions image repository | `bitnami/bitnami-shell` | +| `volumePermissions.image.tag` | Init container volume-permissions image tag (immutable tags are recommended) | `11-debian-11-r75` | +| `volumePermissions.image.digest` | Init container volume-permissions image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag | `""` | +| `volumePermissions.image.pullPolicy` | Init container volume-permissions image pull policy | `IfNotPresent` | +| `volumePermissions.image.pullSecrets` | Init container volume-permissions image pull secrets | `[]` | +| `volumePermissions.resources.limits` | Init container volume-permissions resource limits | `{}` | +| `volumePermissions.resources.requests` | Init container volume-permissions resource requests | `{}` | +| `volumePermissions.containerSecurityContext.runAsUser` | User ID for the init container | `0` | + + +### Other Parameters + +| Name | Description | Value | +| --------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------- | +| `serviceAccount.create` | Enable creation of ServiceAccount for Kafka pods | `true` | +| `serviceAccount.name` | The name of the service account to use. If not set and `create` is `true`, a name is generated | `""` | +| `serviceAccount.automountServiceAccountToken` | Allows auto mount of ServiceAccountToken on the serviceAccount created | `true` | +| `serviceAccount.annotations` | Additional custom annotations for the ServiceAccount | `{}` | +| `rbac.create` | Whether to create & use RBAC resources or not | `false` | + ### Metrics parameters -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `metrics.kafka.enabled` | Whether or not to create a standalone Kafka exporter to expose Kafka metrics | `false` | -| `metrics.kafka.image.registry` | Kafka exporter image registry | `docker.io` | -| `metrics.kafka.image.repository` | Kafka exporter image name | `bitnami/kafka-exporter` | -| `metrics.kafka.image.tag` | Kafka exporter image tag | `{TAG_NAME}` | -| `metrics.kafka.image.pullPolicy` | Kafka exporter image pull policy | `IfNotPresent` | -| `metrics.kafka.image.pullSecrets` | Specify docker-registry secret names as an array | `[]` (does not add image pull secrets to deployed pods) | -| `metrics.kafka.extraFlags` | Extra flags to be passed to Kafka exporter | `{}` | -| `metrics.kafka.certificatesSecret` | Name of the existing secret containing the optional certificate and key files | `nil` | -| `metrics.kafka.resources.limits` | Kafka Exporter container resource limits | `{}` | -| `metrics.kafka.resources.requests` | Kafka Exporter container resource requests | `{}` | -| `metrics.kafka.service.type` | Kubernetes service type (`ClusterIP`, `NodePort` or `LoadBalancer`) for Kafka Exporter | `ClusterIP` | -| `metrics.kafka.service.port` | Kafka Exporter Prometheus port | `9308` | -| `metrics.kafka.service.nodePort` | Kubernetes HTTP node port | `""` | -| `metrics.kafka.service.annotations` | Annotations for Prometheus metrics service | `Check values.yaml file` | -| `metrics.kafka.service.loadBalancerIP` | loadBalancerIP if service type is `LoadBalancer` | `nil` | -| `metrics.kafka.service.clusterIP` | Static clusterIP or None for headless services | `nil` | -| `metrics.jmx.enabled` | Whether or not to expose JMX metrics to Prometheus | `false` | -| `metrics.jmx.image.registry` | JMX exporter image registry | `docker.io` | -| `metrics.jmx.image.repository` | JMX exporter image name | `bitnami/jmx-exporter` | -| `metrics.jmx.image.tag` | JMX exporter image tag | `{TAG_NAME}` | -| `metrics.jmx.image.pullPolicy` | JMX exporter image pull policy | `IfNotPresent` | -| `metrics.jmx.image.pullSecrets` | Specify docker-registry secret names as an array | `[]` (does not add image pull secrets to deployed pods) | -| `metrics.jmx.resources.limits` | JMX Exporter container resource limits | `{}` | -| `metrics.jmx.resources.requests` | JMX Exporter container resource requests | `{}` | -| `metrics.jmx.service.type` | Kubernetes service type (`ClusterIP`, `NodePort` or `LoadBalancer`) for JMX Exporter | `ClusterIP` | -| `metrics.jmx.service.port` | JMX Exporter Prometheus port | `5556` | -| `metrics.jmx.service.nodePort` | Kubernetes HTTP node port | `""` | -| `metrics.jmx.service.annotations` | Annotations for Prometheus metrics service | `Check values.yaml file` | -| `metrics.jmx.service.loadBalancerIP` | loadBalancerIP if service type is `LoadBalancer` | `nil` | -| `metrics.jmx.service.clusterIP` | Static clusterIP or None for headless services | `nil` | -| `metrics.jmx.whitelistObjectNames` | Allows setting which JMX objects you want to expose to via JMX stats to JMX Exporter | (see `values.yaml`) | -| `metrics.jmx.config` | Configuration file for JMX exporter | (see `values.yaml`) | -| `metrics.jmx.existingConfigmap` | Name of existing ConfigMap with JMX exporter configuration | `nil` | -| `metrics.serviceMonitor.enabled` | if `true`, creates a Prometheus Operator ServiceMonitor (requires `metrics.kafka.enabled` or `metrics.jmx.enabled` to be `true`) | `false` | -| `metrics.serviceMonitor.namespace` | Namespace which Prometheus is running in | `monitoring` | -| `metrics.serviceMonitor.interval` | Interval at which metrics should be scraped | `nil` | -| `metrics.serviceMonitor.scrapeTimeout` | Timeout after which the scrape is ended | `nil` (Prometheus Operator default value) | -| `metrics.serviceMonitor.selector` | ServiceMonitor selector labels | `nil` (Prometheus Operator default value) | +| Name | Description | Value | +| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | +| `metrics.kafka.enabled` | Whether or not to create a standalone Kafka exporter to expose Kafka metrics | `false` | +| `metrics.kafka.image.registry` | Kafka exporter image registry | `docker.io` | +| `metrics.kafka.image.repository` | Kafka exporter image repository | `bitnami/kafka-exporter` | +| `metrics.kafka.image.tag` | Kafka exporter image tag (immutable tags are recommended) | `1.6.0-debian-11-r52` | +| `metrics.kafka.image.digest` | Kafka exporter image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag | `""` | +| `metrics.kafka.image.pullPolicy` | Kafka exporter image pull policy | `IfNotPresent` | +| `metrics.kafka.image.pullSecrets` | Specify docker-registry secret names as an array | `[]` | +| `metrics.kafka.certificatesSecret` | Name of the existing secret containing the optional certificate and key files | `""` | +| `metrics.kafka.tlsCert` | The secret key from the certificatesSecret if 'client-cert' key different from the default (cert-file) | `cert-file` | +| `metrics.kafka.tlsKey` | The secret key from the certificatesSecret if 'client-key' key different from the default (key-file) | `key-file` | +| `metrics.kafka.tlsCaSecret` | Name of the existing secret containing the optional ca certificate for Kafka exporter client authentication | `""` | +| `metrics.kafka.tlsCaCert` | The secret key from the certificatesSecret or tlsCaSecret if 'ca-cert' key different from the default (ca-file) | `ca-file` | +| `metrics.kafka.extraFlags` | Extra flags to be passed to Kafka exporter | `{}` | +| `metrics.kafka.command` | Override Kafka exporter container command | `[]` | +| `metrics.kafka.args` | Override Kafka exporter container arguments | `[]` | +| `metrics.kafka.containerPorts.metrics` | Kafka exporter metrics container port | `9308` | +| `metrics.kafka.resources.limits` | The resources limits for the container | `{}` | +| `metrics.kafka.resources.requests` | The requested resources for the container | `{}` | +| `metrics.kafka.podSecurityContext.enabled` | Enable security context for the pods | `true` | +| `metrics.kafka.podSecurityContext.fsGroup` | Set Kafka exporter pod's Security Context fsGroup | `1001` | +| `metrics.kafka.containerSecurityContext.enabled` | Enable Kafka exporter containers' Security Context | `true` | +| `metrics.kafka.containerSecurityContext.runAsUser` | Set Kafka exporter containers' Security Context runAsUser | `1001` | +| `metrics.kafka.containerSecurityContext.runAsNonRoot` | Set Kafka exporter containers' Security Context runAsNonRoot | `true` | +| `metrics.kafka.hostAliases` | Kafka exporter pods host aliases | `[]` | +| `metrics.kafka.podLabels` | Extra labels for Kafka exporter pods | `{}` | +| `metrics.kafka.podAnnotations` | Extra annotations for Kafka exporter pods | `{}` | +| `metrics.kafka.podAffinityPreset` | Pod affinity preset. Ignored if `metrics.kafka.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `metrics.kafka.podAntiAffinityPreset` | Pod anti-affinity preset. Ignored if `metrics.kafka.affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `metrics.kafka.nodeAffinityPreset.type` | Node affinity preset type. Ignored if `metrics.kafka.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `metrics.kafka.nodeAffinityPreset.key` | Node label key to match Ignored if `metrics.kafka.affinity` is set. | `""` | +| `metrics.kafka.nodeAffinityPreset.values` | Node label values to match. Ignored if `metrics.kafka.affinity` is set. | `[]` | +| `metrics.kafka.affinity` | Affinity for pod assignment | `{}` | +| `metrics.kafka.nodeSelector` | Node labels for pod assignment | `{}` | +| `metrics.kafka.tolerations` | Tolerations for pod assignment | `[]` | +| `metrics.kafka.schedulerName` | Name of the k8s scheduler (other than default) for Kafka exporter | `""` | +| `metrics.kafka.priorityClassName` | Kafka exporter pods' priorityClassName | `""` | +| `metrics.kafka.topologySpreadConstraints` | Topology Spread Constraints for pod assignment | `[]` | +| `metrics.kafka.extraVolumes` | Optionally specify extra list of additional volumes for the Kafka exporter pod(s) | `[]` | +| `metrics.kafka.extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the Kafka exporter container(s) | `[]` | +| `metrics.kafka.sidecars` | Add additional sidecar containers to the Kafka exporter pod(s) | `[]` | +| `metrics.kafka.initContainers` | Add init containers to the Kafka exporter pods | `[]` | +| `metrics.kafka.service.ports.metrics` | Kafka exporter metrics service port | `9308` | +| `metrics.kafka.service.clusterIP` | Static clusterIP or None for headless services | `""` | +| `metrics.kafka.service.sessionAffinity` | Control where client requests go, to the same pod or round-robin | `None` | +| `metrics.kafka.service.annotations` | Annotations for the Kafka exporter service | `{}` | +| `metrics.kafka.serviceAccount.create` | Enable creation of ServiceAccount for Kafka exporter pods | `true` | +| `metrics.kafka.serviceAccount.name` | The name of the service account to use. If not set and `create` is `true`, a name is generated | `""` | +| `metrics.kafka.serviceAccount.automountServiceAccountToken` | Allows auto mount of ServiceAccountToken on the serviceAccount created | `true` | +| `metrics.jmx.enabled` | Whether or not to expose JMX metrics to Prometheus | `false` | +| `metrics.jmx.image.registry` | JMX exporter image registry | `docker.io` | +| `metrics.jmx.image.repository` | JMX exporter image repository | `bitnami/jmx-exporter` | +| `metrics.jmx.image.tag` | JMX exporter image tag (immutable tags are recommended) | `0.17.2-debian-11-r41` | +| `metrics.jmx.image.digest` | JMX exporter image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag | `""` | +| `metrics.jmx.image.pullPolicy` | JMX exporter image pull policy | `IfNotPresent` | +| `metrics.jmx.image.pullSecrets` | Specify docker-registry secret names as an array | `[]` | +| `metrics.jmx.containerSecurityContext.enabled` | Enable Prometheus JMX exporter containers' Security Context | `true` | +| `metrics.jmx.containerSecurityContext.runAsUser` | Set Prometheus JMX exporter containers' Security Context runAsUser | `1001` | +| `metrics.jmx.containerSecurityContext.runAsNonRoot` | Set Prometheus JMX exporter containers' Security Context runAsNonRoot | `true` | +| `metrics.jmx.containerPorts.metrics` | Prometheus JMX exporter metrics container port | `5556` | +| `metrics.jmx.resources.limits` | The resources limits for the JMX exporter container | `{}` | +| `metrics.jmx.resources.requests` | The requested resources for the JMX exporter container | `{}` | +| `metrics.jmx.service.ports.metrics` | Prometheus JMX exporter metrics service port | `5556` | +| `metrics.jmx.service.clusterIP` | Static clusterIP or None for headless services | `""` | +| `metrics.jmx.service.sessionAffinity` | Control where client requests go, to the same pod or round-robin | `None` | +| `metrics.jmx.service.annotations` | Annotations for the Prometheus JMX exporter service | `{}` | +| `metrics.jmx.whitelistObjectNames` | Allows setting which JMX objects you want to expose to via JMX stats to JMX exporter | `["kafka.controller:*","kafka.server:*","java.lang:*","kafka.network:*","kafka.log:*"]` | +| `metrics.jmx.config` | Configuration file for JMX exporter | `""` | +| `metrics.jmx.existingConfigmap` | Name of existing ConfigMap with JMX exporter configuration | `""` | +| `metrics.jmx.extraRules` | Add extra rules to JMX exporter configuration | `""` | +| `metrics.serviceMonitor.enabled` | if `true`, creates a Prometheus Operator ServiceMonitor (requires `metrics.kafka.enabled` or `metrics.jmx.enabled` to be `true`) | `false` | +| `metrics.serviceMonitor.namespace` | Namespace in which Prometheus is running | `""` | +| `metrics.serviceMonitor.interval` | Interval at which metrics should be scraped | `""` | +| `metrics.serviceMonitor.scrapeTimeout` | Timeout after which the scrape is ended | `""` | +| `metrics.serviceMonitor.labels` | Additional labels that can be used so ServiceMonitor will be discovered by Prometheus | `{}` | +| `metrics.serviceMonitor.selector` | Prometheus instance selector labels | `{}` | +| `metrics.serviceMonitor.relabelings` | RelabelConfigs to apply to samples before scraping | `[]` | +| `metrics.serviceMonitor.metricRelabelings` | MetricRelabelConfigs to apply to samples before ingestion | `[]` | +| `metrics.serviceMonitor.honorLabels` | Specify honorLabels parameter to add the scrape endpoint | `false` | +| `metrics.serviceMonitor.jobLabel` | The name of the label on the target service to use as the job name in prometheus. | `""` | +| `metrics.prometheusRule.enabled` | if `true`, creates a Prometheus Operator PrometheusRule (requires `metrics.kafka.enabled` or `metrics.jmx.enabled` to be `true`) | `false` | +| `metrics.prometheusRule.namespace` | Namespace in which Prometheus is running | `""` | +| `metrics.prometheusRule.labels` | Additional labels that can be used so PrometheusRule will be discovered by Prometheus | `{}` | +| `metrics.prometheusRule.groups` | Prometheus Rule Groups for Kafka | `[]` | -### Zookeeper chart parameters -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `zookeeper.enabled` | Switch to enable or disable the Zookeeper helm chart | `true` | -| `zookeeper.persistence.enabled` | Enable Zookeeper persistence using PVC | `true` | -| `externalZookeeper.servers` | Server or list of external Zookeeper servers to use | `[]` | +### Kafka provisioning parameters + +| Name | Description | Value | +| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| `provisioning.enabled` | Enable kafka provisioning Job | `false` | +| `provisioning.numPartitions` | Default number of partitions for topics when unspecified | `1` | +| `provisioning.replicationFactor` | Default replication factor for topics when unspecified | `1` | +| `provisioning.topics` | Kafka topics to provision | `[]` | +| `provisioning.nodeSelector` | Node labels for pod assignment | `{}` | +| `provisioning.tolerations` | Tolerations for pod assignment | `[]` | +| `provisioning.extraProvisioningCommands` | Extra commands to run to provision cluster resources | `[]` | +| `provisioning.parallel` | Number of provisioning commands to run at the same time | `1` | +| `provisioning.preScript` | Extra bash script to run before topic provisioning. $CLIENT_CONF is path to properties file with most needed configurations | `""` | +| `provisioning.postScript` | Extra bash script to run after topic provisioning. $CLIENT_CONF is path to properties file with most needed configurations | `""` | +| `provisioning.auth.tls.type` | Format to use for TLS certificates. Allowed types: `jks` and `pem`. | `jks` | +| `provisioning.auth.tls.certificatesSecret` | Existing secret containing the TLS certificates for the Kafka provisioning Job. | `""` | +| `provisioning.auth.tls.cert` | The secret key from the certificatesSecret if 'cert' key different from the default (tls.crt) | `tls.crt` | +| `provisioning.auth.tls.key` | The secret key from the certificatesSecret if 'key' key different from the default (tls.key) | `tls.key` | +| `provisioning.auth.tls.caCert` | The secret key from the certificatesSecret if 'caCert' key different from the default (ca.crt) | `ca.crt` | +| `provisioning.auth.tls.keystore` | The secret key from the certificatesSecret if 'keystore' key different from the default (keystore.jks) | `keystore.jks` | +| `provisioning.auth.tls.truststore` | The secret key from the certificatesSecret if 'truststore' key different from the default (truststore.jks) | `truststore.jks` | +| `provisioning.auth.tls.passwordsSecret` | Name of the secret containing passwords to access the JKS files or PEM key when they are password-protected. | `""` | +| `provisioning.auth.tls.keyPasswordSecretKey` | The secret key from the passwordsSecret if 'keyPasswordSecretKey' key different from the default (key-password) | `key-password` | +| `provisioning.auth.tls.keystorePasswordSecretKey` | The secret key from the passwordsSecret if 'keystorePasswordSecretKey' key different from the default (keystore-password) | `keystore-password` | +| `provisioning.auth.tls.truststorePasswordSecretKey` | The secret key from the passwordsSecret if 'truststorePasswordSecretKey' key different from the default (truststore-password) | `truststore-password` | +| `provisioning.auth.tls.keyPassword` | Password to access the password-protected PEM key if necessary. Ignored if 'passwordsSecret' is provided. | `""` | +| `provisioning.auth.tls.keystorePassword` | Password to access the JKS keystore. Ignored if 'passwordsSecret' is provided. | `""` | +| `provisioning.auth.tls.truststorePassword` | Password to access the JKS truststore. Ignored if 'passwordsSecret' is provided. | `""` | +| `provisioning.command` | Override provisioning container command | `[]` | +| `provisioning.args` | Override provisioning container arguments | `[]` | +| `provisioning.extraEnvVars` | Extra environment variables to add to the provisioning pod | `[]` | +| `provisioning.extraEnvVarsCM` | ConfigMap with extra environment variables | `""` | +| `provisioning.extraEnvVarsSecret` | Secret with extra environment variables | `""` | +| `provisioning.podAnnotations` | Extra annotations for Kafka provisioning pods | `{}` | +| `provisioning.podLabels` | Extra labels for Kafka provisioning pods | `{}` | +| `provisioning.serviceAccount.create` | Enable creation of ServiceAccount for Kafka provisioning pods | `false` | +| `provisioning.serviceAccount.name` | The name of the service account to use. If not set and `create` is `true`, a name is generated | `""` | +| `provisioning.serviceAccount.automountServiceAccountToken` | Allows auto mount of ServiceAccountToken on the serviceAccount created | `true` | +| `provisioning.resources.limits` | The resources limits for the Kafka provisioning container | `{}` | +| `provisioning.resources.requests` | The requested resources for the Kafka provisioning container | `{}` | +| `provisioning.podSecurityContext.enabled` | Enable security context for the pods | `true` | +| `provisioning.podSecurityContext.fsGroup` | Set Kafka provisioning pod's Security Context fsGroup | `1001` | +| `provisioning.containerSecurityContext.enabled` | Enable Kafka provisioning containers' Security Context | `true` | +| `provisioning.containerSecurityContext.runAsUser` | Set Kafka provisioning containers' Security Context runAsUser | `1001` | +| `provisioning.containerSecurityContext.runAsNonRoot` | Set Kafka provisioning containers' Security Context runAsNonRoot | `true` | +| `provisioning.schedulerName` | Name of the k8s scheduler (other than default) for kafka provisioning | `""` | +| `provisioning.extraVolumes` | Optionally specify extra list of additional volumes for the Kafka provisioning pod(s) | `[]` | +| `provisioning.extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the Kafka provisioning container(s) | `[]` | +| `provisioning.sidecars` | Add additional sidecar containers to the Kafka provisioning pod(s) | `[]` | +| `provisioning.initContainers` | Add additional Add init containers to the Kafka provisioning pod(s) | `[]` | +| `provisioning.waitForKafka` | If true use an init container to wait until kafka is ready before starting provisioning | `true` | + + +### ZooKeeper chart parameters + +| Name | Description | Value | +| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | +| `zookeeper.enabled` | Switch to enable or disable the ZooKeeper helm chart | `true` | +| `zookeeper.replicaCount` | Number of ZooKeeper nodes | `1` | +| `zookeeper.auth.client.enabled` | Enable ZooKeeper auth | `false` | +| `zookeeper.auth.client.clientUser` | User that will use ZooKeeper clients to auth | `""` | +| `zookeeper.auth.client.clientPassword` | Password that will use ZooKeeper clients to auth | `""` | +| `zookeeper.auth.client.serverUsers` | Comma, semicolon or whitespace separated list of user to be created. Specify them as a string, for example: "user1,user2,admin" | `""` | +| `zookeeper.auth.client.serverPasswords` | Comma, semicolon or whitespace separated list of passwords to assign to users when created. Specify them as a string, for example: "pass4user1, pass4user2, pass4admin" | `""` | +| `zookeeper.persistence.enabled` | Enable persistence on ZooKeeper using PVC(s) | `true` | +| `zookeeper.persistence.storageClass` | Persistent Volume storage class | `""` | +| `zookeeper.persistence.accessModes` | Persistent Volume access modes | `["ReadWriteOnce"]` | +| `zookeeper.persistence.size` | Persistent Volume size | `8Gi` | +| `externalZookeeper.servers` | List of external zookeeper servers to use. Typically used in combination with 'zookeeperChrootPath'. | `[]` | + Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, ```console -helm install my-release \ +$ helm install my-release \ --set replicaCount=3 \ - bitnami/kafka + my-repo/kafka ``` The above command deploys Kafka with 3 brokers (replicas). @@ -280,7 +506,7 @@ The above command deploys Kafka with 3 brokers (replicas). Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, ```console -helm install my-release -f values.yaml bitnami/kafka +$ helm install my-release -f values.yaml my-repo/kafka ``` > **Tip**: You can use the default [values.yaml](values.yaml) @@ -293,121 +519,20 @@ It is strongly recommended to use immutable tags in a production environment. Th Bitnami will release a new chart updating its containers if a new version of the main container, significant changes, or critical vulnerabilities exist. -### Production configuration and horizontal scaling - -This chart includes a `values-production.yaml` file where you can find some parameters oriented to production configuration in comparison to the regular `values.yaml`. You can use this file instead of the default one. - -- Number of Kafka nodes: - -```diff -- replicaCount: 1 -+ replicaCount: 3 -``` - -- Allow to use the PLAINTEXT listener: - -```diff -- allowPlaintextListener: true -+ allowPlaintextListener: false -``` - -- Default replication factors for automatically created topics: - -```diff -- defaultReplicationFactor: 1 -+ defaultReplicationFactor: 3 -``` - -- Allow auto creation of topics. - -```diff -- autoCreateTopicsEnable: true -+ autoCreateTopicsEnable: false -``` - -- The replication factor for the offsets topic: - -```diff -- offsetsTopicReplicationFactor: 1 -+ offsetsTopicReplicationFactor: 3 -``` - -- The replication factor for the transaction topic: - -```diff -- transactionStateLogReplicationFactor: 1 -+ transactionStateLogReplicationFactor: 3 -``` - -- Overridden min.insync.replicas config for the transaction topic: - -```diff -- transactionStateLogMinIsr: 1 -+ transactionStateLogMinIsr: 3 -``` - -- Switch to enable the Kafka SASAL authentication on client and inter-broker communications: - -```diff -- auth.clientProtocol: plaintext -+ auth.clientProtocol: sasl -- auth.interBrokerProtocol: plaintext -+ auth.interBrokerProtocol: sasl -``` - -- Enable Zookeeper authentication: - -```diff -+ auth.jaas.zookeeperUser: zookeeperUser -+ auth.jaas.zookeeperPassword: zookeeperPassword -- zookeeper.auth.enabled: false -+ zookeeper.auth.enabled: true -+ zookeeper.auth.clientUser: zookeeperUser -+ zookeeper.auth.clientPassword: zookeeperPassword -+ zookeeper.auth.serverUsers: zookeeperUser -+ zookeeper.auth.serverPasswords: zookeeperPassword -``` - -- Enable Pod Disruption Budget: - -```diff -- pdb.create: false -+ pdb.create: true -``` - -- Create a separate Kafka metrics exporter: - -```diff -- metrics.kafka.enabled: false -+ metrics.kafka.enabled: true -``` - -- Expose JMX metrics to Prometheus: - -```diff -- metrics.jmx.enabled: false -+ metrics.jmx.enabled: true -``` - -- Enable Zookeeper metrics: - -```diff -+ zookeeper.metrics.enabled: true -``` - -To horizontally scale this chart once it has been deployed, you can upgrade the statefulset using a new value for the `replicaCount` parameter. Please note that, when enabling TLS encryption, you must update your JKS secret including the keystore for the new replicas. - ### Setting custom parameters Any environment variable beginning with `KAFKA_CFG_` will be mapped to its corresponding Kafka key. For example, use `KAFKA_CFG_BACKGROUND_THREADS` in order to set `background.threads`. In order to pass custom environment variables use the `extraEnvVars` property. +Using `extraEnvVars` with `KAFKA_CFG_` is the preferred and simplest way to add custom Kafka parameters not otherwise specified in this chart. Alternatively, you can provide a *full* Kafka configuration using `config` or `existingConfigmap`. +Setting either `config` or `existingConfigmap` will cause the chart to disregard `KAFKA_CFG_` settings, which are used by many other Kafka-related chart values described above, as well as dynamically generated parameters such as `zookeeper.connect`. This can cause unexpected behavior. + ### Listeners configuration This chart allows you to automatically configure Kafka with 3 listeners: - One for inter-broker communications. - A second one for communications with clients within the K8s cluster. -- (optional) a third listener for communications with clients outside the K8s cluster. Check [this section](#accessing-kafka-brokers-from-outside-the-clusters) for more information. +- (optional) a third listener for communications with clients outside the K8s cluster. Check [this section](#accessing-kafka-brokers-from-outside-the-cluster) for more information. For more complex configurations, set the `listeners`, `advertisedListeners` and `listenerSecurityProtocolMap` parameters as needed. @@ -415,33 +540,43 @@ For more complex configurations, set the `listeners`, `advertisedListeners` and You can configure different authentication protocols for each listener you configure in Kafka. For instance, you can use `sasl_tls` authentication for client communications, while using `tls` for inter-broker communications. This table shows the available protocols and the security they provide: -| Method | Authentication | Encryption via TLS | -|-----------|-------------------------------|--------------------| -| plaintext | None | No | -| tls | None | Yes | -| mtls | Yes (two-way authentication) | Yes | -| sasl | Yes (via SASL) | No | -| sasl_tls | Yes (via SASL) | Yes | +| Method | Authentication | Encryption via TLS | +|-----------|------------------------------|--------------------| +| plaintext | None | No | +| tls | None | Yes | +| mtls | Yes (two-way authentication) | Yes | +| sasl | Yes (via SASL) | No | +| sasl_tls | Yes (via SASL) | Yes | + +Learn more about how to configure Kafka to use the different authentication protocols in the [chart documentation](https://docs.bitnami.com/kubernetes/infrastructure/kafka/administration/enable-security/). If you enabled SASL authentication on any listener, you can set the SASL credentials using the parameters below: -- `auth.jaas.clientUsers`/`auth.jaas.clientPasswords`: when enabling SASL authentication for communications with clients. -- `auth.jaas.interBrokerUser`/`auth.jaas.interBrokerPassword`: when enabling SASL authentication for inter-broker communications. +- `auth.sasl.jaas.clientUsers`/`auth.sasl.jaas.clientPasswords`: when enabling SASL authentication for communications with clients. +- `auth.sasl.jaas.interBrokerUser`/`auth.sasl.jaas.interBrokerPassword`: when enabling SASL authentication for inter-broker communications. - `auth.jaas.zookeeperUser`/`auth.jaas.zookeeperPassword`: In the case that the Zookeeper chart is deployed with SASL authentication enabled. -In order to configure TLS authentication/encryption, you **must** create a secret containing the Java Key Stores (JKS) files: the truststore (`kafka.truststore.jks`) and one keystore (`kafka.keystore.jks`) per Kafka broker you have in the cluster. Then, you need pass the secret name with the `--auth.jksSecret` parameter when deploying the chart. +In order to configure TLS authentication/encryption, you **can** create a secret per Kafka broker you have in the cluster containing the Java Key Stores (JKS) files: the truststore (`kafka.truststore.jks`) and the keystore (`kafka.keystore.jks`). Then, you need pass the secret names with the `auth.tls.existingSecrets` parameter when deploying the chart. -> **Note**: If the JKS files are password protected (recommended), you will need to provide the password to get access to the keystores. To do so, use the `auth.jksPassword` parameter to provide your password. +> **Note**: If the JKS files are password protected (recommended), you will need to provide the password to get access to the keystores. To do so, use the `auth.tls.password` parameter to provide your password. -For instance, to configure TLS authentication on a Kafka cluster with 2 Kafka brokers use the command below to create the secret: +For instance, to configure TLS authentication on a Kafka cluster with 2 Kafka brokers use the commands below to create the secrets: ```console -kubectl create secret generic kafka-jks --from-file=./kafka.truststore.jks --from-file=./kafka-0.keystore.jks --from-file=./kafka-1.keystore.jks +$ kubectl create secret generic kafka-jks-0 --from-file=kafka.truststore.jks=./kafka.truststore.jks --from-file=kafka.keystore.jks=./kafka-0.keystore.jks +$ kubectl create secret generic kafka-jks-1 --from-file=kafka.truststore.jks=./kafka.truststore.jks --from-file=kafka.keystore.jks=./kafka-1.keystore.jks ``` -> **Note**: the command above assumes you already created the trustore and keystores files. This [script](https://raw.githubusercontent.com/confluentinc/confluent-platform-security-tools/master/kafka-generate-ssl.sh) can help you with the JKS files generation. +> **Note**: the command above assumes you already created the truststore and keystores files. This [script](https://raw.githubusercontent.com/confluentinc/confluent-platform-security-tools/master/kafka-generate-ssl.sh) can help you with the JKS files generation. -As an alternative to manually create the secret before installing the chart, you can put your JKS files inside the chart folder `files/jks`, an a secret including them will be generated. Please note this alternative requires to have the chart downloaded locally, so you will have to clone this repository or fetch the chart before installing it. +If, for some reason (like using Cert-Manager) you can not use the default JKS secret scheme, you can use the additional parameters: + +- `auth.tls.jksTruststoreSecret` to define additional secret, where the `kafka.truststore.jks` is being kept. The truststore password **must** be the same as in `auth.tls.password` +- `auth.tls.jksTruststore` to overwrite the default value of the truststore key (`kafka.truststore.jks`). +- `auth.tls.jksKeystoreSAN` if you want to use a SAN certificate for your brokers. Setting this parameter would mean that the chart expects a existing key in the `auth.tls.jksTruststoreSecret` with the `auth.tls.jksKeystoreSAN` value and use this as a keystore for **all** brokers +> **Note**: If you are using cert-manager, particularly when an ACME issuer is used, the `ca.crt` field is not put in the `Secret` that cert-manager creates. To handle this, the `auth.tls.pemChainIncluded` property can be set to `true` and the initContainer created by this Chart will attempt to extract the intermediate certs from the `tls.crt` field of the secret (which is a PEM chain) + +> **Note**: The truststore/keystore from above **must** be protected with the same password as in `auth.tls.password` You can deploy the chart with authentication using the following parameters: @@ -449,12 +584,13 @@ You can deploy the chart with authentication using the following parameters: replicaCount=2 auth.clientProtocol=sasl auth.interBrokerProtocol=tls -auth.certificatesSecret=kafka-jks -auth.certificatesPassword=jksPassword -auth.jaas.clientUsers[0]=brokerUser -auth.jaas.clientPassword[0]=brokerPassword -auth.jaas.zookeeperUser=zookeeperUser -auth.jaas.zookeeperPassword=zookeeperPassword +auth.tls.existingSecrets[0]=kafka-jks-0 +auth.tls.existingSecrets[1]=kafka-jks-1 +auth.tls.password=jksPassword +auth.sasl.jaas.clientUsers[0]=brokerUser +auth.sasl.jaas.clientPasswords[0]=brokerPassword +auth.sasl.jaas.zookeeperUser=zookeeperUser +auth.sasl.jaas.zookeeperPassword=zookeeperPassword zookeeper.auth.enabled=true zookeeper.auth.serverUsers=zookeeperUser zookeeper.auth.serverPasswords=zookeeperPassword @@ -462,7 +598,36 @@ zookeeper.auth.clientUser=zookeeperUser zookeeper.auth.clientPassword=zookeeperPassword ``` -If you also enable exposing metrics using the Kafka expoter, and you are using `sasl_tls`, `tls`, or `mtls` authentication protocols, you need to mount the CA certificated used to sign the brokers certificates in the exporter so it can validate the Kafka brokers. To do so, create a secret containing the CA, and set the `metrics.certificatesSecret` parameter. As an alternative, you can skip TLS validation using extra flags: +You can deploy the chart with AclAuthorizer using the following parameters: + +```console +replicaCount=2 +auth.clientProtocol=sasl +auth.interBrokerProtocol=sasl_tls +auth.tls.existingSecrets[0]=kafka-jks-0 +auth.tls.existingSecrets[1]=kafka-jks-1 +auth.tls.password=jksPassword +auth.sasl.jaas.clientUsers[0]=brokerUser +auth.sasl.jaas.clientPasswords[0]=brokerPassword +auth.sasl.jaas.zookeeperUser=zookeeperUser +auth.sasl.jaas.zookeeperPassword=zookeeperPassword +zookeeper.auth.enabled=true +zookeeper.auth.serverUsers=zookeeperUser +zookeeper.auth.serverPasswords=zookeeperPassword +zookeeper.auth.clientUser=zookeeperUser +zookeeper.auth.clientPassword=zookeeperPassword +authorizerClassName=kafka.security.authorizer.AclAuthorizer +allowEveryoneIfNoAclFound=false +superUsers=User:admin +``` + +If you are using Kafka ACLs, you might encounter in kafka-authorizer.log the following event: `[...] Principal = User:ANONYMOUS is Allowed Operation [...]`. + +By setting the following parameter: `auth.clientProtocol=mtls`, it will set the configuration in Kafka to `ssl.client.auth=required`. This option will require the clients to authenticate to Kafka brokers. + +As result, we will be able to see in kafka-authorizer.log the events specific Subject: `[...] Principal = User:CN=kafka,OU=...,O=...,L=...,C=..,ST=... is [...]`. + +If you also enable exposing metrics using the Kafka exporter, and you are using `sasl_tls`, `tls`, or `mtls` authentication protocols, you need to mount the CA certificated used to sign the brokers certificates in the exporter so it can validate the Kafka brokers. To do so, create a secret containing the CA, and set the `metrics.certificatesSecret` parameter. As an alternative, you can skip TLS validation using extra flags: ```console metrics.kafka.extraFlags={tls.insecure-skip-tls-verify: ""} @@ -472,7 +637,7 @@ metrics.kafka.extraFlags={tls.insecure-skip-tls-verify: ""} In order to access Kafka Brokers from outside the cluster, an additional listener and advertised listener must be configured. Additionally, a specific service per kafka pod will be created. -There are two ways of configuring external access. Using LoadBalancer services or using NodePort services. +There are three ways of configuring external access. Using LoadBalancer services, using NodePort services or using ClusterIP services. #### Using LoadBalancer services @@ -483,7 +648,7 @@ You have two alternatives to use LoadBalancer services: ```console externalAccess.enabled=true externalAccess.service.type=LoadBalancer -externalAccess.service.port=9094 +externalAccess.service.ports.external=9094 externalAccess.autoDiscovery.enabled=true serviceAccount.create=true rbac.create=true @@ -496,13 +661,15 @@ Note: This option requires creating RBAC rules on clusters where RBAC policies a ```console externalAccess.enabled=true externalAccess.service.type=LoadBalancer -externalAccess.service.port=9094 +externalAccess.service.ports.external=9094 externalAccess.service.loadBalancerIPs[0]='external-ip-1' externalAccess.service.loadBalancerIPs[1]='external-ip-2'} ``` Note: You need to know in advance the load balancer IPs so each Kafka broker advertised listener is configured with it. +Following the aforementioned steps will also allow to connect the brokers from the outside using the cluster's default service (when `service.type` is `LoadBalancer` or `NodePort`). Use the property `service.externalPort` to specify the port used for external connections. + #### Using NodePort services You have two alternatives to use NodePort services: @@ -524,15 +691,44 @@ Note: This option requires creating RBAC rules on clusters where RBAC policies a ```console externalAccess.enabled=true externalAccess.service.type=NodePort -externalAccess.serivce.nodePorts[0]='node-port-1' -externalAccess.serivce.nodePorts[1]='node-port-2' +externalAccess.service.nodePorts[0]='node-port-1' +externalAccess.service.nodePorts[1]='node-port-2' ``` Note: You need to know in advance the node ports that will be exposed so each Kafka broker advertised listener is configured with it. -The pod will try to get the external ip of the node using `curl -s https://ipinfo.io/ip` unless `externalAccess.service.domain` is provided. +The pod will try to get the external ip of the node using `curl -s https://ipinfo.io/ip` unless `externalAccess.service.domain` or `externalAccess.service.useHostIPs` is provided. -Following the aforementioned steps will also allow to connect the brokers from the outside using the cluster's default service (when `service.type` is `LoadBalancer` or `NodePort`). Use the property `service.externalPort` to specify the port used for external connections. +#### Using ClusterIP services + +Note: This option requires that an ingress is deployed within your cluster + +```console +externalAccess.enabled=true +externalAccess.service.type=ClusterIP +externalAccess.service.ports.external=9094 +externalAccess.service.domain='ingress-ip' +``` + +Note: the deployed ingress must contain the following block: + +```console +tcp: + 9094: "{{ .Release.Namespace }}/{{ include "kafka.fullname" . }}-0-external:9094" + 9095: "{{ .Release.Namespace }}/{{ include "kafka.fullname" . }}-1-external:9094" + 9096: "{{ .Release.Namespace }}/{{ include "kafka.fullname" . }}-2-external:9094" +``` + +#### Name resolution with External-DNS + +You can use the following values to generate External-DNS annotations which automatically creates DNS records for each ReplicaSet pod: + +```yaml +externalAccess: + service: + annotations: + external-dns.alpha.kubernetes.io/hostname: "{{ .targetPod }}.example.com" +``` ### Sidecars @@ -548,28 +744,35 @@ sidecars: containerPort: 1234 ``` +### Setting Pod's affinity + +This chart allows you to set your custom affinity using the `affinity` parameter. Find more information about Pod's affinity in the [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity). + +As an alternative, you can use of the preset configurations for pod affinity, pod anti-affinity, and node affinity available at the [bitnami/common](https://github.com/bitnami/charts/tree/main/bitnami/common#affinities) chart. To do so, set the `podAffinityPreset`, `podAntiAffinityPreset`, or `nodeAffinityPreset` parameters. + ### Deploying extra resources -There are cases where you may want to deploy extra objects, such as Kafka Connect. For covering this case, the chart allows adding the full specification of other objects using the `extraDeploy` parameter. The following example would create a deployment including a Kafka Connect deployment so you can connect Kafka with MongoDB: +There are cases where you may want to deploy extra objects, such as Kafka Connect. For covering this case, the chart allows adding the full specification of other objects using the `extraDeploy` parameter. The following example would create a deployment including a Kafka Connect deployment so you can connect Kafka with MongoDB®: ```yaml ## Extra objects to deploy (value evaluated as a template) ## -extraDeploy: |- - - apiVersion: apps/v1 +extraDeploy: + - | + apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "kafka.fullname" . }}-connect - labels: {{- include "kafka.labels" . | nindent 6 }} + labels: {{- include "common.labels.standard" . | nindent 4 }} app.kubernetes.io/component: connector spec: replicas: 1 selector: - matchLabels: {{- include "kafka.matchLabels" . | nindent 8 }} + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} app.kubernetes.io/component: connector template: metadata: - labels: {{- include "kafka.labels" . | nindent 10 }} + labels: {{- include "common.labels.standard" . | nindent 8 }} app.kubernetes.io/component: connector spec: containers: @@ -581,16 +784,17 @@ extraDeploy: |- containerPort: 8083 volumeMounts: - name: configuration - mountPath: /opt/bitnami/kafka/config + mountPath: /bitnami/kafka/config volumes: - name: configuration configMap: name: {{ include "kafka.fullname" . }}-connect - - apiVersion: v1 + - | + apiVersion: v1 kind: ConfigMap metadata: name: {{ include "kafka.fullname" . }}-connect - labels: {{- include "kafka.labels" . | nindent 6 }} + labels: {{- include "common.labels.standard" . | nindent 4 }} app.kubernetes.io/component: connector data: connect-standalone.properties: |- @@ -599,18 +803,19 @@ extraDeploy: |- mongodb.properties: |- connection.uri=mongodb://root:password@mongodb-hostname:27017 ... - - apiVersion: v1 + - | + apiVersion: v1 kind: Service metadata: name: {{ include "kafka.fullname" . }}-connect - labels: {{- include "kafka.labels" . | nindent 6 }} + labels: {{- include "common.labels.standard" . | nindent 4 }} app.kubernetes.io/component: connector spec: ports: - protocol: TCP port: 8083 targetPort: connector - selector: {{- include "kafka.matchLabels" . | nindent 6 }} + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} app.kubernetes.io/component: connector ``` @@ -618,7 +823,7 @@ You can create the Kafka Connect image using the Dockerfile below: ```Dockerfile FROM bitnami/kafka:latest -# Download MongoDB Connector for Apache Kafka https://www.confluent.io/hub/mongodb/kafka-connect-mongodb +# Download MongoDB® Connector for Apache Kafka https://www.confluent.io/hub/mongodb/kafka-connect-mongodb RUN mkdir -p /opt/bitnami/kafka/plugins && \ cd /opt/bitnami/kafka/plugins && \ curl --remote-name --location --silent https://search.maven.org/remotecontent?filepath=org/mongodb/kafka/mongo-kafka-connect/1.2.0/mongo-kafka-connect-1.2.0-all.jar @@ -627,7 +832,7 @@ CMD /opt/bitnami/kafka/bin/connect-standalone.sh /opt/bitnami/kafka/config/conne ## Persistence -The [Bitnami Kafka](https://github.com/bitnami/bitnami-docker-kafka) image stores the Kafka data at the `/bitnami/kafka` path of the container. +The [Bitnami Kafka](https://github.com/bitnami/containers/tree/main/bitnami/kafka) image stores the Kafka data at the `/bitnami/kafka` path of the container. Persistent Volume Claims are used to keep the data across deployments. This is known to work in GCE, AWS, and minikube. See the [Parameters](#persistence-parameters) section to configure the PVC or to disable persistence. @@ -640,11 +845,103 @@ As an alternative, this chart supports using an initContainer to change the owne You can enable this initContainer by setting `volumePermissions.enabled` to `true`. +## Troubleshooting + +Find more information about how to deal with common errors related to Bitnami's Helm charts in [this troubleshooting guide](https://docs.bitnami.com/general/how-to/troubleshoot-helm-chart-issues). + ## Upgrading +### To 20.0.0 + +This major updates the Zookeeper subchart to it newest major, 11.0.0. For more information on this subchart's major, please refer to [zookeeper upgrade notes](https://github.com/bitnami/charts/tree/main/bitnami/zookeeper#to-1100). + +### To 19.0.0 + +This major updates Kafka to its newest version, 3.3.x. For more information, please refer to [kafka upgrade notes](https://kafka.apache.org/33/documentation.html#upgrade). + +### To 18.0.0 + +This major updates the Zookeeper subchart to it newest major, 10.0.0. For more information on this subchart's major, please refer to [zookeeper upgrade notes](https://github.com/bitnami/charts/tree/main/bitnami/zookeeper#to-1000). + +### To 16.0.0 + +This major updates the Zookeeper subchart to it newest major, 9.0.0. For more information on this subchart's major, please refer to [zookeeper upgrade notes](https://github.com/bitnami/charts/tree/main/bitnami/zookeeper#to-900). + +### To 15.0.0 + +This major release bumps Kafka major version to `3.x` series. +It also renames several values in this chart and adds missing features, in order to be inline with the rest of assets in the Bitnami charts repository. Some affected values are: + +- `service.port`, `service.internalPort` and `service.externalPort` have been regrouped under the `service.ports` map. +- `metrics.kafka.service.port` has been regrouped under the `metrics.kafka.service.ports` map. +- `metrics.jmx.service.port` has been regrouped under the `metrics.jmx.service.ports` map. +- `updateStrategy` (string) and `rollingUpdatePartition` are regrouped under the `updateStrategy` map. +- Several parameters marked as deprecated `14.x.x` are not supported anymore. + +Additionally updates the ZooKeeper subchart to it newest major, `8.0.0`, which contains similar changes. + +### To 14.0.0 + +In this version, the `image` block is defined once and is used in the different templates, while in the previous version, the `image` block was duplicated for the main container and the provisioning one + +```yaml +image: + registry: docker.io + repository: bitnami/kafka + tag: 2.8.0 +``` + +VS + +```yaml +image: + registry: docker.io + repository: bitnami/kafka + tag: 2.8.0 +... +provisioning: + image: + registry: docker.io + repository: bitnami/kafka + tag: 2.8.0 +``` + +See [PR#7114](https://github.com/bitnami/charts/pull/7114) for more info about the implemented changes + +### To 13.0.0 + +This major updates the Zookeeper subchart to it newest major, 7.0.0, which renames all TLS-related settings. For more information on this subchart's major, please refer to [zookeeper upgrade notes](https://github.com/bitnami/charts/tree/main/bitnami/zookeeper#to-700). + +### To 12.2.0 + +This version also introduces `bitnami/common`, a [library chart](https://helm.sh/docs/topics/library_charts/#helm) as a dependency. More documentation about this new utility could be found [here](https://github.com/bitnami/charts/tree/main/bitnami/common#bitnami-common-library-chart). Please, make sure that you have updated the chart dependencies before executing any upgrade. + +### To 12.0.0 + +[On November 13, 2020, Helm v2 support was formally finished](https://github.com/helm/charts#status-of-the-project), this major version is the result of the required changes applied to the Helm Chart to be able to incorporate the different features added in Helm v3 and to be consistent with the Helm project itself regarding the Helm v2 EOL. + +**What changes were introduced in this major version?** + +- Previous versions of this Helm Chart use `apiVersion: v1` (installable by both Helm 2 and 3), this Helm Chart was updated to `apiVersion: v2` (installable by Helm 3 only). [Here](https://helm.sh/docs/topics/charts/#the-apiversion-field) you can find more information about the `apiVersion` field. +- Move dependency information from the *requirements.yaml* to the *Chart.yaml* +- After running `helm dependency update`, a *Chart.lock* file is generated containing the same structure used in the previous *requirements.lock* +- The different fields present in the *Chart.yaml* file has been ordered alphabetically in a homogeneous way for all the Bitnami Helm Charts + +**Considerations when upgrading to this version** + +- If you want to upgrade to this version from a previous one installed with Helm v3, you shouldn't face any issues +- If you want to upgrade to this version using Helm v2, this scenario is not supported as this version doesn't support Helm v2 anymore +- If you installed the previous version with Helm v2 and wants to upgrade to this version with Helm v3, please refer to the [official Helm documentation](https://helm.sh/docs/topics/v2_v3_migration/#migration-use-cases) about migrating from Helm v2 to v3 + +**Useful links** + +- https://docs.bitnami.com/tutorials/resolve-helm2-helm3-post-migration-issues/ +- https://helm.sh/docs/topics/v2_v3_migration/ +- https://helm.sh/blog/migrate-from-helm-v2-to-helm-v3/ + ### To 11.8.0 -External access to brokers can now be archived through the cluster's Kafka service. +External access to brokers can now be achieved through the cluster's Kafka service. - `service.nodePort` -> deprecated in favor of `service.nodePorts.client` and `service.nodePorts.external` @@ -657,7 +954,7 @@ The way to configure the users and passwords changed. Now it is allowed to creat ### To 11.0.0 -The way to configure listeners and authentication on Kafka is totally refactored allowing users to configure different authentication protocols on different listeners. Please check the sections [Listeners Configuration](listeners-configuration) and [Listeners Configuration](enable-kafka-for-kafka-and-zookeeper) for more information. +The way to configure listeners and athentication on Kafka is totally refactored allowing users to configure different authentication protocols on different listeners. Please check the [Listeners Configuration](#listeners-configuration) section for more information. Backwards compatibility is not guaranteed you adapt your values.yaml to the new format. Here you can find some parameters that were renamed or disappeared in favor of new ones on this major version: @@ -712,8 +1009,8 @@ Backwards compatibility is not guaranteed when Kafka metrics are enabled, unless Use the workaround below to upgrade from versions previous to 7.0.0. The following example assumes that the release name is kafka: ```console -helm upgrade kafka bitnami/kafka --version 6.1.8 --set metrics.kafka.enabled=false -helm upgrade kafka bitnami/kafka --version 7.0.0 --set metrics.kafka.enabled=true +$ helm upgrade kafka my-repo/kafka --version 6.1.8 --set metrics.kafka.enabled=false +$ helm upgrade kafka my-repo/kafka --version 7.0.0 --set metrics.kafka.enabled=true ``` ### To 2.0.0 @@ -722,8 +1019,8 @@ Backwards compatibility is not guaranteed unless you modify the labels used on t Use the workaround below to upgrade from versions previous to 2.0.0. The following example assumes that the release name is kafka: ```console -kubectl delete statefulset kafka-kafka --cascade=false -kubectl delete statefulset kafka-zookeeper --cascade=false +$ kubectl delete statefulset kafka-kafka --cascade=false +$ kubectl delete statefulset kafka-zookeeper --cascade=false ``` ### To 1.0.0 @@ -732,6 +1029,22 @@ Backwards compatibility is not guaranteed unless you modify the labels used on t Use the workaround below to upgrade from versions previous to 1.0.0. The following example assumes that the release name is kafka: ```console -kubectl delete statefulset kafka-kafka --cascade=false -kubectl delete statefulset kafka-zookeeper --cascade=false +$ kubectl delete statefulset kafka-kafka --cascade=false +$ kubectl delete statefulset kafka-zookeeper --cascade=false ``` + +## License + +Copyright © 2022 Bitnami + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/.helmignore b/scripts/helmcharts/databases/charts/kafka/charts/common/.helmignore new file mode 100644 index 000000000..50af03172 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/Chart.yaml b/scripts/helmcharts/databases/charts/kafka/charts/common/Chart.yaml new file mode 100644 index 000000000..f9ba944c8 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/Chart.yaml @@ -0,0 +1,23 @@ +annotations: + category: Infrastructure +apiVersion: v2 +appVersion: 2.2.2 +description: A Library Helm Chart for grouping common logic between bitnami charts. + This chart is not deployable by itself. +home: https://github.com/bitnami/charts/tree/main/bitnami/common +icon: https://bitnami.com/downloads/logos/bitnami-mark.png +keywords: +- common +- helper +- template +- function +- bitnami +maintainers: +- name: Bitnami + url: https://github.com/bitnami/charts +name: common +sources: +- https://github.com/bitnami/charts +- https://www.bitnami.com/ +type: library +version: 2.2.2 diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/README.md b/scripts/helmcharts/databases/charts/kafka/charts/common/README.md new file mode 100644 index 000000000..ec43a5fab --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/README.md @@ -0,0 +1,351 @@ +# Bitnami Common Library Chart + +A [Helm Library Chart](https://helm.sh/docs/topics/library_charts/#helm) for grouping common logic between bitnami charts. + +## TL;DR + +```yaml +dependencies: + - name: common + version: 1.x.x + repository: https://charts.bitnami.com/bitnami +``` + +```bash +$ helm dependency update +``` + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "common.names.fullname" . }} +data: + myvalue: "Hello World" +``` + +## Introduction + +This chart provides a common template helpers which can be used to develop new charts using [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.dev/) for deployment and management of Helm Charts in clusters. + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.2.0+ + +## Parameters + +The following table lists the helpers available in the library which are scoped in different sections. + +### Affinities + +| Helper identifier | Description | Expected Input | +|-------------------------------|------------------------------------------------------|------------------------------------------------| +| `common.affinities.nodes.soft` | Return a soft nodeAffinity definition | `dict "key" "FOO" "values" (list "BAR" "BAZ")` | +| `common.affinities.nodes.hard` | Return a hard nodeAffinity definition | `dict "key" "FOO" "values" (list "BAR" "BAZ")` | +| `common.affinities.pods.soft` | Return a soft podAffinity/podAntiAffinity definition | `dict "component" "FOO" "context" $` | +| `common.affinities.pods.hard` | Return a hard podAffinity/podAntiAffinity definition | `dict "component" "FOO" "context" $` | +| `common.affinities.topologyKey` | Return a topologyKey definition | `dict "topologyKey" "FOO"` | + +### Capabilities + +| Helper identifier | Description | Expected Input | +|------------------------------------------------|------------------------------------------------------------------------------------------------|-------------------| +| `common.capabilities.kubeVersion` | Return the target Kubernetes version (using client default if .Values.kubeVersion is not set). | `.` Chart context | +| `common.capabilities.cronjob.apiVersion` | Return the appropriate apiVersion for cronjob. | `.` Chart context | +| `common.capabilities.deployment.apiVersion` | Return the appropriate apiVersion for deployment. | `.` Chart context | +| `common.capabilities.statefulset.apiVersion` | Return the appropriate apiVersion for statefulset. | `.` Chart context | +| `common.capabilities.ingress.apiVersion` | Return the appropriate apiVersion for ingress. | `.` Chart context | +| `common.capabilities.rbac.apiVersion` | Return the appropriate apiVersion for RBAC resources. | `.` Chart context | +| `common.capabilities.crd.apiVersion` | Return the appropriate apiVersion for CRDs. | `.` Chart context | +| `common.capabilities.policy.apiVersion` | Return the appropriate apiVersion for podsecuritypolicy. | `.` Chart context | +| `common.capabilities.networkPolicy.apiVersion` | Return the appropriate apiVersion for networkpolicy. | `.` Chart context | +| `common.capabilities.apiService.apiVersion` | Return the appropriate apiVersion for APIService. | `.` Chart context | +| `common.capabilities.hpa.apiVersion` | Return the appropriate apiVersion for Horizontal Pod Autoscaler | `.` Chart context | +| `common.capabilities.supportsHelmVersion` | Returns true if the used Helm version is 3.3+ | `.` Chart context | + +### Errors + +| Helper identifier | Description | Expected Input | +|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------| +| `common.errors.upgrade.passwords.empty` | It will ensure required passwords are given when we are upgrading a chart. If `validationErrors` is not empty it will throw an error and will stop the upgrade action. | `dict "validationErrors" (list $validationError00 $validationError01) "context" $` | + +### Images + +| Helper identifier | Description | Expected Input | +|-----------------------------|------------------------------------------------------|---------------------------------------------------------------------------------------------------------| +| `common.images.image` | Return the proper and full image name | `dict "imageRoot" .Values.path.to.the.image "global" $`, see [ImageRoot](#imageroot) for the structure. | +| `common.images.pullSecrets` | Return the proper Docker Image Registry Secret Names (deprecated: use common.images.renderPullSecrets instead) | `dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global` | +| `common.images.renderPullSecrets` | Return the proper Docker Image Registry Secret Names (evaluates values as templates) | `dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "context" $` | + +### Ingress + +| Helper identifier | Description | Expected Input | +|-------------------------------------------|-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.ingress.backend` | Generate a proper Ingress backend entry depending on the API version | `dict "serviceName" "foo" "servicePort" "bar"`, see the [Ingress deprecation notice](https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/) for the syntax differences | +| `common.ingress.supportsPathType` | Prints "true" if the pathType field is supported | `.` Chart context | +| `common.ingress.supportsIngressClassname` | Prints "true" if the ingressClassname field is supported | `.` Chart context | +| `common.ingress.certManagerRequest` | Prints "true" if required cert-manager annotations for TLS signed certificates are set in the Ingress annotations | `dict "annotations" .Values.path.to.the.ingress.annotations` | + +### Labels + +| Helper identifier | Description | Expected Input | +|-----------------------------|-----------------------------------------------------------------------------|-------------------| +| `common.labels.standard` | Return Kubernetes standard labels | `.` Chart context | +| `common.labels.matchLabels` | Labels to use on `deploy.spec.selector.matchLabels` and `svc.spec.selector` | `.` Chart context | + +### Names + +| Helper identifier | Description | Expected Input | +|-----------------------------------|-----------------------------------------------------------------------|-------------------| +| `common.names.name` | Expand the name of the chart or use `.Values.nameOverride` | `.` Chart context | +| `common.names.fullname` | Create a default fully qualified app name. | `.` Chart context | +| `common.names.namespace` | Allow the release namespace to be overridden | `.` Chart context | +| `common.names.fullname.namespace` | Create a fully qualified app name adding the installation's namespace | `.` Chart context | +| `common.names.chart` | Chart name plus version | `.` Chart context | + +### Secrets + +| Helper identifier | Description | Expected Input | +|-----------------------------------|--------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.secrets.name` | Generate the name of the secret. | `dict "existingSecret" .Values.path.to.the.existingSecret "defaultNameSuffix" "mySuffix" "context" $` see [ExistingSecret](#existingsecret) for the structure. | +| `common.secrets.key` | Generate secret key. | `dict "existingSecret" .Values.path.to.the.existingSecret "key" "keyName"` see [ExistingSecret](#existingsecret) for the structure. | +| `common.secrets.passwords.manage` | Generate secret password or retrieve one if already created. | `dict "secret" "secret-name" "key" "keyName" "providedValues" (list "path.to.password1" "path.to.password2") "length" 10 "strong" false "chartName" "chartName" "context" $`, length, strong and chartNAme fields are optional. | +| `common.secrets.exists` | Returns whether a previous generated secret already exists. | `dict "secret" "secret-name" "context" $` | + +### Storage + +| Helper identifier | Description | Expected Input | +|-------------------------------|---------------------------------------|---------------------------------------------------------------------------------------------------------------------| +| `common.storage.class` | Return the proper Storage Class | `dict "persistence" .Values.path.to.the.persistence "global" $`, see [Persistence](#persistence) for the structure. | + +### TplValues + +| Helper identifier | Description | Expected Input | +|---------------------------|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.tplvalues.render` | Renders a value that contains template | `dict "value" .Values.path.to.the.Value "context" $`, value is the value should rendered as template, context frequently is the chart context `$` or `.` | + +### Utils + +| Helper identifier | Description | Expected Input | +|--------------------------------|------------------------------------------------------------------------------------------|------------------------------------------------------------------------| +| `common.utils.fieldToEnvVar` | Build environment variable name given a field. | `dict "field" "my-password"` | +| `common.utils.secret.getvalue` | Print instructions to get a secret value. | `dict "secret" "secret-name" "field" "secret-value-field" "context" $` | +| `common.utils.getValueFromKey` | Gets a value from `.Values` object given its key path | `dict "key" "path.to.key" "context" $` | +| `common.utils.getKeyFromList` | Returns first `.Values` key with a defined value or first of the list if all non-defined | `dict "keys" (list "path.to.key1" "path.to.key2") "context" $` | + +### Validations + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.validations.values.single.empty` | Validate a value must not be empty. | `dict "valueKey" "path.to.value" "secret" "secret.name" "field" "my-password" "subchart" "subchart" "context" $` secret, field and subchart are optional. In case they are given, the helper will generate a how to get instruction. See [ValidateValue](#validatevalue) | +| `common.validations.values.multiple.empty` | Validate a multiple values must not be empty. It returns a shared error for all the values. | `dict "required" (list $validateValueConf00 $validateValueConf01) "context" $`. See [ValidateValue](#validatevalue) | +| `common.validations.values.mariadb.passwords` | This helper will ensure required password for MariaDB are not empty. It returns a shared error for all the values. | `dict "secret" "mariadb-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use mariadb chart and the helper. | +| `common.validations.values.mysql.passwords` | This helper will ensure required password for MySQL are not empty. It returns a shared error for all the values. | `dict "secret" "mysql-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use mysql chart and the helper. | +| `common.validations.values.postgresql.passwords` | This helper will ensure required password for PostgreSQL are not empty. It returns a shared error for all the values. | `dict "secret" "postgresql-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use postgresql chart and the helper. | +| `common.validations.values.redis.passwords` | This helper will ensure required password for Redis® are not empty. It returns a shared error for all the values. | `dict "secret" "redis-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use redis chart and the helper. | +| `common.validations.values.cassandra.passwords` | This helper will ensure required password for Cassandra are not empty. It returns a shared error for all the values. | `dict "secret" "cassandra-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use cassandra chart and the helper. | +| `common.validations.values.mongodb.passwords` | This helper will ensure required password for MongoDB® are not empty. It returns a shared error for all the values. | `dict "secret" "mongodb-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use mongodb chart and the helper. | + +### Warnings + +| Helper identifier | Description | Expected Input | +|------------------------------|----------------------------------|------------------------------------------------------------| +| `common.warnings.rollingTag` | Warning about using rolling tag. | `ImageRoot` see [ImageRoot](#imageroot) for the structure. | + +## Special input schemas + +### ImageRoot + +```yaml +registry: + type: string + description: Docker registry where the image is located + example: docker.io + +repository: + type: string + description: Repository and image name + example: bitnami/nginx + +tag: + type: string + description: image tag + example: 1.16.1-debian-10-r63 + +pullPolicy: + type: string + description: Specify a imagePullPolicy. Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + +pullSecrets: + type: array + items: + type: string + description: Optionally specify an array of imagePullSecrets (evaluated as templates). + +debug: + type: boolean + description: Set to true if you would like to see extra information on logs + example: false + +## An instance would be: +# registry: docker.io +# repository: bitnami/nginx +# tag: 1.16.1-debian-10-r63 +# pullPolicy: IfNotPresent +# debug: false +``` + +### Persistence + +```yaml +enabled: + type: boolean + description: Whether enable persistence. + example: true + +storageClass: + type: string + description: Ghost data Persistent Volume Storage Class, If set to "-", storageClassName: "" which disables dynamic provisioning. + example: "-" + +accessMode: + type: string + description: Access mode for the Persistent Volume Storage. + example: ReadWriteOnce + +size: + type: string + description: Size the Persistent Volume Storage. + example: 8Gi + +path: + type: string + description: Path to be persisted. + example: /bitnami + +## An instance would be: +# enabled: true +# storageClass: "-" +# accessMode: ReadWriteOnce +# size: 8Gi +# path: /bitnami +``` + +### ExistingSecret + +```yaml +name: + type: string + description: Name of the existing secret. + example: mySecret +keyMapping: + description: Mapping between the expected key name and the name of the key in the existing secret. + type: object + +## An instance would be: +# name: mySecret +# keyMapping: +# password: myPasswordKey +``` + +#### Example of use + +When we store sensitive data for a deployment in a secret, some times we want to give to users the possibility of using theirs existing secrets. + +```yaml +# templates/secret.yaml +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "common.names.fullname" . }} + labels: + app: {{ include "common.names.fullname" . }} +type: Opaque +data: + password: {{ .Values.password | b64enc | quote }} + +# templates/dpl.yaml +--- +... + env: + - name: PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "common.secrets.name" (dict "existingSecret" .Values.existingSecret "context" $) }} + key: {{ include "common.secrets.key" (dict "existingSecret" .Values.existingSecret "key" "password") }} +... + +# values.yaml +--- +name: mySecret +keyMapping: + password: myPasswordKey +``` + +### ValidateValue + +#### NOTES.txt + +```console +{{- $validateValueConf00 := (dict "valueKey" "path.to.value00" "secret" "secretName" "field" "password-00") -}} +{{- $validateValueConf01 := (dict "valueKey" "path.to.value01" "secret" "secretName" "field" "password-01") -}} + +{{ include "common.validations.values.multiple.empty" (dict "required" (list $validateValueConf00 $validateValueConf01) "context" $) }} +``` + +If we force those values to be empty we will see some alerts + +```console +$ helm install test mychart --set path.to.value00="",path.to.value01="" + 'path.to.value00' must not be empty, please add '--set path.to.value00=$PASSWORD_00' to the command. To get the current value: + + export PASSWORD_00=$(kubectl get secret --namespace default secretName -o jsonpath="{.data.password-00}" | base64 -d) + + 'path.to.value01' must not be empty, please add '--set path.to.value01=$PASSWORD_01' to the command. To get the current value: + + export PASSWORD_01=$(kubectl get secret --namespace default secretName -o jsonpath="{.data.password-01}" | base64 -d) +``` + +## Upgrading + +### To 1.0.0 + +[On November 13, 2020, Helm v2 support was formally finished](https://github.com/helm/charts#status-of-the-project), this major version is the result of the required changes applied to the Helm Chart to be able to incorporate the different features added in Helm v3 and to be consistent with the Helm project itself regarding the Helm v2 EOL. + +**What changes were introduced in this major version?** + +- Previous versions of this Helm Chart use `apiVersion: v1` (installable by both Helm 2 and 3), this Helm Chart was updated to `apiVersion: v2` (installable by Helm 3 only). [Here](https://helm.sh/docs/topics/charts/#the-apiversion-field) you can find more information about the `apiVersion` field. +- Use `type: library`. [Here](https://v3.helm.sh/docs/faq/#library-chart-support) you can find more information. +- The different fields present in the *Chart.yaml* file has been ordered alphabetically in a homogeneous way for all the Bitnami Helm Charts + +**Considerations when upgrading to this version** + +- If you want to upgrade to this version from a previous one installed with Helm v3, you shouldn't face any issues +- If you want to upgrade to this version using Helm v2, this scenario is not supported as this version doesn't support Helm v2 anymore +- If you installed the previous version with Helm v2 and wants to upgrade to this version with Helm v3, please refer to the [official Helm documentation](https://helm.sh/docs/topics/v2_v3_migration/#migration-use-cases) about migrating from Helm v2 to v3 + +**Useful links** + +- https://docs.bitnami.com/tutorials/resolve-helm2-helm3-post-migration-issues/ +- https://helm.sh/docs/topics/v2_v3_migration/ +- https://helm.sh/blog/migrate-from-helm-v2-to-helm-v3/ + +## License + +Copyright © 2022 Bitnami + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_affinities.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_affinities.tpl new file mode 100644 index 000000000..81902a681 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_affinities.tpl @@ -0,0 +1,106 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Return a soft nodeAffinity definition +{{ include "common.affinities.nodes.soft" (dict "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes.soft" -}} +preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: {{ .key }} + operator: In + values: + {{- range .values }} + - {{ . | quote }} + {{- end }} + weight: 1 +{{- end -}} + +{{/* +Return a hard nodeAffinity definition +{{ include "common.affinities.nodes.hard" (dict "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes.hard" -}} +requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: {{ .key }} + operator: In + values: + {{- range .values }} + - {{ . | quote }} + {{- end }} +{{- end -}} + +{{/* +Return a nodeAffinity definition +{{ include "common.affinities.nodes" (dict "type" "soft" "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes" -}} + {{- if eq .type "soft" }} + {{- include "common.affinities.nodes.soft" . -}} + {{- else if eq .type "hard" }} + {{- include "common.affinities.nodes.hard" . -}} + {{- end -}} +{{- end -}} + +{{/* +Return a topologyKey definition +{{ include "common.affinities.topologyKey" (dict "topologyKey" "BAR") -}} +*/}} +{{- define "common.affinities.topologyKey" -}} +{{ .topologyKey | default "kubernetes.io/hostname" -}} +{{- end -}} + +{{/* +Return a soft podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods.soft" (dict "component" "FOO" "extraMatchLabels" .Values.extraMatchLabels "topologyKey" "BAR" "context" $) -}} +*/}} +{{- define "common.affinities.pods.soft" -}} +{{- $component := default "" .component -}} +{{- $extraMatchLabels := default (dict) .extraMatchLabels -}} +preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: {{- (include "common.labels.matchLabels" .context) | nindent 10 }} + {{- if not (empty $component) }} + {{ printf "app.kubernetes.io/component: %s" $component }} + {{- end }} + {{- range $key, $value := $extraMatchLabels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + topologyKey: {{ include "common.affinities.topologyKey" (dict "topologyKey" .topologyKey) }} + weight: 1 +{{- end -}} + +{{/* +Return a hard podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods.hard" (dict "component" "FOO" "extraMatchLabels" .Values.extraMatchLabels "topologyKey" "BAR" "context" $) -}} +*/}} +{{- define "common.affinities.pods.hard" -}} +{{- $component := default "" .component -}} +{{- $extraMatchLabels := default (dict) .extraMatchLabels -}} +requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: {{- (include "common.labels.matchLabels" .context) | nindent 8 }} + {{- if not (empty $component) }} + {{ printf "app.kubernetes.io/component: %s" $component }} + {{- end }} + {{- range $key, $value := $extraMatchLabels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + topologyKey: {{ include "common.affinities.topologyKey" (dict "topologyKey" .topologyKey) }} +{{- end -}} + +{{/* +Return a podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods" (dict "type" "soft" "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.pods" -}} + {{- if eq .type "soft" }} + {{- include "common.affinities.pods.soft" . -}} + {{- else if eq .type "hard" }} + {{- include "common.affinities.pods.hard" . -}} + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_capabilities.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_capabilities.tpl new file mode 100644 index 000000000..9d9b76004 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_capabilities.tpl @@ -0,0 +1,154 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Return the target Kubernetes version +*/}} +{{- define "common.capabilities.kubeVersion" -}} +{{- if .Values.global }} + {{- if .Values.global.kubeVersion }} + {{- .Values.global.kubeVersion -}} + {{- else }} + {{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}} + {{- end -}} +{{- else }} +{{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for poddisruptionbudget. +*/}} +{{- define "common.capabilities.policy.apiVersion" -}} +{{- if semverCompare "<1.21-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "policy/v1beta1" -}} +{{- else -}} +{{- print "policy/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for networkpolicy. +*/}} +{{- define "common.capabilities.networkPolicy.apiVersion" -}} +{{- if semverCompare "<1.7-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for cronjob. +*/}} +{{- define "common.capabilities.cronjob.apiVersion" -}} +{{- if semverCompare "<1.21-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "batch/v1beta1" -}} +{{- else -}} +{{- print "batch/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for deployment. +*/}} +{{- define "common.capabilities.deployment.apiVersion" -}} +{{- if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for statefulset. +*/}} +{{- define "common.capabilities.statefulset.apiVersion" -}} +{{- if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apps/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for ingress. +*/}} +{{- define "common.capabilities.ingress.apiVersion" -}} +{{- if .Values.ingress -}} +{{- if .Values.ingress.apiVersion -}} +{{- .Values.ingress.apiVersion -}} +{{- else if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end }} +{{- else if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for RBAC resources. +*/}} +{{- define "common.capabilities.rbac.apiVersion" -}} +{{- if semverCompare "<1.17-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "rbac.authorization.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "rbac.authorization.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for CRDs. +*/}} +{{- define "common.capabilities.crd.apiVersion" -}} +{{- if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apiextensions.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "apiextensions.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for APIService. +*/}} +{{- define "common.capabilities.apiService.apiVersion" -}} +{{- if semverCompare "<1.10-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apiregistration.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "apiregistration.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for Horizontal Pod Autoscaler. +*/}} +{{- define "common.capabilities.hpa.apiVersion" -}} +{{- if semverCompare "<1.23-0" (include "common.capabilities.kubeVersion" .context) -}} +{{- if .beta2 -}} +{{- print "autoscaling/v2beta2" -}} +{{- else -}} +{{- print "autoscaling/v2beta1" -}} +{{- end -}} +{{- else -}} +{{- print "autoscaling/v2" -}} +{{- end -}} +{{- end -}} + +{{/* +Returns true if the used Helm version is 3.3+. +A way to check the used Helm version was not introduced until version 3.3.0 with .Capabilities.HelmVersion, which contains an additional "{}}" structure. +This check is introduced as a regexMatch instead of {{ if .Capabilities.HelmVersion }} because checking for the key HelmVersion in <3.3 results in a "interface not found" error. +**To be removed when the catalog's minimun Helm version is 3.3** +*/}} +{{- define "common.capabilities.supportsHelmVersion" -}} +{{- if regexMatch "{(v[0-9])*[^}]*}}$" (.Capabilities | toString ) }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_errors.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_errors.tpl new file mode 100644 index 000000000..a79cc2e32 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_errors.tpl @@ -0,0 +1,23 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Through error when upgrading using empty passwords values that must not be empty. + +Usage: +{{- $validationError00 := include "common.validations.values.single.empty" (dict "valueKey" "path.to.password00" "secret" "secretName" "field" "password-00") -}} +{{- $validationError01 := include "common.validations.values.single.empty" (dict "valueKey" "path.to.password01" "secret" "secretName" "field" "password-01") -}} +{{ include "common.errors.upgrade.passwords.empty" (dict "validationErrors" (list $validationError00 $validationError01) "context" $) }} + +Required password params: + - validationErrors - String - Required. List of validation strings to be return, if it is empty it won't throw error. + - context - Context - Required. Parent context. +*/}} +{{- define "common.errors.upgrade.passwords.empty" -}} + {{- $validationErrors := join "" .validationErrors -}} + {{- if and $validationErrors .context.Release.IsUpgrade -}} + {{- $errorString := "\nPASSWORDS ERROR: You must provide your current passwords when upgrading the release." -}} + {{- $errorString = print $errorString "\n Note that even after reinstallation, old credentials may be needed as they may be kept in persistent volume claims." -}} + {{- $errorString = print $errorString "\n Further information can be obtained at https://docs.bitnami.com/general/how-to/troubleshoot-helm-chart-issues/#credential-errors-while-upgrading-chart-releases" -}} + {{- $errorString = print $errorString "\n%s" -}} + {{- printf $errorString $validationErrors | fail -}} + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_images.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_images.tpl new file mode 100644 index 000000000..46c659e79 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_images.tpl @@ -0,0 +1,76 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Return the proper image name +{{ include "common.images.image" ( dict "imageRoot" .Values.path.to.the.image "global" $) }} +*/}} +{{- define "common.images.image" -}} +{{- $registryName := .imageRoot.registry -}} +{{- $repositoryName := .imageRoot.repository -}} +{{- $separator := ":" -}} +{{- $termination := .imageRoot.tag | toString -}} +{{- if .global }} + {{- if .global.imageRegistry }} + {{- $registryName = .global.imageRegistry -}} + {{- end -}} +{{- end -}} +{{- if .imageRoot.digest }} + {{- $separator = "@" -}} + {{- $termination = .imageRoot.digest | toString -}} +{{- end -}} +{{- printf "%s/%s%s%s" $registryName $repositoryName $separator $termination -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names (deprecated: use common.images.renderPullSecrets instead) +{{ include "common.images.pullSecrets" ( dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global) }} +*/}} +{{- define "common.images.pullSecrets" -}} + {{- $pullSecrets := list }} + + {{- if .global }} + {{- range .global.imagePullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} + {{- end -}} + {{- end -}} + + {{- range .images -}} + {{- range .pullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} + {{- end -}} + {{- end -}} + + {{- if (not (empty $pullSecrets)) }} +imagePullSecrets: + {{- range $pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names evaluating values as templates +{{ include "common.images.renderPullSecrets" ( dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "context" $) }} +*/}} +{{- define "common.images.renderPullSecrets" -}} + {{- $pullSecrets := list }} + {{- $context := .context }} + + {{- if $context.Values.global }} + {{- range $context.Values.global.imagePullSecrets -}} + {{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" $context)) -}} + {{- end -}} + {{- end -}} + + {{- range .images -}} + {{- range .pullSecrets -}} + {{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" $context)) -}} + {{- end -}} + {{- end -}} + + {{- if (not (empty $pullSecrets)) }} +imagePullSecrets: + {{- range $pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_ingress.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_ingress.tpl new file mode 100644 index 000000000..831da9caa --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_ingress.tpl @@ -0,0 +1,68 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Generate backend entry that is compatible with all Kubernetes API versions. + +Usage: +{{ include "common.ingress.backend" (dict "serviceName" "backendName" "servicePort" "backendPort" "context" $) }} + +Params: + - serviceName - String. Name of an existing service backend + - servicePort - String/Int. Port name (or number) of the service. It will be translated to different yaml depending if it is a string or an integer. + - context - Dict - Required. The context for the template evaluation. +*/}} +{{- define "common.ingress.backend" -}} +{{- $apiVersion := (include "common.capabilities.ingress.apiVersion" .context) -}} +{{- if or (eq $apiVersion "extensions/v1beta1") (eq $apiVersion "networking.k8s.io/v1beta1") -}} +serviceName: {{ .serviceName }} +servicePort: {{ .servicePort }} +{{- else -}} +service: + name: {{ .serviceName }} + port: + {{- if typeIs "string" .servicePort }} + name: {{ .servicePort }} + {{- else if or (typeIs "int" .servicePort) (typeIs "float64" .servicePort) }} + number: {{ .servicePort | int }} + {{- end }} +{{- end -}} +{{- end -}} + +{{/* +Print "true" if the API pathType field is supported +Usage: +{{ include "common.ingress.supportsPathType" . }} +*/}} +{{- define "common.ingress.supportsPathType" -}} +{{- if (semverCompare "<1.18-0" (include "common.capabilities.kubeVersion" .)) -}} +{{- print "false" -}} +{{- else -}} +{{- print "true" -}} +{{- end -}} +{{- end -}} + +{{/* +Returns true if the ingressClassname field is supported +Usage: +{{ include "common.ingress.supportsIngressClassname" . }} +*/}} +{{- define "common.ingress.supportsIngressClassname" -}} +{{- if semverCompare "<1.18-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "false" -}} +{{- else -}} +{{- print "true" -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if cert-manager required annotations for TLS signed +certificates are set in the Ingress annotations +Ref: https://cert-manager.io/docs/usage/ingress/#supported-annotations +Usage: +{{ include "common.ingress.certManagerRequest" ( dict "annotations" .Values.path.to.the.ingress.annotations ) }} +*/}} +{{- define "common.ingress.certManagerRequest" -}} +{{ if or (hasKey .annotations "cert-manager.io/cluster-issuer") (hasKey .annotations "cert-manager.io/issuer") (hasKey .annotations "kubernetes.io/tls-acme") }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_labels.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_labels.tpl new file mode 100644 index 000000000..252066c7e --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_labels.tpl @@ -0,0 +1,18 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Kubernetes standard labels +*/}} +{{- define "common.labels.standard" -}} +app.kubernetes.io/name: {{ include "common.names.name" . }} +helm.sh/chart: {{ include "common.names.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Labels to use on deploy.spec.selector.matchLabels and svc.spec.selector +*/}} +{{- define "common.labels.matchLabels" -}} +app.kubernetes.io/name: {{ include "common.names.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_names.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_names.tpl new file mode 100644 index 000000000..617a23489 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_names.tpl @@ -0,0 +1,66 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "common.names.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "common.names.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "common.names.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create a default fully qualified dependency name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +Usage: +{{ include "common.names.dependency.fullname" (dict "chartName" "dependency-chart-name" "chartValues" .Values.dependency-chart "context" $) }} +*/}} +{{- define "common.names.dependency.fullname" -}} +{{- if .chartValues.fullnameOverride -}} +{{- .chartValues.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .chartName .chartValues.nameOverride -}} +{{- if contains $name .context.Release.Name -}} +{{- .context.Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .context.Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Allow the release namespace to be overridden for multi-namespace deployments in combined charts. +*/}} +{{- define "common.names.namespace" -}} +{{- default .Release.Namespace .Values.namespaceOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a fully qualified app name adding the installation's namespace. +*/}} +{{- define "common.names.fullname.namespace" -}} +{{- printf "%s-%s" (include "common.names.fullname" .) (include "common.names.namespace" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_secrets.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_secrets.tpl new file mode 100644 index 000000000..a1708b2e8 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_secrets.tpl @@ -0,0 +1,165 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Generate secret name. + +Usage: +{{ include "common.secrets.name" (dict "existingSecret" .Values.path.to.the.existingSecret "defaultNameSuffix" "mySuffix" "context" $) }} + +Params: + - existingSecret - ExistingSecret/String - Optional. The path to the existing secrets in the values.yaml given by the user + to be used instead of the default one. Allows for it to be of type String (just the secret name) for backwards compatibility. + +info: https://github.com/bitnami/charts/tree/main/bitnami/common#existingsecret + - defaultNameSuffix - String - Optional. It is used only if we have several secrets in the same deployment. + - context - Dict - Required. The context for the template evaluation. +*/}} +{{- define "common.secrets.name" -}} +{{- $name := (include "common.names.fullname" .context) -}} + +{{- if .defaultNameSuffix -}} +{{- $name = printf "%s-%s" $name .defaultNameSuffix | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- with .existingSecret -}} +{{- if not (typeIs "string" .) -}} +{{- with .name -}} +{{- $name = . -}} +{{- end -}} +{{- else -}} +{{- $name = . -}} +{{- end -}} +{{- end -}} + +{{- printf "%s" $name -}} +{{- end -}} + +{{/* +Generate secret key. + +Usage: +{{ include "common.secrets.key" (dict "existingSecret" .Values.path.to.the.existingSecret "key" "keyName") }} + +Params: + - existingSecret - ExistingSecret/String - Optional. The path to the existing secrets in the values.yaml given by the user + to be used instead of the default one. Allows for it to be of type String (just the secret name) for backwards compatibility. + +info: https://github.com/bitnami/charts/tree/main/bitnami/common#existingsecret + - key - String - Required. Name of the key in the secret. +*/}} +{{- define "common.secrets.key" -}} +{{- $key := .key -}} + +{{- if .existingSecret -}} + {{- if not (typeIs "string" .existingSecret) -}} + {{- if .existingSecret.keyMapping -}} + {{- $key = index .existingSecret.keyMapping $.key -}} + {{- end -}} + {{- end }} +{{- end -}} + +{{- printf "%s" $key -}} +{{- end -}} + +{{/* +Generate secret password or retrieve one if already created. + +Usage: +{{ include "common.secrets.passwords.manage" (dict "secret" "secret-name" "key" "keyName" "providedValues" (list "path.to.password1" "path.to.password2") "length" 10 "strong" false "chartName" "chartName" "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - key - String - Required - Name of the key in the secret. + - providedValues - List<String> - Required - The path to the validating value in the values.yaml, e.g: "mysql.password". Will pick first parameter with a defined value. + - length - int - Optional - Length of the generated random password. + - strong - Boolean - Optional - Whether to add symbols to the generated random password. + - chartName - String - Optional - Name of the chart used when said chart is deployed as a subchart. + - context - Context - Required - Parent context. + +The order in which this function returns a secret password: + 1. Already existing 'Secret' resource + (If a 'Secret' resource is found under the name provided to the 'secret' parameter to this function and that 'Secret' resource contains a key with the name passed as the 'key' parameter to this function then the value of this existing secret password will be returned) + 2. Password provided via the values.yaml + (If one of the keys passed to the 'providedValues' parameter to this function is a valid path to a key in the values.yaml and has a value, the value of the first key with a value will be returned) + 3. Randomly generated secret password + (A new random secret password with the length specified in the 'length' parameter will be generated and returned) + +*/}} +{{- define "common.secrets.passwords.manage" -}} + +{{- $password := "" }} +{{- $subchart := "" }} +{{- $chartName := default "" .chartName }} +{{- $passwordLength := default 10 .length }} +{{- $providedPasswordKey := include "common.utils.getKeyFromList" (dict "keys" .providedValues "context" $.context) }} +{{- $providedPasswordValue := include "common.utils.getValueFromKey" (dict "key" $providedPasswordKey "context" $.context) }} +{{- $secretData := (lookup "v1" "Secret" (include "common.names.namespace" .context) .secret).data }} +{{- if $secretData }} + {{- if hasKey $secretData .key }} + {{- $password = index $secretData .key | quote }} + {{- else }} + {{- printf "\nPASSWORDS ERROR: The secret \"%s\" does not contain the key \"%s\"\n" .secret .key | fail -}} + {{- end -}} +{{- else if $providedPasswordValue }} + {{- $password = $providedPasswordValue | toString | b64enc | quote }} +{{- else }} + + {{- if .context.Values.enabled }} + {{- $subchart = $chartName }} + {{- end -}} + + {{- $requiredPassword := dict "valueKey" $providedPasswordKey "secret" .secret "field" .key "subchart" $subchart "context" $.context -}} + {{- $requiredPasswordError := include "common.validations.values.single.empty" $requiredPassword -}} + {{- $passwordValidationErrors := list $requiredPasswordError -}} + {{- include "common.errors.upgrade.passwords.empty" (dict "validationErrors" $passwordValidationErrors "context" $.context) -}} + + {{- if .strong }} + {{- $subStr := list (lower (randAlpha 1)) (randNumeric 1) (upper (randAlpha 1)) | join "_" }} + {{- $password = randAscii $passwordLength }} + {{- $password = regexReplaceAllLiteral "\\W" $password "@" | substr 5 $passwordLength }} + {{- $password = printf "%s%s" $subStr $password | toString | shuffle | b64enc | quote }} + {{- else }} + {{- $password = randAlphaNum $passwordLength | b64enc | quote }} + {{- end }} +{{- end -}} +{{- printf "%s" $password -}} +{{- end -}} + +{{/* +Reuses the value from an existing secret, otherwise sets its value to a default value. + +Usage: +{{ include "common.secrets.lookup" (dict "secret" "secret-name" "key" "keyName" "defaultValue" .Values.myValue "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - key - String - Required - Name of the key in the secret. + - defaultValue - String - Required - The path to the validating value in the values.yaml, e.g: "mysql.password". Will pick first parameter with a defined value. + - context - Context - Required - Parent context. + +*/}} +{{- define "common.secrets.lookup" -}} +{{- $value := "" -}} +{{- $defaultValue := required "\n'common.secrets.lookup': Argument 'defaultValue' missing or empty" .defaultValue -}} +{{- $secretData := (lookup "v1" "Secret" (include "common.names.namespace" .context) .secret).data -}} +{{- if and $secretData (hasKey $secretData .key) -}} + {{- $value = index $secretData .key -}} +{{- else -}} + {{- $value = $defaultValue | toString | b64enc -}} +{{- end -}} +{{- printf "%s" $value -}} +{{- end -}} + +{{/* +Returns whether a previous generated secret already exists + +Usage: +{{ include "common.secrets.exists" (dict "secret" "secret-name" "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - context - Context - Required - Parent context. +*/}} +{{- define "common.secrets.exists" -}} +{{- $secret := (lookup "v1" "Secret" (include "common.names.namespace" .context) .secret) }} +{{- if $secret }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_storage.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_storage.tpl new file mode 100644 index 000000000..60e2a844f --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_storage.tpl @@ -0,0 +1,23 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Return the proper Storage Class +{{ include "common.storage.class" ( dict "persistence" .Values.path.to.the.persistence "global" $) }} +*/}} +{{- define "common.storage.class" -}} + +{{- $storageClass := .persistence.storageClass -}} +{{- if .global -}} + {{- if .global.storageClass -}} + {{- $storageClass = .global.storageClass -}} + {{- end -}} +{{- end -}} + +{{- if $storageClass -}} + {{- if (eq "-" $storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" $storageClass -}} + {{- end -}} +{{- end -}} + +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_tplvalues.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_tplvalues.tpl new file mode 100644 index 000000000..2db166851 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_tplvalues.tpl @@ -0,0 +1,13 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Renders a value that contains template. +Usage: +{{ include "common.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} +{{- define "common.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_utils.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_utils.tpl new file mode 100644 index 000000000..b1ead50cf --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_utils.tpl @@ -0,0 +1,62 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Print instructions to get a secret value. +Usage: +{{ include "common.utils.secret.getvalue" (dict "secret" "secret-name" "field" "secret-value-field" "context" $) }} +*/}} +{{- define "common.utils.secret.getvalue" -}} +{{- $varname := include "common.utils.fieldToEnvVar" . -}} +export {{ $varname }}=$(kubectl get secret --namespace {{ include "common.names.namespace" .context | quote }} {{ .secret }} -o jsonpath="{.data.{{ .field }}}" | base64 -d) +{{- end -}} + +{{/* +Build env var name given a field +Usage: +{{ include "common.utils.fieldToEnvVar" dict "field" "my-password" }} +*/}} +{{- define "common.utils.fieldToEnvVar" -}} + {{- $fieldNameSplit := splitList "-" .field -}} + {{- $upperCaseFieldNameSplit := list -}} + + {{- range $fieldNameSplit -}} + {{- $upperCaseFieldNameSplit = append $upperCaseFieldNameSplit ( upper . ) -}} + {{- end -}} + + {{ join "_" $upperCaseFieldNameSplit }} +{{- end -}} + +{{/* +Gets a value from .Values given +Usage: +{{ include "common.utils.getValueFromKey" (dict "key" "path.to.key" "context" $) }} +*/}} +{{- define "common.utils.getValueFromKey" -}} +{{- $splitKey := splitList "." .key -}} +{{- $value := "" -}} +{{- $latestObj := $.context.Values -}} +{{- range $splitKey -}} + {{- if not $latestObj -}} + {{- printf "please review the entire path of '%s' exists in values" $.key | fail -}} + {{- end -}} + {{- $value = ( index $latestObj . ) -}} + {{- $latestObj = $value -}} +{{- end -}} +{{- printf "%v" (default "" $value) -}} +{{- end -}} + +{{/* +Returns first .Values key with a defined value or first of the list if all non-defined +Usage: +{{ include "common.utils.getKeyFromList" (dict "keys" (list "path.to.key1" "path.to.key2") "context" $) }} +*/}} +{{- define "common.utils.getKeyFromList" -}} +{{- $key := first .keys -}} +{{- $reverseKeys := reverse .keys }} +{{- range $reverseKeys }} + {{- $value := include "common.utils.getValueFromKey" (dict "key" . "context" $.context ) }} + {{- if $value -}} + {{- $key = . }} + {{- end -}} +{{- end -}} +{{- printf "%s" $key -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_warnings.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_warnings.tpl new file mode 100644 index 000000000..ae10fa41e --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/_warnings.tpl @@ -0,0 +1,14 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Warning about using rolling tag. +Usage: +{{ include "common.warnings.rollingTag" .Values.path.to.the.imageRoot }} +*/}} +{{- define "common.warnings.rollingTag" -}} + +{{- if and (contains "bitnami/" .repository) (not (.tag | toString | regexFind "-r\\d+$|sha256:")) }} +WARNING: Rolling tag detected ({{ .repository }}:{{ .tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. ++info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ +{{- end }} + +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_cassandra.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_cassandra.tpl new file mode 100644 index 000000000..ded1ae3bc --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_cassandra.tpl @@ -0,0 +1,72 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate Cassandra required passwords are not empty. + +Usage: +{{ include "common.validations.values.cassandra.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where Cassandra values are stored, e.g: "cassandra-passwords-secret" + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.cassandra.passwords" -}} + {{- $existingSecret := include "common.cassandra.values.existingSecret" . -}} + {{- $enabled := include "common.cassandra.values.enabled" . -}} + {{- $dbUserPrefix := include "common.cassandra.values.key.dbUser" . -}} + {{- $valueKeyPassword := printf "%s.password" $dbUserPrefix -}} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "cassandra-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.cassandra.values.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.cassandra.values.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.cassandra.dbUser.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.dbUser.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled cassandra. + +Usage: +{{ include "common.cassandra.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.cassandra.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.cassandra.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key dbUser + +Usage: +{{ include "common.cassandra.values.key.dbUser" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.cassandra.values.key.dbUser" -}} + {{- if .subchart -}} + cassandra.dbUser + {{- else -}} + dbUser + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_mariadb.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_mariadb.tpl new file mode 100644 index 000000000..b6906ff77 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_mariadb.tpl @@ -0,0 +1,103 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate MariaDB required passwords are not empty. + +Usage: +{{ include "common.validations.values.mariadb.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where MariaDB values are stored, e.g: "mysql-passwords-secret" + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.mariadb.passwords" -}} + {{- $existingSecret := include "common.mariadb.values.auth.existingSecret" . -}} + {{- $enabled := include "common.mariadb.values.enabled" . -}} + {{- $architecture := include "common.mariadb.values.architecture" . -}} + {{- $authPrefix := include "common.mariadb.values.key.auth" . -}} + {{- $valueKeyRootPassword := printf "%s.rootPassword" $authPrefix -}} + {{- $valueKeyUsername := printf "%s.username" $authPrefix -}} + {{- $valueKeyPassword := printf "%s.password" $authPrefix -}} + {{- $valueKeyReplicationPassword := printf "%s.replicationPassword" $authPrefix -}} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" $valueKeyRootPassword "secret" .secret "field" "mariadb-root-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRootPassword -}} + + {{- $valueUsername := include "common.utils.getValueFromKey" (dict "key" $valueKeyUsername "context" .context) }} + {{- if not (empty $valueUsername) -}} + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "mariadb-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq $architecture "replication") -}} + {{- $requiredReplicationPassword := dict "valueKey" $valueKeyReplicationPassword "secret" .secret "field" "mariadb-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredReplicationPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.mariadb.values.auth.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.auth.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.mariadb.auth.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.auth.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled mariadb. + +Usage: +{{ include "common.mariadb.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.mariadb.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.mariadb.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for architecture + +Usage: +{{ include "common.mariadb.values.architecture" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.architecture" -}} + {{- if .subchart -}} + {{- .context.Values.mariadb.architecture -}} + {{- else -}} + {{- .context.Values.architecture -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key auth + +Usage: +{{ include "common.mariadb.values.key.auth" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.key.auth" -}} + {{- if .subchart -}} + mariadb.auth + {{- else -}} + auth + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_mongodb.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_mongodb.tpl new file mode 100644 index 000000000..f820ec107 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_mongodb.tpl @@ -0,0 +1,108 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate MongoDB® required passwords are not empty. + +Usage: +{{ include "common.validations.values.mongodb.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where MongoDB® values are stored, e.g: "mongodb-passwords-secret" + - subchart - Boolean - Optional. Whether MongoDB® is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.mongodb.passwords" -}} + {{- $existingSecret := include "common.mongodb.values.auth.existingSecret" . -}} + {{- $enabled := include "common.mongodb.values.enabled" . -}} + {{- $authPrefix := include "common.mongodb.values.key.auth" . -}} + {{- $architecture := include "common.mongodb.values.architecture" . -}} + {{- $valueKeyRootPassword := printf "%s.rootPassword" $authPrefix -}} + {{- $valueKeyUsername := printf "%s.username" $authPrefix -}} + {{- $valueKeyDatabase := printf "%s.database" $authPrefix -}} + {{- $valueKeyPassword := printf "%s.password" $authPrefix -}} + {{- $valueKeyReplicaSetKey := printf "%s.replicaSetKey" $authPrefix -}} + {{- $valueKeyAuthEnabled := printf "%s.enabled" $authPrefix -}} + + {{- $authEnabled := include "common.utils.getValueFromKey" (dict "key" $valueKeyAuthEnabled "context" .context) -}} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") (eq $authEnabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" $valueKeyRootPassword "secret" .secret "field" "mongodb-root-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRootPassword -}} + + {{- $valueUsername := include "common.utils.getValueFromKey" (dict "key" $valueKeyUsername "context" .context) }} + {{- $valueDatabase := include "common.utils.getValueFromKey" (dict "key" $valueKeyDatabase "context" .context) }} + {{- if and $valueUsername $valueDatabase -}} + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "mongodb-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq $architecture "replicaset") -}} + {{- $requiredReplicaSetKey := dict "valueKey" $valueKeyReplicaSetKey "secret" .secret "field" "mongodb-replica-set-key" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredReplicaSetKey -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.mongodb.values.auth.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MongoDb is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.auth.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.mongodb.auth.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.auth.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled mongodb. + +Usage: +{{ include "common.mongodb.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.mongodb.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.mongodb.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key auth + +Usage: +{{ include "common.mongodb.values.key.auth" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MongoDB® is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.key.auth" -}} + {{- if .subchart -}} + mongodb.auth + {{- else -}} + auth + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for architecture + +Usage: +{{ include "common.mongodb.values.architecture" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MongoDB® is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.architecture" -}} + {{- if .subchart -}} + {{- .context.Values.mongodb.architecture -}} + {{- else -}} + {{- .context.Values.architecture -}} + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_mysql.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_mysql.tpl new file mode 100644 index 000000000..74472a061 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_mysql.tpl @@ -0,0 +1,103 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate MySQL required passwords are not empty. + +Usage: +{{ include "common.validations.values.mysql.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where MySQL values are stored, e.g: "mysql-passwords-secret" + - subchart - Boolean - Optional. Whether MySQL is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.mysql.passwords" -}} + {{- $existingSecret := include "common.mysql.values.auth.existingSecret" . -}} + {{- $enabled := include "common.mysql.values.enabled" . -}} + {{- $architecture := include "common.mysql.values.architecture" . -}} + {{- $authPrefix := include "common.mysql.values.key.auth" . -}} + {{- $valueKeyRootPassword := printf "%s.rootPassword" $authPrefix -}} + {{- $valueKeyUsername := printf "%s.username" $authPrefix -}} + {{- $valueKeyPassword := printf "%s.password" $authPrefix -}} + {{- $valueKeyReplicationPassword := printf "%s.replicationPassword" $authPrefix -}} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" $valueKeyRootPassword "secret" .secret "field" "mysql-root-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRootPassword -}} + + {{- $valueUsername := include "common.utils.getValueFromKey" (dict "key" $valueKeyUsername "context" .context) }} + {{- if not (empty $valueUsername) -}} + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "mysql-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq $architecture "replication") -}} + {{- $requiredReplicationPassword := dict "valueKey" $valueKeyReplicationPassword "secret" .secret "field" "mysql-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredReplicationPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.mysql.values.auth.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MySQL is used as subchart or not. Default: false +*/}} +{{- define "common.mysql.values.auth.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.mysql.auth.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.auth.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled mysql. + +Usage: +{{ include "common.mysql.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.mysql.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.mysql.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for architecture + +Usage: +{{ include "common.mysql.values.architecture" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MySQL is used as subchart or not. Default: false +*/}} +{{- define "common.mysql.values.architecture" -}} + {{- if .subchart -}} + {{- .context.Values.mysql.architecture -}} + {{- else -}} + {{- .context.Values.architecture -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key auth + +Usage: +{{ include "common.mysql.values.key.auth" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MySQL is used as subchart or not. Default: false +*/}} +{{- define "common.mysql.values.key.auth" -}} + {{- if .subchart -}} + mysql.auth + {{- else -}} + auth + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_postgresql.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_postgresql.tpl new file mode 100644 index 000000000..164ec0d01 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_postgresql.tpl @@ -0,0 +1,129 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate PostgreSQL required passwords are not empty. + +Usage: +{{ include "common.validations.values.postgresql.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where postgresql values are stored, e.g: "postgresql-passwords-secret" + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.postgresql.passwords" -}} + {{- $existingSecret := include "common.postgresql.values.existingSecret" . -}} + {{- $enabled := include "common.postgresql.values.enabled" . -}} + {{- $valueKeyPostgresqlPassword := include "common.postgresql.values.key.postgressPassword" . -}} + {{- $valueKeyPostgresqlReplicationEnabled := include "common.postgresql.values.key.replicationPassword" . -}} + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + {{- $requiredPostgresqlPassword := dict "valueKey" $valueKeyPostgresqlPassword "secret" .secret "field" "postgresql-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPostgresqlPassword -}} + + {{- $enabledReplication := include "common.postgresql.values.enabled.replication" . -}} + {{- if (eq $enabledReplication "true") -}} + {{- $requiredPostgresqlReplicationPassword := dict "valueKey" $valueKeyPostgresqlReplicationEnabled "secret" .secret "field" "postgresql-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPostgresqlReplicationPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to decide whether evaluate global values. + +Usage: +{{ include "common.postgresql.values.use.global" (dict "key" "key-of-global" "context" $) }} +Params: + - key - String - Required. Field to be evaluated within global, e.g: "existingSecret" +*/}} +{{- define "common.postgresql.values.use.global" -}} + {{- if .context.Values.global -}} + {{- if .context.Values.global.postgresql -}} + {{- index .context.Values.global.postgresql .key | quote -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.postgresql.values.existingSecret" (dict "context" $) }} +*/}} +{{- define "common.postgresql.values.existingSecret" -}} + {{- $globalValue := include "common.postgresql.values.use.global" (dict "key" "existingSecret" "context" .context) -}} + + {{- if .subchart -}} + {{- default (.context.Values.postgresql.existingSecret | quote) $globalValue -}} + {{- else -}} + {{- default (.context.Values.existingSecret | quote) $globalValue -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled postgresql. + +Usage: +{{ include "common.postgresql.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.postgresql.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.postgresql.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key postgressPassword. + +Usage: +{{ include "common.postgresql.values.key.postgressPassword" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.key.postgressPassword" -}} + {{- $globalValue := include "common.postgresql.values.use.global" (dict "key" "postgresqlUsername" "context" .context) -}} + + {{- if not $globalValue -}} + {{- if .subchart -}} + postgresql.postgresqlPassword + {{- else -}} + postgresqlPassword + {{- end -}} + {{- else -}} + global.postgresql.postgresqlPassword + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled.replication. + +Usage: +{{ include "common.postgresql.values.enabled.replication" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.enabled.replication" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.postgresql.replication.enabled -}} + {{- else -}} + {{- printf "%v" .context.Values.replication.enabled -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key replication.password. + +Usage: +{{ include "common.postgresql.values.key.replicationPassword" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.key.replicationPassword" -}} + {{- if .subchart -}} + postgresql.replication.password + {{- else -}} + replication.password + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_redis.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_redis.tpl new file mode 100644 index 000000000..dcccfc1ae --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_redis.tpl @@ -0,0 +1,76 @@ + +{{/* vim: set filetype=mustache: */}} +{{/* +Validate Redis® required passwords are not empty. + +Usage: +{{ include "common.validations.values.redis.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where redis values are stored, e.g: "redis-passwords-secret" + - subchart - Boolean - Optional. Whether redis is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.redis.passwords" -}} + {{- $enabled := include "common.redis.values.enabled" . -}} + {{- $valueKeyPrefix := include "common.redis.values.keys.prefix" . -}} + {{- $standarizedVersion := include "common.redis.values.standarized.version" . }} + + {{- $existingSecret := ternary (printf "%s%s" $valueKeyPrefix "auth.existingSecret") (printf "%s%s" $valueKeyPrefix "existingSecret") (eq $standarizedVersion "true") }} + {{- $existingSecretValue := include "common.utils.getValueFromKey" (dict "key" $existingSecret "context" .context) }} + + {{- $valueKeyRedisPassword := ternary (printf "%s%s" $valueKeyPrefix "auth.password") (printf "%s%s" $valueKeyPrefix "password") (eq $standarizedVersion "true") }} + {{- $valueKeyRedisUseAuth := ternary (printf "%s%s" $valueKeyPrefix "auth.enabled") (printf "%s%s" $valueKeyPrefix "usePassword") (eq $standarizedVersion "true") }} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $useAuth := include "common.utils.getValueFromKey" (dict "key" $valueKeyRedisUseAuth "context" .context) -}} + {{- if eq $useAuth "true" -}} + {{- $requiredRedisPassword := dict "valueKey" $valueKeyRedisPassword "secret" .secret "field" "redis-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRedisPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled redis. + +Usage: +{{ include "common.redis.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.redis.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.redis.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right prefix path for the values + +Usage: +{{ include "common.redis.values.key.prefix" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether redis is used as subchart or not. Default: false +*/}} +{{- define "common.redis.values.keys.prefix" -}} + {{- if .subchart -}}redis.{{- else -}}{{- end -}} +{{- end -}} + +{{/* +Checks whether the redis chart's includes the standarizations (version >= 14) + +Usage: +{{ include "common.redis.values.standarized.version" (dict "context" $) }} +*/}} +{{- define "common.redis.values.standarized.version" -}} + + {{- $standarizedAuth := printf "%s%s" (include "common.redis.values.keys.prefix" .) "auth" -}} + {{- $standarizedAuthValues := include "common.utils.getValueFromKey" (dict "key" $standarizedAuth "context" .context) }} + + {{- if $standarizedAuthValues -}} + {{- true -}} + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_validations.tpl b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_validations.tpl new file mode 100644 index 000000000..9a814cf40 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/templates/validations/_validations.tpl @@ -0,0 +1,46 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate values must not be empty. + +Usage: +{{- $validateValueConf00 := (dict "valueKey" "path.to.value" "secret" "secretName" "field" "password-00") -}} +{{- $validateValueConf01 := (dict "valueKey" "path.to.value" "secret" "secretName" "field" "password-01") -}} +{{ include "common.validations.values.empty" (dict "required" (list $validateValueConf00 $validateValueConf01) "context" $) }} + +Validate value params: + - valueKey - String - Required. The path to the validating value in the values.yaml, e.g: "mysql.password" + - secret - String - Optional. Name of the secret where the validating value is generated/stored, e.g: "mysql-passwords-secret" + - field - String - Optional. Name of the field in the secret data, e.g: "mysql-password" +*/}} +{{- define "common.validations.values.multiple.empty" -}} + {{- range .required -}} + {{- include "common.validations.values.single.empty" (dict "valueKey" .valueKey "secret" .secret "field" .field "context" $.context) -}} + {{- end -}} +{{- end -}} + +{{/* +Validate a value must not be empty. + +Usage: +{{ include "common.validations.value.empty" (dict "valueKey" "mariadb.password" "secret" "secretName" "field" "my-password" "subchart" "subchart" "context" $) }} + +Validate value params: + - valueKey - String - Required. The path to the validating value in the values.yaml, e.g: "mysql.password" + - secret - String - Optional. Name of the secret where the validating value is generated/stored, e.g: "mysql-passwords-secret" + - field - String - Optional. Name of the field in the secret data, e.g: "mysql-password" + - subchart - String - Optional - Name of the subchart that the validated password is part of. +*/}} +{{- define "common.validations.values.single.empty" -}} + {{- $value := include "common.utils.getValueFromKey" (dict "key" .valueKey "context" .context) }} + {{- $subchart := ternary "" (printf "%s." .subchart) (empty .subchart) }} + + {{- if not $value -}} + {{- $varname := "my-value" -}} + {{- $getCurrentValue := "" -}} + {{- if and .secret .field -}} + {{- $varname = include "common.utils.fieldToEnvVar" . -}} + {{- $getCurrentValue = printf " To get the current value:\n\n %s\n" (include "common.utils.secret.getvalue" .) -}} + {{- end -}} + {{- printf "\n '%s' must not be empty, please add '--set %s%s=$%s' to the command.%s" .valueKey $subchart .valueKey $varname $getCurrentValue -}} + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/common/values.yaml b/scripts/helmcharts/databases/charts/kafka/charts/common/values.yaml new file mode 100644 index 000000000..f2df68e5e --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/common/values.yaml @@ -0,0 +1,5 @@ +## bitnami/common +## It is required by CI/CD tools and processes. +## @skip exampleValue +## +exampleValue: common-chart diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/.helmignore b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/.helmignore old mode 100755 new mode 100644 diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/Chart.lock b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/Chart.lock new file mode 100644 index 000000000..065985261 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + version: 2.2.2 +digest: sha256:49ca75cf23ba5eb7df4becef52580f98c8bd8194eb80368b9d7b875f6eefa8e5 +generated: "2023-01-06T05:12:14.420203052Z" diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/Chart.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/Chart.yaml old mode 100755 new mode 100644 index c3b15dc5c..ae6fd7e1f --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/Chart.yaml +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/Chart.yaml @@ -1,20 +1,26 @@ annotations: category: Infrastructure -apiVersion: v1 -appVersion: 3.6.2 -description: A centralized service for maintaining configuration information, naming, - providing distributed synchronization, and providing group services for distributed - applications. -engine: gotpl -home: https://github.com/bitnami/charts/tree/master/bitnami/zookeeper -icon: https://bitnami.com/assets/stacks/zookeeper/img/zookeeper-stack-110x117.png + licenses: | + - Apache-2.0 +apiVersion: v2 +appVersion: 3.8.0 +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + tags: + - bitnami-common + version: 2.x.x +description: Apache ZooKeeper provides a reliable, centralized register of configuration + data and services for distributed applications. +home: https://github.com/bitnami/charts/tree/main/bitnami/zookeeper +icon: https://bitnami.com/assets/stacks/zookeeper/img/zookeeper-stack-220x234.png keywords: - zookeeper maintainers: -- email: containers@bitnami.com - name: Bitnami +- name: Bitnami + url: https://github.com/bitnami/charts name: zookeeper sources: -- https://github.com/bitnami/bitnami-docker-zookeeper +- https://github.com/bitnami/containers/tree/main/bitnami/zookeeper - https://zookeeper.apache.org/ -version: 5.21.9 +version: 11.1.0 diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/README.md b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/README.md old mode 100755 new mode 100644 index 0291875ed..7a1c17ffb --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/README.md +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/README.md @@ -1,24 +1,30 @@ -# ZooKeeper +<!--- app-name: Apache ZooKeeper --> -[ZooKeeper](https://zookeeper.apache.org/) is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. All of these kinds of services are used in some form or other by distributed applications. +# Apache ZooKeeper packaged by Bitnami +Apache ZooKeeper provides a reliable, centralized register of configuration data and services for distributed applications. + +[Overview of Apache ZooKeeper](https://zookeeper.apache.org) + +Trademarks: This software listing is packaged by Bitnami. The respective trademarks mentioned in the offering are owned by the respective companies, and use of them does not imply any affiliation or endorsement. + ## TL;DR ```console -$ helm repo add bitnami https://charts.bitnami.com/bitnami -$ helm install my-release bitnami/zookeeper +$ helm repo add my-repo https://charts.bitnami.com/bitnami +$ helm install my-release my-repo/zookeeper ``` ## Introduction -This chart bootstraps a [ZooKeeper](https://github.com/bitnami/bitnami-docker-zookeeper) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. +This chart bootstraps a [ZooKeeper](https://github.com/bitnami/containers/tree/main/bitnami/zookeeper) deployment on a [Kubernetes](https://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. -Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment and management of Helm Charts in clusters. This Helm chart has been tested on top of [Bitnami Kubernetes Production Runtime](https://kubeprod.io/) (BKPR). Deploy BKPR to get automated TLS certificates, logging and monitoring for your applications. +Bitnami charts can be used with [Kubeapps](https://kubeapps.dev/) for deployment and management of Helm Charts in clusters. ## Prerequisites -- Kubernetes 1.12+ -- Helm 2.12+ or Helm 3.0-beta3+ +- Kubernetes 1.19+ +- Helm 3.2.0+ - PV provisioner support in the underlying infrastructure ## Installing the Chart @@ -26,8 +32,8 @@ Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment To install the chart with the release name `my-release`: ```console -$ helm repo add bitnami https://charts.bitnami.com/bitnami -$ helm install my-release bitnami/zookeeper +$ helm repo add my-repo https://charts.bitnami.com/bitnami +$ helm install my-release my-repo/zookeeper ``` These commands deploy ZooKeeper on the Kubernetes cluster in the default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. @@ -46,206 +52,358 @@ The command removes all the Kubernetes components associated with the chart and ## Parameters -The following tables lists the configurable parameters of the ZooKeeper chart and their default values per section/component: +### Global parameters + +| Name | Description | Value | +| ------------------------- | ----------------------------------------------- | ----- | +| `global.imageRegistry` | Global Docker image registry | `""` | +| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` | +| `global.storageClass` | Global StorageClass for Persistent Volume(s) | `""` | -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `global.imageRegistry` | Global Docker image registry | `nil` | -| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` (does not add image pull secrets to deployed pods) | -| `global.storageClass` | Global storage class for dynamic provisioning | `nil` | ### Common parameters -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `nameOverride` | String to partially override zookeeper.fullname | `nil` | -| `fullnameOverride` | String to fully override zookeeper.fullname | `nil` | -| `clusterDomain` | Default Kubernetes cluster domain | `cluster.local` | -| `commonLabels` | Labels to add to all deployed objects | `{}` | -| `commonAnnotations` | Annotations to add to all deployed objects | `{}` | -| `schedulerName` | Kubernetes pod scheduler registry | `nil` (use the default-scheduler) | +| Name | Description | Value | +| ------------------------ | -------------------------------------------------------------------------------------------- | --------------- | +| `kubeVersion` | Override Kubernetes version | `""` | +| `nameOverride` | String to partially override common.names.fullname template (will maintain the release name) | `""` | +| `fullnameOverride` | String to fully override common.names.fullname template | `""` | +| `clusterDomain` | Kubernetes Cluster Domain | `cluster.local` | +| `extraDeploy` | Extra objects to deploy (evaluated as a template) | `[]` | +| `commonLabels` | Add labels to all the deployed resources | `{}` | +| `commonAnnotations` | Add annotations to all the deployed resources | `{}` | +| `namespaceOverride` | Override namespace for ZooKeeper resources | `""` | +| `diagnosticMode.enabled` | Enable diagnostic mode (all probes will be disabled and the command will be overridden) | `false` | +| `diagnosticMode.command` | Command to override all containers in the statefulset | `["sleep"]` | +| `diagnosticMode.args` | Args to override all containers in the statefulset | `["infinity"]` | -### Zookeeper chart parameters -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `image.registry` | ZooKeeper image registry | `docker.io` | -| `image.repository` | ZooKeeper Image name | `bitnami/zookeeper` | -| `image.tag` | ZooKeeper Image tag | `{TAG_NAME}` | -| `image.pullPolicy` | ZooKeeper image pull policy | `IfNotPresent` | -| `image.pullSecrets` | Specify docker-registry secret names as an array | `[]` (does not add image pull secrets to deployed pods) | -| `image.debug` | Specify if debug values should be set | `false` | -| `tickTime` | Basic time unit in milliseconds used by ZooKeeper for heartbeats | `2000` | -| `initLimit` | Time the ZooKeeper servers in quorum have to connect to a leader | `10` | -| `syncLimit` | How far out of date a server can be from a leader | `5` | -| `maxClientCnxns` | Number of concurrent connections that a single client may make to a single member | `60` | -| `maxSessionTimeout` | Maximum session timeout in milliseconds that the server will allow the client to negotiate. | `40000` | -| `autopurge.snapRetainCount` | Number of retains snapshots for autopurge | `3` | -| `autopurge.purgeInterval` | The time interval in hours for which the purge task has to be triggered | `0` | -| `fourlwCommandsWhitelist` | A list of comma separated Four Letter Words commands to use | `srvr, mntr` | -| `listenOnAllIPs` | Allow Zookeeper to listen for connections from its peers on all available IP addresses. | `false` | -| `allowAnonymousLogin` | Allow to accept connections from unauthenticated users | `yes` | -| `auth.existingSecret` | Use existing secret (ignores previous password) | `nil` | -| `auth.enabled` | Enable ZooKeeper auth | `false` | -| `auth.clientUser` | User that will use ZooKeeper clients to auth | `nil` | -| `auth.clientPassword` | Password that will use ZooKeeper clients to auth | `nil` | -| `auth.serverUsers` | List of user to be created | `nil` | -| `auth.serverPasswords` | List of passwords to assign to users when created | `nil` | -| `heapSize` | Size in MB for the Java Heap options (Xmx and XMs) | `[]` | -| `logLevel` | Log level of ZooKeeper server | `ERROR` | -| `jvmFlags` | Default JVMFLAGS for the ZooKeeper process | `nil` | -| `config` | Configure ZooKeeper with a custom zoo.conf file | `nil` | -| `dataLogDir` | Data log directory | `""` | +### ZooKeeper chart parameters + +| Name | Description | Value | +| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `image.registry` | ZooKeeper image registry | `docker.io` | +| `image.repository` | ZooKeeper image repository | `bitnami/zookeeper` | +| `image.tag` | ZooKeeper image tag (immutable tags are recommended) | `3.8.0-debian-11-r74` | +| `image.digest` | ZooKeeper image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag | `""` | +| `image.pullPolicy` | ZooKeeper image pull policy | `IfNotPresent` | +| `image.pullSecrets` | Specify docker-registry secret names as an array | `[]` | +| `image.debug` | Specify if debug values should be set | `false` | +| `auth.client.enabled` | Enable ZooKeeper client-server authentication. It uses SASL/Digest-MD5 | `false` | +| `auth.client.clientUser` | User that will use ZooKeeper clients to auth | `""` | +| `auth.client.clientPassword` | Password that will use ZooKeeper clients to auth | `""` | +| `auth.client.serverUsers` | Comma, semicolon or whitespace separated list of user to be created | `""` | +| `auth.client.serverPasswords` | Comma, semicolon or whitespace separated list of passwords to assign to users when created | `""` | +| `auth.client.existingSecret` | Use existing secret (ignores previous passwords) | `""` | +| `auth.quorum.enabled` | Enable ZooKeeper server-server authentication. It uses SASL/Digest-MD5 | `false` | +| `auth.quorum.learnerUser` | User that the ZooKeeper quorumLearner will use to authenticate to quorumServers. | `""` | +| `auth.quorum.learnerPassword` | Password that the ZooKeeper quorumLearner will use to authenticate to quorumServers. | `""` | +| `auth.quorum.serverUsers` | Comma, semicolon or whitespace separated list of users for the quorumServers. | `""` | +| `auth.quorum.serverPasswords` | Comma, semicolon or whitespace separated list of passwords to assign to users when created | `""` | +| `auth.quorum.existingSecret` | Use existing secret (ignores previous passwords) | `""` | +| `tickTime` | Basic time unit (in milliseconds) used by ZooKeeper for heartbeats | `2000` | +| `initLimit` | ZooKeeper uses to limit the length of time the ZooKeeper servers in quorum have to connect to a leader | `10` | +| `syncLimit` | How far out of date a server can be from a leader | `5` | +| `preAllocSize` | Block size for transaction log file | `65536` | +| `snapCount` | The number of transactions recorded in the transaction log before a snapshot can be taken (and the transaction log rolled) | `100000` | +| `maxClientCnxns` | Limits the number of concurrent connections that a single client may make to a single member of the ZooKeeper ensemble | `60` | +| `maxSessionTimeout` | Maximum session timeout (in milliseconds) that the server will allow the client to negotiate | `40000` | +| `heapSize` | Size (in MB) for the Java Heap options (Xmx and Xms) | `1024` | +| `fourlwCommandsWhitelist` | A list of comma separated Four Letter Words commands that can be executed | `srvr, mntr, ruok` | +| `minServerId` | Minimal SERVER_ID value, nodes increment their IDs respectively | `1` | +| `listenOnAllIPs` | Allow ZooKeeper to listen for connections from its peers on all available IP addresses | `false` | +| `autopurge.snapRetainCount` | The most recent snapshots amount (and corresponding transaction logs) to retain | `3` | +| `autopurge.purgeInterval` | The time interval (in hours) for which the purge task has to be triggered | `0` | +| `logLevel` | Log level for the ZooKeeper server. ERROR by default | `ERROR` | +| `jvmFlags` | Default JVM flags for the ZooKeeper process | `""` | +| `dataLogDir` | Dedicated data log directory | `""` | +| `configuration` | Configure ZooKeeper with a custom zoo.cfg file | `""` | +| `existingConfigmap` | The name of an existing ConfigMap with your custom configuration for ZooKeeper | `""` | +| `extraEnvVars` | Array with extra environment variables to add to ZooKeeper nodes | `[]` | +| `extraEnvVarsCM` | Name of existing ConfigMap containing extra env vars for ZooKeeper nodes | `""` | +| `extraEnvVarsSecret` | Name of existing Secret containing extra env vars for ZooKeeper nodes | `""` | +| `command` | Override default container command (useful when using custom images) | `["/scripts/setup.sh"]` | +| `args` | Override default container args (useful when using custom images) | `[]` | + ### Statefulset parameters -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `replicaCount` | Number of ZooKeeper nodes | `1` | -| `updateStrategy` | Update strategy for the statefulset | `RollingUpdate` | -| `rollingUpdatePartition` | Partition update strategy | `nil` | -| `podManagementPolicy` | Pod management policy | `Parallel` | -| `podLabels` | ZooKeeper pod labels | `{}` (evaluated as a template) | -| `podAnnotations` | ZooKeeper Pod annotations | `{}` (evaluated as a template) | -| `affinity` | Affinity for pod assignment | `{}` (evaluated as a template) | -| `nodeSelector` | Node labels for pod assignment | `{}` (evaluated as a template) | -| `tolerations` | Tolerations for pod assignment | `[]` (evaluated as a template) | -| `priorityClassName` | Name of the existing priority class to be used by ZooKeeper pods | `""` | -| `securityContext.enabled` | Enable security context (ZooKeeper master pod) | `true` | -| `securityContext.fsGroup` | Group ID for the container (ZooKeeper master pod) | `1001` | -| `securityContext.runAsUser` | User ID for the container (ZooKeeper master pod) | `1001` | -| `resources` | CPU/Memory resource requests/limits | Memory: `256Mi`, CPU: `250m` | -| `livenessProbe` | Liveness probe configuration for ZooKeeper | Check `values.yaml` file | -| `readinessProbe` | Readiness probe configuration for ZooKeeper | Check `values.yaml` file | -| `extraVolumes` | Extra volumes | `nil` | -| `extraVolumeMounts` | Mount extra volume(s) | `nil` | -| `podDisruptionBudget.maxUnavailable` | Max number of pods down simultaneously | `1` | +| Name | Description | Value | +| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `replicaCount` | Number of ZooKeeper nodes | `1` | +| `containerPorts.client` | ZooKeeper client container port | `2181` | +| `containerPorts.tls` | ZooKeeper TLS container port | `3181` | +| `containerPorts.follower` | ZooKeeper follower container port | `2888` | +| `containerPorts.election` | ZooKeeper election container port | `3888` | +| `livenessProbe.enabled` | Enable livenessProbe on ZooKeeper containers | `true` | +| `livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `30` | +| `livenessProbe.periodSeconds` | Period seconds for livenessProbe | `10` | +| `livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `5` | +| `livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `6` | +| `livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` | +| `livenessProbe.probeCommandTimeout` | Probe command timeout for livenessProbe | `2` | +| `readinessProbe.enabled` | Enable readinessProbe on ZooKeeper containers | `true` | +| `readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `5` | +| `readinessProbe.periodSeconds` | Period seconds for readinessProbe | `10` | +| `readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `5` | +| `readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `6` | +| `readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` | +| `readinessProbe.probeCommandTimeout` | Probe command timeout for readinessProbe | `2` | +| `startupProbe.enabled` | Enable startupProbe on ZooKeeper containers | `false` | +| `startupProbe.initialDelaySeconds` | Initial delay seconds for startupProbe | `30` | +| `startupProbe.periodSeconds` | Period seconds for startupProbe | `10` | +| `startupProbe.timeoutSeconds` | Timeout seconds for startupProbe | `1` | +| `startupProbe.failureThreshold` | Failure threshold for startupProbe | `15` | +| `startupProbe.successThreshold` | Success threshold for startupProbe | `1` | +| `customLivenessProbe` | Custom livenessProbe that overrides the default one | `{}` | +| `customReadinessProbe` | Custom readinessProbe that overrides the default one | `{}` | +| `customStartupProbe` | Custom startupProbe that overrides the default one | `{}` | +| `lifecycleHooks` | for the ZooKeeper container(s) to automate configuration before or after startup | `{}` | +| `resources.limits` | The resources limits for the ZooKeeper containers | `{}` | +| `resources.requests.memory` | The requested memory for the ZooKeeper containers | `256Mi` | +| `resources.requests.cpu` | The requested cpu for the ZooKeeper containers | `250m` | +| `podSecurityContext.enabled` | Enabled ZooKeeper pods' Security Context | `true` | +| `podSecurityContext.fsGroup` | Set ZooKeeper pod's Security Context fsGroup | `1001` | +| `containerSecurityContext.enabled` | Enabled ZooKeeper containers' Security Context | `true` | +| `containerSecurityContext.runAsUser` | Set ZooKeeper containers' Security Context runAsUser | `1001` | +| `containerSecurityContext.runAsNonRoot` | Set ZooKeeper containers' Security Context runAsNonRoot | `true` | +| `containerSecurityContext.allowPrivilegeEscalation` | Force the child process to be run as nonprivilege | `false` | +| `hostAliases` | ZooKeeper pods host aliases | `[]` | +| `podLabels` | Extra labels for ZooKeeper pods | `{}` | +| `podAnnotations` | Annotations for ZooKeeper pods | `{}` | +| `podAffinityPreset` | Pod affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `podAntiAffinityPreset` | Pod anti-affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `nodeAffinityPreset.type` | Node affinity preset type. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `nodeAffinityPreset.key` | Node label key to match Ignored if `affinity` is set. | `""` | +| `nodeAffinityPreset.values` | Node label values to match. Ignored if `affinity` is set. | `[]` | +| `affinity` | Affinity for pod assignment | `{}` | +| `nodeSelector` | Node labels for pod assignment | `{}` | +| `tolerations` | Tolerations for pod assignment | `[]` | +| `topologySpreadConstraints` | Topology Spread Constraints for pod assignment spread across your cluster among failure-domains. Evaluated as a template | `[]` | +| `podManagementPolicy` | StatefulSet controller supports relax its ordering guarantees while preserving its uniqueness and identity guarantees. There are two valid pod management policies: `OrderedReady` and `Parallel` | `Parallel` | +| `priorityClassName` | Name of the existing priority class to be used by ZooKeeper pods, priority class needs to be created beforehand | `""` | +| `schedulerName` | Kubernetes pod scheduler registry | `""` | +| `updateStrategy.type` | ZooKeeper statefulset strategy type | `RollingUpdate` | +| `updateStrategy.rollingUpdate` | ZooKeeper statefulset rolling update configuration parameters | `{}` | +| `extraVolumes` | Optionally specify extra list of additional volumes for the ZooKeeper pod(s) | `[]` | +| `extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the ZooKeeper container(s) | `[]` | +| `sidecars` | Add additional sidecar containers to the ZooKeeper pod(s) | `[]` | +| `initContainers` | Add additional init containers to the ZooKeeper pod(s) | `[]` | +| `pdb.create` | Deploy a pdb object for the ZooKeeper pod | `false` | +| `pdb.minAvailable` | Minimum available ZooKeeper replicas | `""` | +| `pdb.maxUnavailable` | Maximum unavailable ZooKeeper replicas | `1` | -### Exposure parameters -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `service.type` | Kubernetes Service type | `ClusterIP` | -| `service.port` | ZooKeeper port | `2181` | -| `service.followerPort` | ZooKeeper follower port | `2888` | -| `service.electionPort` | ZooKeeper election port | `3888` | -| `service.publishNotReadyAddresses` | If the ZooKeeper headless service should publish DNS records for not ready pods | `true` | -| `serviceAccount.create` | Enable creation of ServiceAccount for zookeeper pod | `false` | -| `serviceAccount.name` | The name of the service account to use. If not set and `create` is `true`, a name is generated | Generated using the `zookeeper.fullname` template | -| `service.tls.client_enable` | Enable tls for client connections | `false` | -| `service.tls.quorum_enable` | Enable tls for quorum protocol | `false` | -| `service.tls.disable_base_client_port` | Remove client port from service definitions. | `false` | -| `service.tls.client_port` | Service port for tls client connections | `3181` | -| `service.tls.client_keystore_path` | KeyStore file path. Refer to extraVolumes and extraVolumeMounts for mounting files into the pods | `/tls_key_store/key_store_file` | -| `service.tls.client_keystore_password` | KeyStore password. You can use environment variables. | `nil` | -| `service.tls.client_truststore_path` | TrustStore file path. Refer to extraVolumes and extraVolumeMounts for mounting files into the pods | `/tls_trust_store/trust_store_file` | -| `service.tls.client_truststore_password` | TrustStore password. You can use environment variables. | `nil` | -| `service.tls.quorum_keystore_path` | KeyStore file path. Refer to extraVolumes and extraVolumeMounts for mounting files into the pods | `/tls_key_store/key_store_file` | -| `service.tls.quorum_keystore_password` | KeyStore password. You can use environment variables. | `nil` | -| `service.tls.quorum_truststore_path` | TrustStore file path. Refer to extraVolumes and extraVolumeMounts for mounting files into the pods | `/tls_trust_store/trust_store_file` | -| `service.tls.quorum_truststore_password` | TrustStore password. You can use environment variables. | `nil` | -| `service.annotations` | Annotations for the Service | `{}` | -| `service.headless.annotations` | Annotations for the Headless Service | `{}` | -| `networkPolicy.enabled` | Enable NetworkPolicy | `false` | -| `networkPolicy.allowExternal` | Don't require client label for connections | `true` | +### Traffic Exposure parameters + +| Name | Description | Value | +| ------------------------------------------- | --------------------------------------------------------------------------------------- | ----------- | +| `service.type` | Kubernetes Service type | `ClusterIP` | +| `service.ports.client` | ZooKeeper client service port | `2181` | +| `service.ports.tls` | ZooKeeper TLS service port | `3181` | +| `service.ports.follower` | ZooKeeper follower service port | `2888` | +| `service.ports.election` | ZooKeeper election service port | `3888` | +| `service.nodePorts.client` | Node port for clients | `""` | +| `service.nodePorts.tls` | Node port for TLS | `""` | +| `service.disableBaseClientPort` | Remove client port from service definitions. | `false` | +| `service.sessionAffinity` | Control where client requests go, to the same pod or round-robin | `None` | +| `service.sessionAffinityConfig` | Additional settings for the sessionAffinity | `{}` | +| `service.clusterIP` | ZooKeeper service Cluster IP | `""` | +| `service.loadBalancerIP` | ZooKeeper service Load Balancer IP | `""` | +| `service.loadBalancerSourceRanges` | ZooKeeper service Load Balancer sources | `[]` | +| `service.externalTrafficPolicy` | ZooKeeper service external traffic policy | `Cluster` | +| `service.annotations` | Additional custom annotations for ZooKeeper service | `{}` | +| `service.extraPorts` | Extra ports to expose in the ZooKeeper service (normally used with the `sidecar` value) | `[]` | +| `service.headless.annotations` | Annotations for the Headless Service | `{}` | +| `service.headless.publishNotReadyAddresses` | If the ZooKeeper headless service should publish DNS records for not ready pods | `true` | +| `service.headless.servicenameOverride` | String to partially override headless service name | `""` | +| `networkPolicy.enabled` | Specifies whether a NetworkPolicy should be created | `false` | +| `networkPolicy.allowExternal` | Don't require client label for connections | `true` | + + +### Other Parameters + +| Name | Description | Value | +| --------------------------------------------- | ---------------------------------------------------------------------- | ------- | +| `serviceAccount.create` | Enable creation of ServiceAccount for ZooKeeper pod | `false` | +| `serviceAccount.name` | The name of the ServiceAccount to use. | `""` | +| `serviceAccount.automountServiceAccountToken` | Allows auto mount of ServiceAccountToken on the serviceAccount created | `true` | +| `serviceAccount.annotations` | Additional custom annotations for the ServiceAccount | `{}` | + ### Persistence parameters -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `persistence.enabled` | Enable Zookeeper data persistence using PVC | `true` | -| `persistence.existingClaim` | Provide an existing `PersistentVolumeClaim` | `nil` (evaluated as a template) | -| `persistence.storageClass` | PVC Storage Class for ZooKeeper data volume | `nil` | -| `persistence.accessMode` | PVC Access Mode for ZooKeeper data volume | `ReadWriteOnce` | -| `persistence.size` | PVC Storage Request for ZooKeeper data volume | `8Gi` | -| `persistence.annotations` | Annotations for the PVC | `{}` (evaluated as a template) | -| `persistence.dataLogDir.size` | PVC Storage Request for ZooKeeper's Data log directory | `8Gi` | -| `persistence.dataLogDir.existingClaim` | Provide an existing `PersistentVolumeClaim` for Zookeeper's Data log directory | `nil` (evaluated as a template) | +| Name | Description | Value | +| -------------------------------------- | ------------------------------------------------------------------------------ | ------------------- | +| `persistence.enabled` | Enable ZooKeeper data persistence using PVC. If false, use emptyDir | `true` | +| `persistence.existingClaim` | Name of an existing PVC to use (only when deploying a single replica) | `""` | +| `persistence.storageClass` | PVC Storage Class for ZooKeeper data volume | `""` | +| `persistence.accessModes` | PVC Access modes | `["ReadWriteOnce"]` | +| `persistence.size` | PVC Storage Request for ZooKeeper data volume | `8Gi` | +| `persistence.annotations` | Annotations for the PVC | `{}` | +| `persistence.labels` | Labels for the PVC | `{}` | +| `persistence.selector` | Selector to match an existing Persistent Volume for ZooKeeper's data PVC | `{}` | +| `persistence.dataLogDir.size` | PVC Storage Request for ZooKeeper's dedicated data log directory | `8Gi` | +| `persistence.dataLogDir.existingClaim` | Provide an existing `PersistentVolumeClaim` for ZooKeeper's data log directory | `""` | +| `persistence.dataLogDir.selector` | Selector to match an existing Persistent Volume for ZooKeeper's data log PVC | `{}` | + ### Volume Permissions parameters -| Parameter | Description | Default | -|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| -| `volumePermissions.enabled` | Enable init container that changes the owner and group of the persistent volume(s) mountpoint to `runAsUser:fsGroup` | `false` | -| `volumePermissions.image.registry` | Init container volume-permissions image registry | `docker.io` | -| `volumePermissions.image.repository` | Init container volume-permissions image name | `bitnami/minideb` | -| `volumePermissions.image.tag` | Init container volume-permissions image tag | `buster` | -| `volumePermissions.image.pullPolicy` | Init container volume-permissions image pull policy | `Always` | -| `volumePermissions.resources` | Init container resource requests/limit | `nil` | +| Name | Description | Value | +| ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `volumePermissions.enabled` | Enable init container that changes the owner and group of the persistent volume | `false` | +| `volumePermissions.image.registry` | Init container volume-permissions image registry | `docker.io` | +| `volumePermissions.image.repository` | Init container volume-permissions image repository | `bitnami/bitnami-shell` | +| `volumePermissions.image.tag` | Init container volume-permissions image tag (immutable tags are recommended) | `11-debian-11-r69` | +| `volumePermissions.image.digest` | Init container volume-permissions image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag | `""` | +| `volumePermissions.image.pullPolicy` | Init container volume-permissions image pull policy | `IfNotPresent` | +| `volumePermissions.image.pullSecrets` | Init container volume-permissions image pull secrets | `[]` | +| `volumePermissions.resources.limits` | Init container volume-permissions resource limits | `{}` | +| `volumePermissions.resources.requests` | Init container volume-permissions resource requests | `{}` | +| `volumePermissions.containerSecurityContext.enabled` | Enabled init container Security Context | `true` | +| `volumePermissions.containerSecurityContext.runAsUser` | User ID for the init container | `0` | + ### Metrics parameters -| Parameter | Description | Default | -|-------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------| -| `metrics.enabled` | Enable prometheus to access zookeeper metrics endpoint | `false` | -| `metrics.containerPort` | Port where a Jetty server will expose Prometheus metrics | `9141` | -| `metrics.service.type` | Kubernetes service type (`ClusterIP`, `NodePort` or `LoadBalancer`) for Jetty server exposing Prometheus metrics | `ClusterIP` | -| `metrics.service.port` | Prometheus metrics service port | `9141` | -| `metrics.service.annotations` | Service annotations for Prometheus to auto-discover the metrics endpoint | `{prometheus.io/scrape: "true", prometheus.io/port: "9141"}` | -| `metrics.serviceMonitor.enabled` | if `true`, creates a Prometheus Operator ServiceMonitor (also requires `metrics.enabled` to be `true`) | `false` | -| `metrics.serviceMonitor.namespace` | Namespace for the ServiceMonitor Resource | The Release Namespace | -| `metrics.serviceMonitor.interval` | Interval at which metrics should be scraped. | `nil` (Prometheus Operator default value) | -| `metrics.serviceMonitor.scrapeTimeout` | Timeout after which the scrape is ended | `nil` (Prometheus Operator default value) | -| `metrics.serviceMonitor.selector` | Prometheus instance selector labels | `nil` | -| `metrics.prometheusRule.enabled` | if `true`, creates a Prometheus Operator PrometheusRule (also requires `metrics.enabled` to be `true` and `metrics.prometheusRule.rules`) | `false` | -| `metrics.prometheusRule.namespace` | Namespace for the PrometheusRule Resource | The Release Namespace | -| `metrics.prometheusRule.selector` | Prometheus instance selector labels | `nil` | -| `metrics.prometheusRule.rules` | Prometheus Rule definitions (see values.yaml for examples) | `[]` | +| Name | Description | Value | +| ------------------------------------------ | ------------------------------------------------------------------------------------- | ----------- | +| `metrics.enabled` | Enable Prometheus to access ZooKeeper metrics endpoint | `false` | +| `metrics.containerPort` | ZooKeeper Prometheus Exporter container port | `9141` | +| `metrics.service.type` | ZooKeeper Prometheus Exporter service type | `ClusterIP` | +| `metrics.service.port` | ZooKeeper Prometheus Exporter service port | `9141` | +| `metrics.service.annotations` | Annotations for Prometheus to auto-discover the metrics endpoint | `{}` | +| `metrics.serviceMonitor.enabled` | Create ServiceMonitor Resource for scraping metrics using Prometheus Operator | `false` | +| `metrics.serviceMonitor.namespace` | Namespace for the ServiceMonitor Resource (defaults to the Release Namespace) | `""` | +| `metrics.serviceMonitor.interval` | Interval at which metrics should be scraped. | `""` | +| `metrics.serviceMonitor.scrapeTimeout` | Timeout after which the scrape is ended | `""` | +| `metrics.serviceMonitor.additionalLabels` | Additional labels that can be used so ServiceMonitor will be discovered by Prometheus | `{}` | +| `metrics.serviceMonitor.selector` | Prometheus instance selector labels | `{}` | +| `metrics.serviceMonitor.relabelings` | RelabelConfigs to apply to samples before scraping | `[]` | +| `metrics.serviceMonitor.metricRelabelings` | MetricRelabelConfigs to apply to samples before ingestion | `[]` | +| `metrics.serviceMonitor.honorLabels` | Specify honorLabels parameter to add the scrape endpoint | `false` | +| `metrics.serviceMonitor.jobLabel` | The name of the label on the target service to use as the job name in prometheus. | `""` | +| `metrics.prometheusRule.enabled` | Create a PrometheusRule for Prometheus Operator | `false` | +| `metrics.prometheusRule.namespace` | Namespace for the PrometheusRule Resource (defaults to the Release Namespace) | `""` | +| `metrics.prometheusRule.additionalLabels` | Additional labels that can be used so PrometheusRule will be discovered by Prometheus | `{}` | +| `metrics.prometheusRule.rules` | PrometheusRule definitions | `[]` | + + +### TLS/SSL parameters + +| Name | Description | Value | +| ----------------------------------------- | -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | +| `tls.client.enabled` | Enable TLS for client connections | `false` | +| `tls.client.auth` | SSL Client auth. Can be "none", "want" or "need". | `none` | +| `tls.client.autoGenerated` | Generate automatically self-signed TLS certificates for ZooKeeper client communications | `false` | +| `tls.client.existingSecret` | Name of the existing secret containing the TLS certificates for ZooKeeper client communications | `""` | +| `tls.client.existingSecretKeystoreKey` | The secret key from the tls.client.existingSecret containing the Keystore. | `""` | +| `tls.client.existingSecretTruststoreKey` | The secret key from the tls.client.existingSecret containing the Truststore. | `""` | +| `tls.client.keystorePath` | Location of the KeyStore file used for Client connections | `/opt/bitnami/zookeeper/config/certs/client/zookeeper.keystore.jks` | +| `tls.client.truststorePath` | Location of the TrustStore file used for Client connections | `/opt/bitnami/zookeeper/config/certs/client/zookeeper.truststore.jks` | +| `tls.client.passwordsSecretName` | Existing secret containing Keystore and truststore passwords | `""` | +| `tls.client.passwordsSecretKeystoreKey` | The secret key from the tls.client.passwordsSecretName containing the password for the Keystore. | `""` | +| `tls.client.passwordsSecretTruststoreKey` | The secret key from the tls.client.passwordsSecretName containing the password for the Truststore. | `""` | +| `tls.client.keystorePassword` | Password to access KeyStore if needed | `""` | +| `tls.client.truststorePassword` | Password to access TrustStore if needed | `""` | +| `tls.quorum.enabled` | Enable TLS for quorum protocol | `false` | +| `tls.quorum.auth` | SSL Quorum Client auth. Can be "none", "want" or "need". | `none` | +| `tls.quorum.autoGenerated` | Create self-signed TLS certificates. Currently only supports PEM certificates. | `false` | +| `tls.quorum.existingSecret` | Name of the existing secret containing the TLS certificates for ZooKeeper quorum protocol | `""` | +| `tls.quorum.existingSecretKeystoreKey` | The secret key from the tls.quorum.existingSecret containing the Keystore. | `""` | +| `tls.quorum.existingSecretTruststoreKey` | The secret key from the tls.quorum.existingSecret containing the Truststore. | `""` | +| `tls.quorum.keystorePath` | Location of the KeyStore file used for Quorum protocol | `/opt/bitnami/zookeeper/config/certs/quorum/zookeeper.keystore.jks` | +| `tls.quorum.truststorePath` | Location of the TrustStore file used for Quorum protocol | `/opt/bitnami/zookeeper/config/certs/quorum/zookeeper.truststore.jks` | +| `tls.quorum.passwordsSecretName` | Existing secret containing Keystore and truststore passwords | `""` | +| `tls.quorum.passwordsSecretKeystoreKey` | The secret key from the tls.quorum.passwordsSecretName containing the password for the Keystore. | `""` | +| `tls.quorum.passwordsSecretTruststoreKey` | The secret key from the tls.quorum.passwordsSecretName containing the password for the Truststore. | `""` | +| `tls.quorum.keystorePassword` | Password to access KeyStore if needed | `""` | +| `tls.quorum.truststorePassword` | Password to access TrustStore if needed | `""` | +| `tls.resources.limits` | The resources limits for the TLS init container | `{}` | +| `tls.resources.requests` | The requested resources for the TLS init container | `{}` | + Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, ```console $ helm install my-release \ --set auth.clientUser=newUser \ - bitnami/zookeeper + my-repo/zookeeper ``` The above command sets the ZooKeeper user to `newUser`. +> NOTE: Once this chart is deployed, it is not possible to change the application's access credentials, such as usernames or passwords, using Helm. To change these application credentials after deployment, delete any persistent volumes (PVs) used by the chart and re-deploy it, or use the application's built-in administrative tools if available. + Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, ```console -$ helm install my-release -f values.yaml bitnami/zookeeper +$ helm install my-release -f values.yaml my-repo/zookeeper ``` > **Tip**: You can use the default [values.yaml](values.yaml) ## Configuration and installation details -### [Rolling VS Immutable tags](https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/) +### [Rolling vs Immutable tags](https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/) It is strongly recommended to use immutable tags in a production environment. This ensures your deployment does not change automatically if the same tag is updated with a different image. Bitnami will release a new chart updating its containers if a new version of the main container, significant changes, or critical vulnerabilities exist. -### Production configuration +### Configure log level -This chart includes a `values-production.yaml` file where you can find some parameters oriented to production configuration in comparison to the regular `values.yaml`. You can use this file instead of the default one. +You can configure the ZooKeeper log level using the `ZOO_LOG_LEVEL` environment variable or the parameter `logLevel`. By default, it is set to `ERROR` because each use of the liveness probe and the readiness probe produces an `INFO` message on connection and a `WARN` message on disconnection, generating a high volume of noise in your logs. -- Number of ZooKeeper nodes: +In order to remove that log noise so levels can be set to 'INFO', two changes must be made. -```diff -- replicaCount: 1 -+ replicaCount: 3 +First, ensure that you are not getting metrics via the deprecated pattern of polling 'mntr' on the ZooKeeper client port. The preferred method of polling for Apache ZooKeeper metrics is the ZooKeeper metrics server. This is supported in this chart when setting `metrics.enabled` to `true`. + +Second, to avoid the connection/disconnection messages from the probes, you can set custom values for these checks which direct them to the ZooKeeper Admin Server instead of the client port. By default, an Admin Server will be started that listens on `localhost` at port `8080`. The following is an example of this use of the Admin Server for probes: + +``` +livenessProbe: + enabled: false +readinessProbe: + enabled: false +customLivenessProbe: + exec: + command: ['/bin/bash', '-c', 'curl -s -m 2 http://localhost:8080/commands/ruok | grep ruok'] + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 6 +customReadinessProbe: + exec: + command: ['/bin/bash', '-c', 'curl -s -m 2 http://localhost:8080/commands/ruok | grep error | grep null'] + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 6 ``` -- Enable prometheus metrics: +You can also set the log4j logging level and what log appenders are turned on, by using `ZOO_LOG4J_PROP` set inside of conf/log4j.properties as zookeeper.root.logger by default to -```diff -- metrics.enabled: false -+ metrics.enabled: true +```console +zookeeper.root.logger=INFO, CONSOLE ``` +the available appender is -### Log level - -You can configure the ZooKeeper log level using the `ZOO_LOG_LEVEL` environment variable. By default, it is set to `ERROR` because of each readiness probe produce an `INFO` message on connection and a `WARN` message on disconnection. +- CONSOLE +- ROLLINGFILE +- RFAAUDIT +- TRACEFILE ## Persistence -The [Bitnami ZooKeeper](https://github.com/bitnami/bitnami-docker-zookeeper) image stores the ZooKeeper data and configurations at the `/bitnami/zookeeper` path of the container. +The [Bitnami ZooKeeper](https://github.com/bitnami/containers/tree/main/bitnami/zookeeper) image stores the ZooKeeper data and configurations at the `/bitnami/zookeeper` path of the container. -Persistent Volume Claims are used to keep the data across deployments. This is known to work in GCE, AWS, and minikube. -See the [Parameters](#parameters) section to configure the PVC or to disable persistence. +Persistent Volume Claims are used to keep the data across deployments. This is known to work in GCE, AWS, and minikube. See the [Parameters](#parameters) section to configure the PVC or to disable persistence. + +If you encounter errors when working with persistent volumes, refer to our [troubleshooting guide for persistent volumes](https://docs.bitnami.com/kubernetes/faq/troubleshooting/troubleshooting-persistence-volumes/). ### Adjust permissions of persistent volume mountpoint @@ -256,14 +414,76 @@ As an alternative, this chart supports using an initContainer to change the owne You can enable this initContainer by setting `volumePermissions.enabled` to `true`. -### Data Log Directory +### Configure the data log directory -You can use a dedicated device for logs (instead of using the data directory) to help avoiding competition between logging and snapshots. To do so, set the `dataLogDir` parameter with the path to be used for writing transaction logs. Alternatively, set this parameter with an empty string an it result in the log being written to the data directory (Zookeeper's default behavior). +You can use a dedicated device for logs (instead of using the data directory) to help avoiding competition between logging and snaphots. To do so, set the `dataLogDir` parameter with the path to be used for writing transaction logs. Alternatively, set this parameter with an empty string and it will result in the log being written to the data directory (Zookeeper's default behavior). When using a dedicated device for logs, you can use a PVC to persist the logs. To do so, set `persistence.enabled` to `true`. See the [Persistence Parameters](#persistence-parameters) section for more information. +### Set pod affinity + +This chart allows you to set custom pod affinity using the `affinity` parameter. Find more information about pod affinity in the [Kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity). + +As an alternative, you can use any of the preset configurations for pod affinity, pod anti-affinity, and node affinity available at the [bitnami/common](https://github.com/bitnami/charts/tree/main/bitnami/common#affinities) chart. To do so, set the `podAffinityPreset`, `podAntiAffinityPreset`, or `nodeAffinityPreset` parameters. + +## Troubleshooting + +Find more information about how to deal with common errors related to Bitnami's Helm charts in [this troubleshooting guide](https://docs.bitnami.com/general/how-to/troubleshoot-helm-chart-issues). + ## Upgrading +### To 11.0.0 + +This major version removes `commonAnnotations` and `commonLabels` from `volumeClaimTemplates`. Now annotations and labels can be set in volume claims using `persistence.annotations` and `persistence.labels` values. If the previous deployment has already set `commonAnnotations` and/or `commonLabels` values, to ensure a clean upgrade from previous version without loosing data, please set `persistence.annotations` and/or `persistence.labels` values with the same content as the common values. + +### To 10.0.0 + +This new version of the chart adds support for server-server authentication. +The chart previously supported client-server authentication, to avoid confusion, the previous parameters have been renamed from `auth.*` to `auth.client.*`. + +### To 9.0.0 + +This new version of the chart includes the new ZooKeeper major version 3.8.0. Upgrade compatibility is not guaranteed. + +### To 8.0.0 + +This major release renames several values in this chart and adds missing features, in order to be inline with the rest of assets in the Bitnami charts repository. + +Affected values: + +- `allowAnonymousLogin` is deprecated. +- `containerPort`, `tlsContainerPort`, `followerContainerPort` and `electionContainerPort` have been regrouped under the `containerPorts` map. +- `service.port`, `service.tlsClientPort`, `service.followerPort`, and `service.electionPort` have been regrouped under the `service.ports` map. +- `updateStrategy` (string) and `rollingUpdatePartition` are regrouped under the `updateStrategy` map. +- `podDisruptionBudget.*` parameters are renamed to `pdb.*`. + +### To 7.0.0 + +This new version renames the parameters used to configure TLS for both client and quorum. + +- `service.tls.disable_base_client_port` is renamed to `service.disableBaseClientPort` +- `service.tls.client_port` is renamed to `service.tlsClientPort` +- `service.tls.client_enable` is renamed to `tls.client.enabled` +- `service.tls.client_keystore_path` is renamed to `tls.client.keystorePath` +- `service.tls.client_truststore_path` is renamed to `tls.client.truststorePath` +- `service.tls.client_keystore_password` is renamed to `tls.client.keystorePassword` +- `service.tls.client_truststore_password` is renamed to `tls.client.truststorePassword` +- `service.tls.quorum_enable` is renamed to `tls.quorum.enabled` +- `service.tls.quorum_keystore_path` is renamed to `tls.quorum.keystorePath` +- `service.tls.quorum_truststore_path` is renamed to `tls.quorum.truststorePath` +- `service.tls.quorum_keystore_password` is renamed to `tls.quorum.keystorePassword` +- `service.tls.quorum_truststore_password` is renamed to `tls.quorum.truststorePassword` + +### To 6.1.0 + +This version introduces `bitnami/common`, a [library chart](https://helm.sh/docs/topics/library_charts/#helm) as a dependency. More documentation about this new utility could be found [here](https://github.com/bitnami/charts/tree/main/bitnami/common#bitnami-common-library-chart). Please, make sure that you have updated the chart dependencies before executing any upgrade. + +### To 6.0.0 + +[On November 13, 2020, Helm v2 support was formally finished](https://github.com/helm/charts#status-of-the-project), this major version is the result of the required changes applied to the Helm Chart to be able to incorporate the different features added in Helm v3 and to be consistent with the Helm project itself regarding the Helm v2 EOL. + +[Learn more about this change and related upgrade considerations](https://docs.bitnami.com/kubernetes/infrastructure/zookeeper/administration/upgrade-helm3/). + ### To 5.21.0 A couple of parameters related to Zookeeper metrics were renamed or disappeared in favor of new ones: @@ -295,3 +515,19 @@ Use the workaround below to upgrade from versions previous to 1.0.0. The followi ```console $ kubectl delete statefulset zookeeper-zookeeper --cascade=false ``` + +## License + +Copyright © 2022 Bitnami + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/.helmignore b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/.helmignore new file mode 100644 index 000000000..50af03172 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/Chart.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/Chart.yaml new file mode 100644 index 000000000..f9ba944c8 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/Chart.yaml @@ -0,0 +1,23 @@ +annotations: + category: Infrastructure +apiVersion: v2 +appVersion: 2.2.2 +description: A Library Helm Chart for grouping common logic between bitnami charts. + This chart is not deployable by itself. +home: https://github.com/bitnami/charts/tree/main/bitnami/common +icon: https://bitnami.com/downloads/logos/bitnami-mark.png +keywords: +- common +- helper +- template +- function +- bitnami +maintainers: +- name: Bitnami + url: https://github.com/bitnami/charts +name: common +sources: +- https://github.com/bitnami/charts +- https://www.bitnami.com/ +type: library +version: 2.2.2 diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/README.md b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/README.md new file mode 100644 index 000000000..ec43a5fab --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/README.md @@ -0,0 +1,351 @@ +# Bitnami Common Library Chart + +A [Helm Library Chart](https://helm.sh/docs/topics/library_charts/#helm) for grouping common logic between bitnami charts. + +## TL;DR + +```yaml +dependencies: + - name: common + version: 1.x.x + repository: https://charts.bitnami.com/bitnami +``` + +```bash +$ helm dependency update +``` + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "common.names.fullname" . }} +data: + myvalue: "Hello World" +``` + +## Introduction + +This chart provides a common template helpers which can be used to develop new charts using [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.dev/) for deployment and management of Helm Charts in clusters. + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.2.0+ + +## Parameters + +The following table lists the helpers available in the library which are scoped in different sections. + +### Affinities + +| Helper identifier | Description | Expected Input | +|-------------------------------|------------------------------------------------------|------------------------------------------------| +| `common.affinities.nodes.soft` | Return a soft nodeAffinity definition | `dict "key" "FOO" "values" (list "BAR" "BAZ")` | +| `common.affinities.nodes.hard` | Return a hard nodeAffinity definition | `dict "key" "FOO" "values" (list "BAR" "BAZ")` | +| `common.affinities.pods.soft` | Return a soft podAffinity/podAntiAffinity definition | `dict "component" "FOO" "context" $` | +| `common.affinities.pods.hard` | Return a hard podAffinity/podAntiAffinity definition | `dict "component" "FOO" "context" $` | +| `common.affinities.topologyKey` | Return a topologyKey definition | `dict "topologyKey" "FOO"` | + +### Capabilities + +| Helper identifier | Description | Expected Input | +|------------------------------------------------|------------------------------------------------------------------------------------------------|-------------------| +| `common.capabilities.kubeVersion` | Return the target Kubernetes version (using client default if .Values.kubeVersion is not set). | `.` Chart context | +| `common.capabilities.cronjob.apiVersion` | Return the appropriate apiVersion for cronjob. | `.` Chart context | +| `common.capabilities.deployment.apiVersion` | Return the appropriate apiVersion for deployment. | `.` Chart context | +| `common.capabilities.statefulset.apiVersion` | Return the appropriate apiVersion for statefulset. | `.` Chart context | +| `common.capabilities.ingress.apiVersion` | Return the appropriate apiVersion for ingress. | `.` Chart context | +| `common.capabilities.rbac.apiVersion` | Return the appropriate apiVersion for RBAC resources. | `.` Chart context | +| `common.capabilities.crd.apiVersion` | Return the appropriate apiVersion for CRDs. | `.` Chart context | +| `common.capabilities.policy.apiVersion` | Return the appropriate apiVersion for podsecuritypolicy. | `.` Chart context | +| `common.capabilities.networkPolicy.apiVersion` | Return the appropriate apiVersion for networkpolicy. | `.` Chart context | +| `common.capabilities.apiService.apiVersion` | Return the appropriate apiVersion for APIService. | `.` Chart context | +| `common.capabilities.hpa.apiVersion` | Return the appropriate apiVersion for Horizontal Pod Autoscaler | `.` Chart context | +| `common.capabilities.supportsHelmVersion` | Returns true if the used Helm version is 3.3+ | `.` Chart context | + +### Errors + +| Helper identifier | Description | Expected Input | +|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------| +| `common.errors.upgrade.passwords.empty` | It will ensure required passwords are given when we are upgrading a chart. If `validationErrors` is not empty it will throw an error and will stop the upgrade action. | `dict "validationErrors" (list $validationError00 $validationError01) "context" $` | + +### Images + +| Helper identifier | Description | Expected Input | +|-----------------------------|------------------------------------------------------|---------------------------------------------------------------------------------------------------------| +| `common.images.image` | Return the proper and full image name | `dict "imageRoot" .Values.path.to.the.image "global" $`, see [ImageRoot](#imageroot) for the structure. | +| `common.images.pullSecrets` | Return the proper Docker Image Registry Secret Names (deprecated: use common.images.renderPullSecrets instead) | `dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global` | +| `common.images.renderPullSecrets` | Return the proper Docker Image Registry Secret Names (evaluates values as templates) | `dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "context" $` | + +### Ingress + +| Helper identifier | Description | Expected Input | +|-------------------------------------------|-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.ingress.backend` | Generate a proper Ingress backend entry depending on the API version | `dict "serviceName" "foo" "servicePort" "bar"`, see the [Ingress deprecation notice](https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/) for the syntax differences | +| `common.ingress.supportsPathType` | Prints "true" if the pathType field is supported | `.` Chart context | +| `common.ingress.supportsIngressClassname` | Prints "true" if the ingressClassname field is supported | `.` Chart context | +| `common.ingress.certManagerRequest` | Prints "true" if required cert-manager annotations for TLS signed certificates are set in the Ingress annotations | `dict "annotations" .Values.path.to.the.ingress.annotations` | + +### Labels + +| Helper identifier | Description | Expected Input | +|-----------------------------|-----------------------------------------------------------------------------|-------------------| +| `common.labels.standard` | Return Kubernetes standard labels | `.` Chart context | +| `common.labels.matchLabels` | Labels to use on `deploy.spec.selector.matchLabels` and `svc.spec.selector` | `.` Chart context | + +### Names + +| Helper identifier | Description | Expected Input | +|-----------------------------------|-----------------------------------------------------------------------|-------------------| +| `common.names.name` | Expand the name of the chart or use `.Values.nameOverride` | `.` Chart context | +| `common.names.fullname` | Create a default fully qualified app name. | `.` Chart context | +| `common.names.namespace` | Allow the release namespace to be overridden | `.` Chart context | +| `common.names.fullname.namespace` | Create a fully qualified app name adding the installation's namespace | `.` Chart context | +| `common.names.chart` | Chart name plus version | `.` Chart context | + +### Secrets + +| Helper identifier | Description | Expected Input | +|-----------------------------------|--------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.secrets.name` | Generate the name of the secret. | `dict "existingSecret" .Values.path.to.the.existingSecret "defaultNameSuffix" "mySuffix" "context" $` see [ExistingSecret](#existingsecret) for the structure. | +| `common.secrets.key` | Generate secret key. | `dict "existingSecret" .Values.path.to.the.existingSecret "key" "keyName"` see [ExistingSecret](#existingsecret) for the structure. | +| `common.secrets.passwords.manage` | Generate secret password or retrieve one if already created. | `dict "secret" "secret-name" "key" "keyName" "providedValues" (list "path.to.password1" "path.to.password2") "length" 10 "strong" false "chartName" "chartName" "context" $`, length, strong and chartNAme fields are optional. | +| `common.secrets.exists` | Returns whether a previous generated secret already exists. | `dict "secret" "secret-name" "context" $` | + +### Storage + +| Helper identifier | Description | Expected Input | +|-------------------------------|---------------------------------------|---------------------------------------------------------------------------------------------------------------------| +| `common.storage.class` | Return the proper Storage Class | `dict "persistence" .Values.path.to.the.persistence "global" $`, see [Persistence](#persistence) for the structure. | + +### TplValues + +| Helper identifier | Description | Expected Input | +|---------------------------|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.tplvalues.render` | Renders a value that contains template | `dict "value" .Values.path.to.the.Value "context" $`, value is the value should rendered as template, context frequently is the chart context `$` or `.` | + +### Utils + +| Helper identifier | Description | Expected Input | +|--------------------------------|------------------------------------------------------------------------------------------|------------------------------------------------------------------------| +| `common.utils.fieldToEnvVar` | Build environment variable name given a field. | `dict "field" "my-password"` | +| `common.utils.secret.getvalue` | Print instructions to get a secret value. | `dict "secret" "secret-name" "field" "secret-value-field" "context" $` | +| `common.utils.getValueFromKey` | Gets a value from `.Values` object given its key path | `dict "key" "path.to.key" "context" $` | +| `common.utils.getKeyFromList` | Returns first `.Values` key with a defined value or first of the list if all non-defined | `dict "keys" (list "path.to.key1" "path.to.key2") "context" $` | + +### Validations + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.validations.values.single.empty` | Validate a value must not be empty. | `dict "valueKey" "path.to.value" "secret" "secret.name" "field" "my-password" "subchart" "subchart" "context" $` secret, field and subchart are optional. In case they are given, the helper will generate a how to get instruction. See [ValidateValue](#validatevalue) | +| `common.validations.values.multiple.empty` | Validate a multiple values must not be empty. It returns a shared error for all the values. | `dict "required" (list $validateValueConf00 $validateValueConf01) "context" $`. See [ValidateValue](#validatevalue) | +| `common.validations.values.mariadb.passwords` | This helper will ensure required password for MariaDB are not empty. It returns a shared error for all the values. | `dict "secret" "mariadb-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use mariadb chart and the helper. | +| `common.validations.values.mysql.passwords` | This helper will ensure required password for MySQL are not empty. It returns a shared error for all the values. | `dict "secret" "mysql-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use mysql chart and the helper. | +| `common.validations.values.postgresql.passwords` | This helper will ensure required password for PostgreSQL are not empty. It returns a shared error for all the values. | `dict "secret" "postgresql-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use postgresql chart and the helper. | +| `common.validations.values.redis.passwords` | This helper will ensure required password for Redis® are not empty. It returns a shared error for all the values. | `dict "secret" "redis-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use redis chart and the helper. | +| `common.validations.values.cassandra.passwords` | This helper will ensure required password for Cassandra are not empty. It returns a shared error for all the values. | `dict "secret" "cassandra-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use cassandra chart and the helper. | +| `common.validations.values.mongodb.passwords` | This helper will ensure required password for MongoDB® are not empty. It returns a shared error for all the values. | `dict "secret" "mongodb-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use mongodb chart and the helper. | + +### Warnings + +| Helper identifier | Description | Expected Input | +|------------------------------|----------------------------------|------------------------------------------------------------| +| `common.warnings.rollingTag` | Warning about using rolling tag. | `ImageRoot` see [ImageRoot](#imageroot) for the structure. | + +## Special input schemas + +### ImageRoot + +```yaml +registry: + type: string + description: Docker registry where the image is located + example: docker.io + +repository: + type: string + description: Repository and image name + example: bitnami/nginx + +tag: + type: string + description: image tag + example: 1.16.1-debian-10-r63 + +pullPolicy: + type: string + description: Specify a imagePullPolicy. Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + +pullSecrets: + type: array + items: + type: string + description: Optionally specify an array of imagePullSecrets (evaluated as templates). + +debug: + type: boolean + description: Set to true if you would like to see extra information on logs + example: false + +## An instance would be: +# registry: docker.io +# repository: bitnami/nginx +# tag: 1.16.1-debian-10-r63 +# pullPolicy: IfNotPresent +# debug: false +``` + +### Persistence + +```yaml +enabled: + type: boolean + description: Whether enable persistence. + example: true + +storageClass: + type: string + description: Ghost data Persistent Volume Storage Class, If set to "-", storageClassName: "" which disables dynamic provisioning. + example: "-" + +accessMode: + type: string + description: Access mode for the Persistent Volume Storage. + example: ReadWriteOnce + +size: + type: string + description: Size the Persistent Volume Storage. + example: 8Gi + +path: + type: string + description: Path to be persisted. + example: /bitnami + +## An instance would be: +# enabled: true +# storageClass: "-" +# accessMode: ReadWriteOnce +# size: 8Gi +# path: /bitnami +``` + +### ExistingSecret + +```yaml +name: + type: string + description: Name of the existing secret. + example: mySecret +keyMapping: + description: Mapping between the expected key name and the name of the key in the existing secret. + type: object + +## An instance would be: +# name: mySecret +# keyMapping: +# password: myPasswordKey +``` + +#### Example of use + +When we store sensitive data for a deployment in a secret, some times we want to give to users the possibility of using theirs existing secrets. + +```yaml +# templates/secret.yaml +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "common.names.fullname" . }} + labels: + app: {{ include "common.names.fullname" . }} +type: Opaque +data: + password: {{ .Values.password | b64enc | quote }} + +# templates/dpl.yaml +--- +... + env: + - name: PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "common.secrets.name" (dict "existingSecret" .Values.existingSecret "context" $) }} + key: {{ include "common.secrets.key" (dict "existingSecret" .Values.existingSecret "key" "password") }} +... + +# values.yaml +--- +name: mySecret +keyMapping: + password: myPasswordKey +``` + +### ValidateValue + +#### NOTES.txt + +```console +{{- $validateValueConf00 := (dict "valueKey" "path.to.value00" "secret" "secretName" "field" "password-00") -}} +{{- $validateValueConf01 := (dict "valueKey" "path.to.value01" "secret" "secretName" "field" "password-01") -}} + +{{ include "common.validations.values.multiple.empty" (dict "required" (list $validateValueConf00 $validateValueConf01) "context" $) }} +``` + +If we force those values to be empty we will see some alerts + +```console +$ helm install test mychart --set path.to.value00="",path.to.value01="" + 'path.to.value00' must not be empty, please add '--set path.to.value00=$PASSWORD_00' to the command. To get the current value: + + export PASSWORD_00=$(kubectl get secret --namespace default secretName -o jsonpath="{.data.password-00}" | base64 -d) + + 'path.to.value01' must not be empty, please add '--set path.to.value01=$PASSWORD_01' to the command. To get the current value: + + export PASSWORD_01=$(kubectl get secret --namespace default secretName -o jsonpath="{.data.password-01}" | base64 -d) +``` + +## Upgrading + +### To 1.0.0 + +[On November 13, 2020, Helm v2 support was formally finished](https://github.com/helm/charts#status-of-the-project), this major version is the result of the required changes applied to the Helm Chart to be able to incorporate the different features added in Helm v3 and to be consistent with the Helm project itself regarding the Helm v2 EOL. + +**What changes were introduced in this major version?** + +- Previous versions of this Helm Chart use `apiVersion: v1` (installable by both Helm 2 and 3), this Helm Chart was updated to `apiVersion: v2` (installable by Helm 3 only). [Here](https://helm.sh/docs/topics/charts/#the-apiversion-field) you can find more information about the `apiVersion` field. +- Use `type: library`. [Here](https://v3.helm.sh/docs/faq/#library-chart-support) you can find more information. +- The different fields present in the *Chart.yaml* file has been ordered alphabetically in a homogeneous way for all the Bitnami Helm Charts + +**Considerations when upgrading to this version** + +- If you want to upgrade to this version from a previous one installed with Helm v3, you shouldn't face any issues +- If you want to upgrade to this version using Helm v2, this scenario is not supported as this version doesn't support Helm v2 anymore +- If you installed the previous version with Helm v2 and wants to upgrade to this version with Helm v3, please refer to the [official Helm documentation](https://helm.sh/docs/topics/v2_v3_migration/#migration-use-cases) about migrating from Helm v2 to v3 + +**Useful links** + +- https://docs.bitnami.com/tutorials/resolve-helm2-helm3-post-migration-issues/ +- https://helm.sh/docs/topics/v2_v3_migration/ +- https://helm.sh/blog/migrate-from-helm-v2-to-helm-v3/ + +## License + +Copyright © 2022 Bitnami + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_affinities.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_affinities.tpl new file mode 100644 index 000000000..81902a681 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_affinities.tpl @@ -0,0 +1,106 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Return a soft nodeAffinity definition +{{ include "common.affinities.nodes.soft" (dict "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes.soft" -}} +preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: {{ .key }} + operator: In + values: + {{- range .values }} + - {{ . | quote }} + {{- end }} + weight: 1 +{{- end -}} + +{{/* +Return a hard nodeAffinity definition +{{ include "common.affinities.nodes.hard" (dict "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes.hard" -}} +requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: {{ .key }} + operator: In + values: + {{- range .values }} + - {{ . | quote }} + {{- end }} +{{- end -}} + +{{/* +Return a nodeAffinity definition +{{ include "common.affinities.nodes" (dict "type" "soft" "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes" -}} + {{- if eq .type "soft" }} + {{- include "common.affinities.nodes.soft" . -}} + {{- else if eq .type "hard" }} + {{- include "common.affinities.nodes.hard" . -}} + {{- end -}} +{{- end -}} + +{{/* +Return a topologyKey definition +{{ include "common.affinities.topologyKey" (dict "topologyKey" "BAR") -}} +*/}} +{{- define "common.affinities.topologyKey" -}} +{{ .topologyKey | default "kubernetes.io/hostname" -}} +{{- end -}} + +{{/* +Return a soft podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods.soft" (dict "component" "FOO" "extraMatchLabels" .Values.extraMatchLabels "topologyKey" "BAR" "context" $) -}} +*/}} +{{- define "common.affinities.pods.soft" -}} +{{- $component := default "" .component -}} +{{- $extraMatchLabels := default (dict) .extraMatchLabels -}} +preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: {{- (include "common.labels.matchLabels" .context) | nindent 10 }} + {{- if not (empty $component) }} + {{ printf "app.kubernetes.io/component: %s" $component }} + {{- end }} + {{- range $key, $value := $extraMatchLabels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + topologyKey: {{ include "common.affinities.topologyKey" (dict "topologyKey" .topologyKey) }} + weight: 1 +{{- end -}} + +{{/* +Return a hard podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods.hard" (dict "component" "FOO" "extraMatchLabels" .Values.extraMatchLabels "topologyKey" "BAR" "context" $) -}} +*/}} +{{- define "common.affinities.pods.hard" -}} +{{- $component := default "" .component -}} +{{- $extraMatchLabels := default (dict) .extraMatchLabels -}} +requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: {{- (include "common.labels.matchLabels" .context) | nindent 8 }} + {{- if not (empty $component) }} + {{ printf "app.kubernetes.io/component: %s" $component }} + {{- end }} + {{- range $key, $value := $extraMatchLabels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + topologyKey: {{ include "common.affinities.topologyKey" (dict "topologyKey" .topologyKey) }} +{{- end -}} + +{{/* +Return a podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods" (dict "type" "soft" "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.pods" -}} + {{- if eq .type "soft" }} + {{- include "common.affinities.pods.soft" . -}} + {{- else if eq .type "hard" }} + {{- include "common.affinities.pods.hard" . -}} + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_capabilities.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_capabilities.tpl new file mode 100644 index 000000000..9d9b76004 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_capabilities.tpl @@ -0,0 +1,154 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Return the target Kubernetes version +*/}} +{{- define "common.capabilities.kubeVersion" -}} +{{- if .Values.global }} + {{- if .Values.global.kubeVersion }} + {{- .Values.global.kubeVersion -}} + {{- else }} + {{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}} + {{- end -}} +{{- else }} +{{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for poddisruptionbudget. +*/}} +{{- define "common.capabilities.policy.apiVersion" -}} +{{- if semverCompare "<1.21-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "policy/v1beta1" -}} +{{- else -}} +{{- print "policy/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for networkpolicy. +*/}} +{{- define "common.capabilities.networkPolicy.apiVersion" -}} +{{- if semverCompare "<1.7-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for cronjob. +*/}} +{{- define "common.capabilities.cronjob.apiVersion" -}} +{{- if semverCompare "<1.21-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "batch/v1beta1" -}} +{{- else -}} +{{- print "batch/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for deployment. +*/}} +{{- define "common.capabilities.deployment.apiVersion" -}} +{{- if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for statefulset. +*/}} +{{- define "common.capabilities.statefulset.apiVersion" -}} +{{- if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apps/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for ingress. +*/}} +{{- define "common.capabilities.ingress.apiVersion" -}} +{{- if .Values.ingress -}} +{{- if .Values.ingress.apiVersion -}} +{{- .Values.ingress.apiVersion -}} +{{- else if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end }} +{{- else if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for RBAC resources. +*/}} +{{- define "common.capabilities.rbac.apiVersion" -}} +{{- if semverCompare "<1.17-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "rbac.authorization.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "rbac.authorization.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for CRDs. +*/}} +{{- define "common.capabilities.crd.apiVersion" -}} +{{- if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apiextensions.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "apiextensions.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for APIService. +*/}} +{{- define "common.capabilities.apiService.apiVersion" -}} +{{- if semverCompare "<1.10-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apiregistration.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "apiregistration.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for Horizontal Pod Autoscaler. +*/}} +{{- define "common.capabilities.hpa.apiVersion" -}} +{{- if semverCompare "<1.23-0" (include "common.capabilities.kubeVersion" .context) -}} +{{- if .beta2 -}} +{{- print "autoscaling/v2beta2" -}} +{{- else -}} +{{- print "autoscaling/v2beta1" -}} +{{- end -}} +{{- else -}} +{{- print "autoscaling/v2" -}} +{{- end -}} +{{- end -}} + +{{/* +Returns true if the used Helm version is 3.3+. +A way to check the used Helm version was not introduced until version 3.3.0 with .Capabilities.HelmVersion, which contains an additional "{}}" structure. +This check is introduced as a regexMatch instead of {{ if .Capabilities.HelmVersion }} because checking for the key HelmVersion in <3.3 results in a "interface not found" error. +**To be removed when the catalog's minimun Helm version is 3.3** +*/}} +{{- define "common.capabilities.supportsHelmVersion" -}} +{{- if regexMatch "{(v[0-9])*[^}]*}}$" (.Capabilities | toString ) }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_errors.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_errors.tpl new file mode 100644 index 000000000..a79cc2e32 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_errors.tpl @@ -0,0 +1,23 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Through error when upgrading using empty passwords values that must not be empty. + +Usage: +{{- $validationError00 := include "common.validations.values.single.empty" (dict "valueKey" "path.to.password00" "secret" "secretName" "field" "password-00") -}} +{{- $validationError01 := include "common.validations.values.single.empty" (dict "valueKey" "path.to.password01" "secret" "secretName" "field" "password-01") -}} +{{ include "common.errors.upgrade.passwords.empty" (dict "validationErrors" (list $validationError00 $validationError01) "context" $) }} + +Required password params: + - validationErrors - String - Required. List of validation strings to be return, if it is empty it won't throw error. + - context - Context - Required. Parent context. +*/}} +{{- define "common.errors.upgrade.passwords.empty" -}} + {{- $validationErrors := join "" .validationErrors -}} + {{- if and $validationErrors .context.Release.IsUpgrade -}} + {{- $errorString := "\nPASSWORDS ERROR: You must provide your current passwords when upgrading the release." -}} + {{- $errorString = print $errorString "\n Note that even after reinstallation, old credentials may be needed as they may be kept in persistent volume claims." -}} + {{- $errorString = print $errorString "\n Further information can be obtained at https://docs.bitnami.com/general/how-to/troubleshoot-helm-chart-issues/#credential-errors-while-upgrading-chart-releases" -}} + {{- $errorString = print $errorString "\n%s" -}} + {{- printf $errorString $validationErrors | fail -}} + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_images.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_images.tpl new file mode 100644 index 000000000..46c659e79 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_images.tpl @@ -0,0 +1,76 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Return the proper image name +{{ include "common.images.image" ( dict "imageRoot" .Values.path.to.the.image "global" $) }} +*/}} +{{- define "common.images.image" -}} +{{- $registryName := .imageRoot.registry -}} +{{- $repositoryName := .imageRoot.repository -}} +{{- $separator := ":" -}} +{{- $termination := .imageRoot.tag | toString -}} +{{- if .global }} + {{- if .global.imageRegistry }} + {{- $registryName = .global.imageRegistry -}} + {{- end -}} +{{- end -}} +{{- if .imageRoot.digest }} + {{- $separator = "@" -}} + {{- $termination = .imageRoot.digest | toString -}} +{{- end -}} +{{- printf "%s/%s%s%s" $registryName $repositoryName $separator $termination -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names (deprecated: use common.images.renderPullSecrets instead) +{{ include "common.images.pullSecrets" ( dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global) }} +*/}} +{{- define "common.images.pullSecrets" -}} + {{- $pullSecrets := list }} + + {{- if .global }} + {{- range .global.imagePullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} + {{- end -}} + {{- end -}} + + {{- range .images -}} + {{- range .pullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} + {{- end -}} + {{- end -}} + + {{- if (not (empty $pullSecrets)) }} +imagePullSecrets: + {{- range $pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names evaluating values as templates +{{ include "common.images.renderPullSecrets" ( dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "context" $) }} +*/}} +{{- define "common.images.renderPullSecrets" -}} + {{- $pullSecrets := list }} + {{- $context := .context }} + + {{- if $context.Values.global }} + {{- range $context.Values.global.imagePullSecrets -}} + {{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" $context)) -}} + {{- end -}} + {{- end -}} + + {{- range .images -}} + {{- range .pullSecrets -}} + {{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" $context)) -}} + {{- end -}} + {{- end -}} + + {{- if (not (empty $pullSecrets)) }} +imagePullSecrets: + {{- range $pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_ingress.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_ingress.tpl new file mode 100644 index 000000000..831da9caa --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_ingress.tpl @@ -0,0 +1,68 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Generate backend entry that is compatible with all Kubernetes API versions. + +Usage: +{{ include "common.ingress.backend" (dict "serviceName" "backendName" "servicePort" "backendPort" "context" $) }} + +Params: + - serviceName - String. Name of an existing service backend + - servicePort - String/Int. Port name (or number) of the service. It will be translated to different yaml depending if it is a string or an integer. + - context - Dict - Required. The context for the template evaluation. +*/}} +{{- define "common.ingress.backend" -}} +{{- $apiVersion := (include "common.capabilities.ingress.apiVersion" .context) -}} +{{- if or (eq $apiVersion "extensions/v1beta1") (eq $apiVersion "networking.k8s.io/v1beta1") -}} +serviceName: {{ .serviceName }} +servicePort: {{ .servicePort }} +{{- else -}} +service: + name: {{ .serviceName }} + port: + {{- if typeIs "string" .servicePort }} + name: {{ .servicePort }} + {{- else if or (typeIs "int" .servicePort) (typeIs "float64" .servicePort) }} + number: {{ .servicePort | int }} + {{- end }} +{{- end -}} +{{- end -}} + +{{/* +Print "true" if the API pathType field is supported +Usage: +{{ include "common.ingress.supportsPathType" . }} +*/}} +{{- define "common.ingress.supportsPathType" -}} +{{- if (semverCompare "<1.18-0" (include "common.capabilities.kubeVersion" .)) -}} +{{- print "false" -}} +{{- else -}} +{{- print "true" -}} +{{- end -}} +{{- end -}} + +{{/* +Returns true if the ingressClassname field is supported +Usage: +{{ include "common.ingress.supportsIngressClassname" . }} +*/}} +{{- define "common.ingress.supportsIngressClassname" -}} +{{- if semverCompare "<1.18-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "false" -}} +{{- else -}} +{{- print "true" -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if cert-manager required annotations for TLS signed +certificates are set in the Ingress annotations +Ref: https://cert-manager.io/docs/usage/ingress/#supported-annotations +Usage: +{{ include "common.ingress.certManagerRequest" ( dict "annotations" .Values.path.to.the.ingress.annotations ) }} +*/}} +{{- define "common.ingress.certManagerRequest" -}} +{{ if or (hasKey .annotations "cert-manager.io/cluster-issuer") (hasKey .annotations "cert-manager.io/issuer") (hasKey .annotations "kubernetes.io/tls-acme") }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_labels.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_labels.tpl new file mode 100644 index 000000000..252066c7e --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_labels.tpl @@ -0,0 +1,18 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Kubernetes standard labels +*/}} +{{- define "common.labels.standard" -}} +app.kubernetes.io/name: {{ include "common.names.name" . }} +helm.sh/chart: {{ include "common.names.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Labels to use on deploy.spec.selector.matchLabels and svc.spec.selector +*/}} +{{- define "common.labels.matchLabels" -}} +app.kubernetes.io/name: {{ include "common.names.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_names.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_names.tpl new file mode 100644 index 000000000..617a23489 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_names.tpl @@ -0,0 +1,66 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "common.names.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "common.names.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "common.names.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create a default fully qualified dependency name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +Usage: +{{ include "common.names.dependency.fullname" (dict "chartName" "dependency-chart-name" "chartValues" .Values.dependency-chart "context" $) }} +*/}} +{{- define "common.names.dependency.fullname" -}} +{{- if .chartValues.fullnameOverride -}} +{{- .chartValues.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .chartName .chartValues.nameOverride -}} +{{- if contains $name .context.Release.Name -}} +{{- .context.Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .context.Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Allow the release namespace to be overridden for multi-namespace deployments in combined charts. +*/}} +{{- define "common.names.namespace" -}} +{{- default .Release.Namespace .Values.namespaceOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a fully qualified app name adding the installation's namespace. +*/}} +{{- define "common.names.fullname.namespace" -}} +{{- printf "%s-%s" (include "common.names.fullname" .) (include "common.names.namespace" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_secrets.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_secrets.tpl new file mode 100644 index 000000000..a1708b2e8 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_secrets.tpl @@ -0,0 +1,165 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Generate secret name. + +Usage: +{{ include "common.secrets.name" (dict "existingSecret" .Values.path.to.the.existingSecret "defaultNameSuffix" "mySuffix" "context" $) }} + +Params: + - existingSecret - ExistingSecret/String - Optional. The path to the existing secrets in the values.yaml given by the user + to be used instead of the default one. Allows for it to be of type String (just the secret name) for backwards compatibility. + +info: https://github.com/bitnami/charts/tree/main/bitnami/common#existingsecret + - defaultNameSuffix - String - Optional. It is used only if we have several secrets in the same deployment. + - context - Dict - Required. The context for the template evaluation. +*/}} +{{- define "common.secrets.name" -}} +{{- $name := (include "common.names.fullname" .context) -}} + +{{- if .defaultNameSuffix -}} +{{- $name = printf "%s-%s" $name .defaultNameSuffix | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- with .existingSecret -}} +{{- if not (typeIs "string" .) -}} +{{- with .name -}} +{{- $name = . -}} +{{- end -}} +{{- else -}} +{{- $name = . -}} +{{- end -}} +{{- end -}} + +{{- printf "%s" $name -}} +{{- end -}} + +{{/* +Generate secret key. + +Usage: +{{ include "common.secrets.key" (dict "existingSecret" .Values.path.to.the.existingSecret "key" "keyName") }} + +Params: + - existingSecret - ExistingSecret/String - Optional. The path to the existing secrets in the values.yaml given by the user + to be used instead of the default one. Allows for it to be of type String (just the secret name) for backwards compatibility. + +info: https://github.com/bitnami/charts/tree/main/bitnami/common#existingsecret + - key - String - Required. Name of the key in the secret. +*/}} +{{- define "common.secrets.key" -}} +{{- $key := .key -}} + +{{- if .existingSecret -}} + {{- if not (typeIs "string" .existingSecret) -}} + {{- if .existingSecret.keyMapping -}} + {{- $key = index .existingSecret.keyMapping $.key -}} + {{- end -}} + {{- end }} +{{- end -}} + +{{- printf "%s" $key -}} +{{- end -}} + +{{/* +Generate secret password or retrieve one if already created. + +Usage: +{{ include "common.secrets.passwords.manage" (dict "secret" "secret-name" "key" "keyName" "providedValues" (list "path.to.password1" "path.to.password2") "length" 10 "strong" false "chartName" "chartName" "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - key - String - Required - Name of the key in the secret. + - providedValues - List<String> - Required - The path to the validating value in the values.yaml, e.g: "mysql.password". Will pick first parameter with a defined value. + - length - int - Optional - Length of the generated random password. + - strong - Boolean - Optional - Whether to add symbols to the generated random password. + - chartName - String - Optional - Name of the chart used when said chart is deployed as a subchart. + - context - Context - Required - Parent context. + +The order in which this function returns a secret password: + 1. Already existing 'Secret' resource + (If a 'Secret' resource is found under the name provided to the 'secret' parameter to this function and that 'Secret' resource contains a key with the name passed as the 'key' parameter to this function then the value of this existing secret password will be returned) + 2. Password provided via the values.yaml + (If one of the keys passed to the 'providedValues' parameter to this function is a valid path to a key in the values.yaml and has a value, the value of the first key with a value will be returned) + 3. Randomly generated secret password + (A new random secret password with the length specified in the 'length' parameter will be generated and returned) + +*/}} +{{- define "common.secrets.passwords.manage" -}} + +{{- $password := "" }} +{{- $subchart := "" }} +{{- $chartName := default "" .chartName }} +{{- $passwordLength := default 10 .length }} +{{- $providedPasswordKey := include "common.utils.getKeyFromList" (dict "keys" .providedValues "context" $.context) }} +{{- $providedPasswordValue := include "common.utils.getValueFromKey" (dict "key" $providedPasswordKey "context" $.context) }} +{{- $secretData := (lookup "v1" "Secret" (include "common.names.namespace" .context) .secret).data }} +{{- if $secretData }} + {{- if hasKey $secretData .key }} + {{- $password = index $secretData .key | quote }} + {{- else }} + {{- printf "\nPASSWORDS ERROR: The secret \"%s\" does not contain the key \"%s\"\n" .secret .key | fail -}} + {{- end -}} +{{- else if $providedPasswordValue }} + {{- $password = $providedPasswordValue | toString | b64enc | quote }} +{{- else }} + + {{- if .context.Values.enabled }} + {{- $subchart = $chartName }} + {{- end -}} + + {{- $requiredPassword := dict "valueKey" $providedPasswordKey "secret" .secret "field" .key "subchart" $subchart "context" $.context -}} + {{- $requiredPasswordError := include "common.validations.values.single.empty" $requiredPassword -}} + {{- $passwordValidationErrors := list $requiredPasswordError -}} + {{- include "common.errors.upgrade.passwords.empty" (dict "validationErrors" $passwordValidationErrors "context" $.context) -}} + + {{- if .strong }} + {{- $subStr := list (lower (randAlpha 1)) (randNumeric 1) (upper (randAlpha 1)) | join "_" }} + {{- $password = randAscii $passwordLength }} + {{- $password = regexReplaceAllLiteral "\\W" $password "@" | substr 5 $passwordLength }} + {{- $password = printf "%s%s" $subStr $password | toString | shuffle | b64enc | quote }} + {{- else }} + {{- $password = randAlphaNum $passwordLength | b64enc | quote }} + {{- end }} +{{- end -}} +{{- printf "%s" $password -}} +{{- end -}} + +{{/* +Reuses the value from an existing secret, otherwise sets its value to a default value. + +Usage: +{{ include "common.secrets.lookup" (dict "secret" "secret-name" "key" "keyName" "defaultValue" .Values.myValue "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - key - String - Required - Name of the key in the secret. + - defaultValue - String - Required - The path to the validating value in the values.yaml, e.g: "mysql.password". Will pick first parameter with a defined value. + - context - Context - Required - Parent context. + +*/}} +{{- define "common.secrets.lookup" -}} +{{- $value := "" -}} +{{- $defaultValue := required "\n'common.secrets.lookup': Argument 'defaultValue' missing or empty" .defaultValue -}} +{{- $secretData := (lookup "v1" "Secret" (include "common.names.namespace" .context) .secret).data -}} +{{- if and $secretData (hasKey $secretData .key) -}} + {{- $value = index $secretData .key -}} +{{- else -}} + {{- $value = $defaultValue | toString | b64enc -}} +{{- end -}} +{{- printf "%s" $value -}} +{{- end -}} + +{{/* +Returns whether a previous generated secret already exists + +Usage: +{{ include "common.secrets.exists" (dict "secret" "secret-name" "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - context - Context - Required - Parent context. +*/}} +{{- define "common.secrets.exists" -}} +{{- $secret := (lookup "v1" "Secret" (include "common.names.namespace" .context) .secret) }} +{{- if $secret }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_storage.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_storage.tpl new file mode 100644 index 000000000..60e2a844f --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_storage.tpl @@ -0,0 +1,23 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Return the proper Storage Class +{{ include "common.storage.class" ( dict "persistence" .Values.path.to.the.persistence "global" $) }} +*/}} +{{- define "common.storage.class" -}} + +{{- $storageClass := .persistence.storageClass -}} +{{- if .global -}} + {{- if .global.storageClass -}} + {{- $storageClass = .global.storageClass -}} + {{- end -}} +{{- end -}} + +{{- if $storageClass -}} + {{- if (eq "-" $storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" $storageClass -}} + {{- end -}} +{{- end -}} + +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_tplvalues.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_tplvalues.tpl new file mode 100644 index 000000000..2db166851 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_tplvalues.tpl @@ -0,0 +1,13 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Renders a value that contains template. +Usage: +{{ include "common.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} +{{- define "common.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_utils.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_utils.tpl new file mode 100644 index 000000000..b1ead50cf --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_utils.tpl @@ -0,0 +1,62 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Print instructions to get a secret value. +Usage: +{{ include "common.utils.secret.getvalue" (dict "secret" "secret-name" "field" "secret-value-field" "context" $) }} +*/}} +{{- define "common.utils.secret.getvalue" -}} +{{- $varname := include "common.utils.fieldToEnvVar" . -}} +export {{ $varname }}=$(kubectl get secret --namespace {{ include "common.names.namespace" .context | quote }} {{ .secret }} -o jsonpath="{.data.{{ .field }}}" | base64 -d) +{{- end -}} + +{{/* +Build env var name given a field +Usage: +{{ include "common.utils.fieldToEnvVar" dict "field" "my-password" }} +*/}} +{{- define "common.utils.fieldToEnvVar" -}} + {{- $fieldNameSplit := splitList "-" .field -}} + {{- $upperCaseFieldNameSplit := list -}} + + {{- range $fieldNameSplit -}} + {{- $upperCaseFieldNameSplit = append $upperCaseFieldNameSplit ( upper . ) -}} + {{- end -}} + + {{ join "_" $upperCaseFieldNameSplit }} +{{- end -}} + +{{/* +Gets a value from .Values given +Usage: +{{ include "common.utils.getValueFromKey" (dict "key" "path.to.key" "context" $) }} +*/}} +{{- define "common.utils.getValueFromKey" -}} +{{- $splitKey := splitList "." .key -}} +{{- $value := "" -}} +{{- $latestObj := $.context.Values -}} +{{- range $splitKey -}} + {{- if not $latestObj -}} + {{- printf "please review the entire path of '%s' exists in values" $.key | fail -}} + {{- end -}} + {{- $value = ( index $latestObj . ) -}} + {{- $latestObj = $value -}} +{{- end -}} +{{- printf "%v" (default "" $value) -}} +{{- end -}} + +{{/* +Returns first .Values key with a defined value or first of the list if all non-defined +Usage: +{{ include "common.utils.getKeyFromList" (dict "keys" (list "path.to.key1" "path.to.key2") "context" $) }} +*/}} +{{- define "common.utils.getKeyFromList" -}} +{{- $key := first .keys -}} +{{- $reverseKeys := reverse .keys }} +{{- range $reverseKeys }} + {{- $value := include "common.utils.getValueFromKey" (dict "key" . "context" $.context ) }} + {{- if $value -}} + {{- $key = . }} + {{- end -}} +{{- end -}} +{{- printf "%s" $key -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_warnings.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_warnings.tpl new file mode 100644 index 000000000..ae10fa41e --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/_warnings.tpl @@ -0,0 +1,14 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Warning about using rolling tag. +Usage: +{{ include "common.warnings.rollingTag" .Values.path.to.the.imageRoot }} +*/}} +{{- define "common.warnings.rollingTag" -}} + +{{- if and (contains "bitnami/" .repository) (not (.tag | toString | regexFind "-r\\d+$|sha256:")) }} +WARNING: Rolling tag detected ({{ .repository }}:{{ .tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. ++info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ +{{- end }} + +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_cassandra.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_cassandra.tpl new file mode 100644 index 000000000..ded1ae3bc --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_cassandra.tpl @@ -0,0 +1,72 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate Cassandra required passwords are not empty. + +Usage: +{{ include "common.validations.values.cassandra.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where Cassandra values are stored, e.g: "cassandra-passwords-secret" + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.cassandra.passwords" -}} + {{- $existingSecret := include "common.cassandra.values.existingSecret" . -}} + {{- $enabled := include "common.cassandra.values.enabled" . -}} + {{- $dbUserPrefix := include "common.cassandra.values.key.dbUser" . -}} + {{- $valueKeyPassword := printf "%s.password" $dbUserPrefix -}} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "cassandra-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.cassandra.values.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.cassandra.values.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.cassandra.dbUser.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.dbUser.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled cassandra. + +Usage: +{{ include "common.cassandra.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.cassandra.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.cassandra.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key dbUser + +Usage: +{{ include "common.cassandra.values.key.dbUser" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.cassandra.values.key.dbUser" -}} + {{- if .subchart -}} + cassandra.dbUser + {{- else -}} + dbUser + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_mariadb.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_mariadb.tpl new file mode 100644 index 000000000..b6906ff77 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_mariadb.tpl @@ -0,0 +1,103 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate MariaDB required passwords are not empty. + +Usage: +{{ include "common.validations.values.mariadb.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where MariaDB values are stored, e.g: "mysql-passwords-secret" + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.mariadb.passwords" -}} + {{- $existingSecret := include "common.mariadb.values.auth.existingSecret" . -}} + {{- $enabled := include "common.mariadb.values.enabled" . -}} + {{- $architecture := include "common.mariadb.values.architecture" . -}} + {{- $authPrefix := include "common.mariadb.values.key.auth" . -}} + {{- $valueKeyRootPassword := printf "%s.rootPassword" $authPrefix -}} + {{- $valueKeyUsername := printf "%s.username" $authPrefix -}} + {{- $valueKeyPassword := printf "%s.password" $authPrefix -}} + {{- $valueKeyReplicationPassword := printf "%s.replicationPassword" $authPrefix -}} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" $valueKeyRootPassword "secret" .secret "field" "mariadb-root-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRootPassword -}} + + {{- $valueUsername := include "common.utils.getValueFromKey" (dict "key" $valueKeyUsername "context" .context) }} + {{- if not (empty $valueUsername) -}} + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "mariadb-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq $architecture "replication") -}} + {{- $requiredReplicationPassword := dict "valueKey" $valueKeyReplicationPassword "secret" .secret "field" "mariadb-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredReplicationPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.mariadb.values.auth.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.auth.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.mariadb.auth.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.auth.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled mariadb. + +Usage: +{{ include "common.mariadb.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.mariadb.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.mariadb.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for architecture + +Usage: +{{ include "common.mariadb.values.architecture" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.architecture" -}} + {{- if .subchart -}} + {{- .context.Values.mariadb.architecture -}} + {{- else -}} + {{- .context.Values.architecture -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key auth + +Usage: +{{ include "common.mariadb.values.key.auth" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.key.auth" -}} + {{- if .subchart -}} + mariadb.auth + {{- else -}} + auth + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_mongodb.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_mongodb.tpl new file mode 100644 index 000000000..f820ec107 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_mongodb.tpl @@ -0,0 +1,108 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate MongoDB® required passwords are not empty. + +Usage: +{{ include "common.validations.values.mongodb.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where MongoDB® values are stored, e.g: "mongodb-passwords-secret" + - subchart - Boolean - Optional. Whether MongoDB® is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.mongodb.passwords" -}} + {{- $existingSecret := include "common.mongodb.values.auth.existingSecret" . -}} + {{- $enabled := include "common.mongodb.values.enabled" . -}} + {{- $authPrefix := include "common.mongodb.values.key.auth" . -}} + {{- $architecture := include "common.mongodb.values.architecture" . -}} + {{- $valueKeyRootPassword := printf "%s.rootPassword" $authPrefix -}} + {{- $valueKeyUsername := printf "%s.username" $authPrefix -}} + {{- $valueKeyDatabase := printf "%s.database" $authPrefix -}} + {{- $valueKeyPassword := printf "%s.password" $authPrefix -}} + {{- $valueKeyReplicaSetKey := printf "%s.replicaSetKey" $authPrefix -}} + {{- $valueKeyAuthEnabled := printf "%s.enabled" $authPrefix -}} + + {{- $authEnabled := include "common.utils.getValueFromKey" (dict "key" $valueKeyAuthEnabled "context" .context) -}} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") (eq $authEnabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" $valueKeyRootPassword "secret" .secret "field" "mongodb-root-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRootPassword -}} + + {{- $valueUsername := include "common.utils.getValueFromKey" (dict "key" $valueKeyUsername "context" .context) }} + {{- $valueDatabase := include "common.utils.getValueFromKey" (dict "key" $valueKeyDatabase "context" .context) }} + {{- if and $valueUsername $valueDatabase -}} + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "mongodb-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq $architecture "replicaset") -}} + {{- $requiredReplicaSetKey := dict "valueKey" $valueKeyReplicaSetKey "secret" .secret "field" "mongodb-replica-set-key" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredReplicaSetKey -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.mongodb.values.auth.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MongoDb is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.auth.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.mongodb.auth.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.auth.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled mongodb. + +Usage: +{{ include "common.mongodb.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.mongodb.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.mongodb.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key auth + +Usage: +{{ include "common.mongodb.values.key.auth" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MongoDB® is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.key.auth" -}} + {{- if .subchart -}} + mongodb.auth + {{- else -}} + auth + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for architecture + +Usage: +{{ include "common.mongodb.values.architecture" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MongoDB® is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.architecture" -}} + {{- if .subchart -}} + {{- .context.Values.mongodb.architecture -}} + {{- else -}} + {{- .context.Values.architecture -}} + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_mysql.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_mysql.tpl new file mode 100644 index 000000000..74472a061 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_mysql.tpl @@ -0,0 +1,103 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate MySQL required passwords are not empty. + +Usage: +{{ include "common.validations.values.mysql.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where MySQL values are stored, e.g: "mysql-passwords-secret" + - subchart - Boolean - Optional. Whether MySQL is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.mysql.passwords" -}} + {{- $existingSecret := include "common.mysql.values.auth.existingSecret" . -}} + {{- $enabled := include "common.mysql.values.enabled" . -}} + {{- $architecture := include "common.mysql.values.architecture" . -}} + {{- $authPrefix := include "common.mysql.values.key.auth" . -}} + {{- $valueKeyRootPassword := printf "%s.rootPassword" $authPrefix -}} + {{- $valueKeyUsername := printf "%s.username" $authPrefix -}} + {{- $valueKeyPassword := printf "%s.password" $authPrefix -}} + {{- $valueKeyReplicationPassword := printf "%s.replicationPassword" $authPrefix -}} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" $valueKeyRootPassword "secret" .secret "field" "mysql-root-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRootPassword -}} + + {{- $valueUsername := include "common.utils.getValueFromKey" (dict "key" $valueKeyUsername "context" .context) }} + {{- if not (empty $valueUsername) -}} + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "mysql-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq $architecture "replication") -}} + {{- $requiredReplicationPassword := dict "valueKey" $valueKeyReplicationPassword "secret" .secret "field" "mysql-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredReplicationPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.mysql.values.auth.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MySQL is used as subchart or not. Default: false +*/}} +{{- define "common.mysql.values.auth.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.mysql.auth.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.auth.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled mysql. + +Usage: +{{ include "common.mysql.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.mysql.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.mysql.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for architecture + +Usage: +{{ include "common.mysql.values.architecture" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MySQL is used as subchart or not. Default: false +*/}} +{{- define "common.mysql.values.architecture" -}} + {{- if .subchart -}} + {{- .context.Values.mysql.architecture -}} + {{- else -}} + {{- .context.Values.architecture -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key auth + +Usage: +{{ include "common.mysql.values.key.auth" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MySQL is used as subchart or not. Default: false +*/}} +{{- define "common.mysql.values.key.auth" -}} + {{- if .subchart -}} + mysql.auth + {{- else -}} + auth + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_postgresql.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_postgresql.tpl new file mode 100644 index 000000000..164ec0d01 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_postgresql.tpl @@ -0,0 +1,129 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate PostgreSQL required passwords are not empty. + +Usage: +{{ include "common.validations.values.postgresql.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where postgresql values are stored, e.g: "postgresql-passwords-secret" + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.postgresql.passwords" -}} + {{- $existingSecret := include "common.postgresql.values.existingSecret" . -}} + {{- $enabled := include "common.postgresql.values.enabled" . -}} + {{- $valueKeyPostgresqlPassword := include "common.postgresql.values.key.postgressPassword" . -}} + {{- $valueKeyPostgresqlReplicationEnabled := include "common.postgresql.values.key.replicationPassword" . -}} + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + {{- $requiredPostgresqlPassword := dict "valueKey" $valueKeyPostgresqlPassword "secret" .secret "field" "postgresql-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPostgresqlPassword -}} + + {{- $enabledReplication := include "common.postgresql.values.enabled.replication" . -}} + {{- if (eq $enabledReplication "true") -}} + {{- $requiredPostgresqlReplicationPassword := dict "valueKey" $valueKeyPostgresqlReplicationEnabled "secret" .secret "field" "postgresql-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPostgresqlReplicationPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to decide whether evaluate global values. + +Usage: +{{ include "common.postgresql.values.use.global" (dict "key" "key-of-global" "context" $) }} +Params: + - key - String - Required. Field to be evaluated within global, e.g: "existingSecret" +*/}} +{{- define "common.postgresql.values.use.global" -}} + {{- if .context.Values.global -}} + {{- if .context.Values.global.postgresql -}} + {{- index .context.Values.global.postgresql .key | quote -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.postgresql.values.existingSecret" (dict "context" $) }} +*/}} +{{- define "common.postgresql.values.existingSecret" -}} + {{- $globalValue := include "common.postgresql.values.use.global" (dict "key" "existingSecret" "context" .context) -}} + + {{- if .subchart -}} + {{- default (.context.Values.postgresql.existingSecret | quote) $globalValue -}} + {{- else -}} + {{- default (.context.Values.existingSecret | quote) $globalValue -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled postgresql. + +Usage: +{{ include "common.postgresql.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.postgresql.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.postgresql.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key postgressPassword. + +Usage: +{{ include "common.postgresql.values.key.postgressPassword" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.key.postgressPassword" -}} + {{- $globalValue := include "common.postgresql.values.use.global" (dict "key" "postgresqlUsername" "context" .context) -}} + + {{- if not $globalValue -}} + {{- if .subchart -}} + postgresql.postgresqlPassword + {{- else -}} + postgresqlPassword + {{- end -}} + {{- else -}} + global.postgresql.postgresqlPassword + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled.replication. + +Usage: +{{ include "common.postgresql.values.enabled.replication" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.enabled.replication" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.postgresql.replication.enabled -}} + {{- else -}} + {{- printf "%v" .context.Values.replication.enabled -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key replication.password. + +Usage: +{{ include "common.postgresql.values.key.replicationPassword" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.key.replicationPassword" -}} + {{- if .subchart -}} + postgresql.replication.password + {{- else -}} + replication.password + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_redis.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_redis.tpl new file mode 100644 index 000000000..dcccfc1ae --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_redis.tpl @@ -0,0 +1,76 @@ + +{{/* vim: set filetype=mustache: */}} +{{/* +Validate Redis® required passwords are not empty. + +Usage: +{{ include "common.validations.values.redis.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where redis values are stored, e.g: "redis-passwords-secret" + - subchart - Boolean - Optional. Whether redis is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.redis.passwords" -}} + {{- $enabled := include "common.redis.values.enabled" . -}} + {{- $valueKeyPrefix := include "common.redis.values.keys.prefix" . -}} + {{- $standarizedVersion := include "common.redis.values.standarized.version" . }} + + {{- $existingSecret := ternary (printf "%s%s" $valueKeyPrefix "auth.existingSecret") (printf "%s%s" $valueKeyPrefix "existingSecret") (eq $standarizedVersion "true") }} + {{- $existingSecretValue := include "common.utils.getValueFromKey" (dict "key" $existingSecret "context" .context) }} + + {{- $valueKeyRedisPassword := ternary (printf "%s%s" $valueKeyPrefix "auth.password") (printf "%s%s" $valueKeyPrefix "password") (eq $standarizedVersion "true") }} + {{- $valueKeyRedisUseAuth := ternary (printf "%s%s" $valueKeyPrefix "auth.enabled") (printf "%s%s" $valueKeyPrefix "usePassword") (eq $standarizedVersion "true") }} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $useAuth := include "common.utils.getValueFromKey" (dict "key" $valueKeyRedisUseAuth "context" .context) -}} + {{- if eq $useAuth "true" -}} + {{- $requiredRedisPassword := dict "valueKey" $valueKeyRedisPassword "secret" .secret "field" "redis-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRedisPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled redis. + +Usage: +{{ include "common.redis.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.redis.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.redis.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right prefix path for the values + +Usage: +{{ include "common.redis.values.key.prefix" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether redis is used as subchart or not. Default: false +*/}} +{{- define "common.redis.values.keys.prefix" -}} + {{- if .subchart -}}redis.{{- else -}}{{- end -}} +{{- end -}} + +{{/* +Checks whether the redis chart's includes the standarizations (version >= 14) + +Usage: +{{ include "common.redis.values.standarized.version" (dict "context" $) }} +*/}} +{{- define "common.redis.values.standarized.version" -}} + + {{- $standarizedAuth := printf "%s%s" (include "common.redis.values.keys.prefix" .) "auth" -}} + {{- $standarizedAuthValues := include "common.utils.getValueFromKey" (dict "key" $standarizedAuth "context" .context) }} + + {{- if $standarizedAuthValues -}} + {{- true -}} + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_validations.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_validations.tpl new file mode 100644 index 000000000..9a814cf40 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/templates/validations/_validations.tpl @@ -0,0 +1,46 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate values must not be empty. + +Usage: +{{- $validateValueConf00 := (dict "valueKey" "path.to.value" "secret" "secretName" "field" "password-00") -}} +{{- $validateValueConf01 := (dict "valueKey" "path.to.value" "secret" "secretName" "field" "password-01") -}} +{{ include "common.validations.values.empty" (dict "required" (list $validateValueConf00 $validateValueConf01) "context" $) }} + +Validate value params: + - valueKey - String - Required. The path to the validating value in the values.yaml, e.g: "mysql.password" + - secret - String - Optional. Name of the secret where the validating value is generated/stored, e.g: "mysql-passwords-secret" + - field - String - Optional. Name of the field in the secret data, e.g: "mysql-password" +*/}} +{{- define "common.validations.values.multiple.empty" -}} + {{- range .required -}} + {{- include "common.validations.values.single.empty" (dict "valueKey" .valueKey "secret" .secret "field" .field "context" $.context) -}} + {{- end -}} +{{- end -}} + +{{/* +Validate a value must not be empty. + +Usage: +{{ include "common.validations.value.empty" (dict "valueKey" "mariadb.password" "secret" "secretName" "field" "my-password" "subchart" "subchart" "context" $) }} + +Validate value params: + - valueKey - String - Required. The path to the validating value in the values.yaml, e.g: "mysql.password" + - secret - String - Optional. Name of the secret where the validating value is generated/stored, e.g: "mysql-passwords-secret" + - field - String - Optional. Name of the field in the secret data, e.g: "mysql-password" + - subchart - String - Optional - Name of the subchart that the validated password is part of. +*/}} +{{- define "common.validations.values.single.empty" -}} + {{- $value := include "common.utils.getValueFromKey" (dict "key" .valueKey "context" .context) }} + {{- $subchart := ternary "" (printf "%s." .subchart) (empty .subchart) }} + + {{- if not $value -}} + {{- $varname := "my-value" -}} + {{- $getCurrentValue := "" -}} + {{- if and .secret .field -}} + {{- $varname = include "common.utils.fieldToEnvVar" . -}} + {{- $getCurrentValue = printf " To get the current value:\n\n %s\n" (include "common.utils.secret.getvalue" .) -}} + {{- end -}} + {{- printf "\n '%s' must not be empty, please add '--set %s%s=$%s' to the command.%s" .valueKey $subchart .valueKey $varname $getCurrentValue -}} + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/values.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/values.yaml new file mode 100644 index 000000000..f2df68e5e --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/charts/common/values.yaml @@ -0,0 +1,5 @@ +## bitnami/common +## It is required by CI/CD tools and processes. +## @skip exampleValue +## +exampleValue: common-chart diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/NOTES.txt b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/NOTES.txt old mode 100755 new mode 100644 index 3cc2edbed..c287e1e56 --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/NOTES.txt +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/NOTES.txt @@ -1,5 +1,8 @@ -{{- if contains .Values.service.type "LoadBalancer" }} -{{- if not .Values.auth.clientPassword }} +CHART NAME: {{ .Chart.Name }} +CHART VERSION: {{ .Chart.Version }} +APP VERSION: {{ .Chart.AppVersion }} + +{{- if and (not .Values.auth.client.enabled) (eq .Values.service.type "LoadBalancer") }} ------------------------------------------------------------------------------- WARNING @@ -13,45 +16,61 @@ ------------------------------------------------------------------------------- {{- end }} -{{- end }} ** Please be patient while the chart is being deployed ** -ZooKeeper can be accessed via port 2181 on the following DNS name from within your cluster: +{{- if .Values.diagnosticMode.enabled }} +The chart has been deployed in diagnostic mode. All probes have been disabled and the command has been overwritten with: - {{ template "zookeeper.fullname" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 4 }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 4 }} + +Get the list of pods by executing: + + kubectl get pods --namespace {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} + +Access the pod you want to debug by executing + + kubectl exec --namespace {{ .Release.Namespace }} -ti <NAME OF THE POD> -- bash + +In order to replicate the container startup scripts execute this command: + + /opt/bitnami/scripts/zookeeper/entrypoint.sh /opt/bitnami/scripts/zookeeper/run.sh + +{{- else }} + +ZooKeeper can be accessed via port {{ .Values.service.ports.client }} on the following DNS name from within your cluster: + + {{ template "common.names.fullname" . }}.{{ template "zookeeper.namespace" . }}.svc.{{ .Values.clusterDomain }} To connect to your ZooKeeper server run the following commands: - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ template "zookeeper.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=zookeeper" -o jsonpath="{.items[0].metadata.name}") + export POD_NAME=$(kubectl get pods --namespace {{ template "zookeeper.namespace" . }} -l "app.kubernetes.io/name={{ template "common.names.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=zookeeper" -o jsonpath="{.items[0].metadata.name}") kubectl exec -it $POD_NAME -- zkCli.sh To connect to your ZooKeeper server from outside the cluster execute the following commands: -{{- if contains "NodePort" .Values.service.type }} +{{- if eq .Values.service.type "NodePort" }} - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "zookeeper.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ template "zookeeper.namespace" . }} -o jsonpath="{.items[0].status.addresses[0].address}") + export NODE_PORT=$(kubectl get --namespace {{ template "zookeeper.namespace" . }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "common.names.fullname" . }}) zkCli.sh $NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} +{{- else if eq .Values.service.type "LoadBalancer" }} NOTE: It may take a few minutes for the LoadBalancer IP to be available. - Watch the status with: 'kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "zookeeper.fullname" . }}' + Watch the status with: 'kubectl get svc --namespace {{ template "zookeeper.namespace" . }} -w {{ template "common.names.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "zookeeper.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") - zkCli.sh $SERVICE_IP:2181 + export SERVICE_IP=$(kubectl get svc --namespace {{ template "zookeeper.namespace" . }} {{ template "common.names.fullname" . }} --template "{{ "{{ range (index .status.loadBalancer.ingress 0) }}{{ . }}{{ end }}" }}") + zkCli.sh $SERVICE_IP:{{ .Values.service.ports.client }} -{{- else if contains "ClusterIP" .Values.service.type }} +{{- else if eq .Values.service.type "ClusterIP" }} - kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ template "zookeeper.fullname" . }} 2181:2181 & - zkCli.sh 127.0.0.1:2181 + kubectl port-forward --namespace {{ template "zookeeper.namespace" . }} svc/{{ template "common.names.fullname" . }} {{ .Values.service.ports.client }}:{{ .Values.containerPorts.client }} & + zkCli.sh 127.0.0.1:{{ .Values.service.ports.client }} {{- end }} - -{{- if and (contains "bitnami/" .Values.image.repository) (not (.Values.image.tag | toString | regexFind "-r\\d+$|sha256:")) }} - -WARNING: Rolling tag detected ({{ .Values.image.repository }}:{{ .Values.image.tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. -+info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ - {{- end }} + +{{- include "zookeeper.validateValues" . }} +{{- include "zookeeper.checkRollingTags" . }} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/_helpers.tpl b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/_helpers.tpl old mode 100755 new mode 100644 index f82502d69..d855bada0 --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/_helpers.tpl +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/_helpers.tpl @@ -1,34 +1,43 @@ {{/* vim: set filetype=mustache: */}} + {{/* -Expand the name of the chart. +Return the proper ZooKeeper image name */}} -{{- define "zookeeper.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- define "zookeeper.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }} {{- end -}} {{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. +Return the proper image name (for the init container volume-permissions image) */}} -{{- define "zookeeper.fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- if contains $name .Release.Name -}} -{{- .Release.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} +{{- define "zookeeper.volumePermissions.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.volumePermissions.image "global" .Values.global) }} {{- end -}} {{/* -Create chart name and version as used by the chart label. +Return the proper Docker Image Registry Secret Names */}} -{{- define "zookeeper.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- define "zookeeper.imagePullSecrets" -}} +{{- include "common.images.pullSecrets" (dict "images" (list .Values.image .Values.volumePermissions.image) "global" .Values.global) -}} +{{- end -}} + +{{/* +Check if there are rolling tags in the images +*/}} +{{- define "zookeeper.checkRollingTags" -}} +{{- include "common.warnings.rollingTag" .Values.image }} +{{- include "common.warnings.rollingTag" .Values.volumePermissions.image }} +{{- end -}} + +{{/* +Return ZooKeeper Namespace to use +*/}} +{{- define "zookeeper.namespace" -}} +{{- if .Values.namespaceOverride -}} + {{- .Values.namespaceOverride -}} +{{- else -}} + {{- .Release.Namespace -}} +{{- end -}} {{- end -}} {{/* @@ -36,177 +45,317 @@ Create chart name and version as used by the chart label. */}} {{- define "zookeeper.serviceAccountName" -}} {{- if .Values.serviceAccount.create -}} - {{ default (include "zookeeper.fullname" .) .Values.serviceAccount.name }} + {{ default (include "common.names.fullname" .) .Values.serviceAccount.name }} {{- else -}} {{ default "default" .Values.serviceAccount.name }} {{- end -}} {{- end -}} {{/* -Return the proper Zookeeper image name +Return the ZooKeeper client-server authentication credentials secret */}} -{{- define "zookeeper.image" -}} -{{- $registryName := .Values.image.registry -}} -{{- $repositoryName := .Values.image.repository -}} -{{- $tag := .Values.image.tag | toString -}} +{{- define "zookeeper.client.secretName" -}} +{{- if .Values.auth.client.existingSecret -}} + {{- printf "%s" (tpl .Values.auth.client.existingSecret $) -}} +{{- else -}} + {{- printf "%s-client-auth" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + {{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. -Also, we can't use a single if because lazy evaluation is not an option +Return the ZooKeeper server-server authentication credentials secret */}} -{{- if .Values.global }} - {{- if .Values.global.imageRegistry }} - {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} +{{- define "zookeeper.quorum.secretName" -}} +{{- if .Values.auth.quorum.existingSecret -}} + {{- printf "%s" (tpl .Values.auth.quorum.existingSecret $) -}} +{{- else -}} + {{- printf "%s-quorum-auth" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a ZooKeeper client-server authentication credentials secret object should be created +*/}} +{{- define "zookeeper.client.createSecret" -}} +{{- if and .Values.auth.client.enabled (empty .Values.auth.client.existingSecret) -}} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a ZooKeeper server-server authentication credentials secret object should be created +*/}} +{{- define "zookeeper.quorum.createSecret" -}} +{{- if and .Values.auth.quorum.enabled (empty .Values.auth.quorum.existingSecret) -}} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Returns the available value for certain key in an existing secret (if it exists), +otherwise it generates a random value. +*/}} +{{- define "getValueFromSecret" }} + {{- $len := (default 16 .Length) | int -}} + {{- $obj := (lookup "v1" "Secret" .Namespace .Name).data -}} + {{- if $obj }} + {{- index $obj .Key | b64dec -}} {{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{- randAlphaNum $len -}} {{- end -}} -{{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} -{{- end -}} -{{- end -}} - -{{/* -Return the proper Docker Image Registry Secret Names -*/}} -{{- define "zookeeper.imagePullSecrets" -}} -{{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic. -Also, we can not use a single if because lazy evaluation is not an option -*/}} -{{- if .Values.global }} -{{- if .Values.global.imagePullSecrets }} -imagePullSecrets: -{{- range .Values.global.imagePullSecrets }} - - name: {{ . }} {{- end }} -{{- else if or .Values.image.pullSecrets .Values.volumePermissions.image.pullSecrets }} -imagePullSecrets: -{{- range .Values.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- range .Values.volumePermissions.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- end -}} -{{- else if or .Values.image.pullSecrets .Values.volumePermissions.image.pullSecrets }} -imagePullSecrets: -{{- range .Values.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- range .Values.volumePermissions.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- end -}} -{{- end -}} {{/* -Common labels +Return the ZooKeeper configuration ConfigMap name */}} -{{- define "zookeeper.labels" -}} -app.kubernetes.io/name: {{ include "zookeeper.name" . }} -helm.sh/chart: {{ include "zookeeper.chart" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end -}} - -{{/* -Renders a value that contains template. -Usage: -{{ include "zookeeper.tplValue" ( dict "value" .Values.path.to.the.Value "context" $) }} -*/}} -{{- define "zookeeper.tplValue" -}} - {{- if typeIs "string" .value }} - {{- tpl .value .context }} - {{- else }} - {{- tpl (.value | toYaml) .context }} - {{- end }} -{{- end -}} - -{{/* -Labels to use on deploy.spec.selector.matchLabels and svc.spec.selector -*/}} -{{- define "zookeeper.matchLabels" -}} -app.kubernetes.io/name: {{ include "zookeeper.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end -}} - -{{/* -Return ZooKeeper Client Password -*/}} -{{- define "zookeeper.clientPassword" -}} -{{- if .Values.auth.clientPassword -}} - {{- .Values.auth.clientPassword -}} +{{- define "zookeeper.configmapName" -}} +{{- if .Values.existingConfigmap -}} + {{- printf "%s" (tpl .Values.existingConfigmap $) -}} {{- else -}} - {{- randAlphaNum 10 -}} + {{- printf "%s" (include "common.names.fullname" .) -}} {{- end -}} {{- end -}} {{/* -Return ZooKeeper Servers Passwords +Return true if a ConfigMap object should be created for ZooKeeper configuration */}} -{{- define "zookeeper.serverPasswords" -}} -{{- if .Values.auth.serverPasswords -}} - {{- .Values.auth.serverPasswords -}} -{{- else -}} - {{- randAlphaNum 10 -}} +{{- define "zookeeper.createConfigmap" -}} +{{- if and .Values.configuration (not .Values.existingConfigmap) }} + {{- true -}} {{- end -}} {{- end -}} {{/* -Return the proper image name (for the init container volume-permissions image) +Return true if a TLS secret should be created for ZooKeeper quorum */}} -{{- define "zookeeper.volumePermissions.image" -}} -{{- $registryName := .Values.volumePermissions.image.registry -}} -{{- $repositoryName := .Values.volumePermissions.image.repository -}} -{{- $tag := .Values.volumePermissions.image.tag | toString -}} -{{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. -Also, we can't use a single if because lazy evaluation is not an option -*/}} -{{- if .Values.global }} - {{- if .Values.global.imageRegistry }} - {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} - {{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} - {{- end -}} -{{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- define "zookeeper.quorum.createTlsSecret" -}} +{{- if and .Values.tls.quorum.enabled .Values.tls.quorum.autoGenerated (not .Values.tls.quorum.existingSecret) }} + {{- true -}} {{- end -}} {{- end -}} {{/* -Return the proper Storage Class +Return the secret containing ZooKeeper quorum TLS certificates */}} -{{- define "zookeeper.storageClass" -}} -{{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic. -*/}} -{{- if .Values.global -}} - {{- if .Values.global.storageClass -}} - {{- if (eq "-" .Values.global.storageClass) -}} - {{- printf "storageClassName: \"\"" -}} - {{- else }} - {{- printf "storageClassName: %s" .Values.global.storageClass -}} - {{- end -}} - {{- else -}} - {{- if .Values.persistence.storageClass -}} - {{- if (eq "-" .Values.persistence.storageClass) -}} - {{- printf "storageClassName: \"\"" -}} - {{- else }} - {{- printf "storageClassName: %s" .Values.persistence.storageClass -}} - {{- end -}} - {{- end -}} - {{- end -}} +{{- define "zookeeper.quorum.tlsSecretName" -}} +{{- $secretName := .Values.tls.quorum.existingSecret -}} +{{- if $secretName -}} + {{- printf "%s" (tpl $secretName $) -}} {{- else -}} - {{- if .Values.persistence.storageClass -}} - {{- if (eq "-" .Values.persistence.storageClass) -}} - {{- printf "storageClassName: \"\"" -}} - {{- else }} - {{- printf "storageClassName: %s" .Values.persistence.storageClass -}} - {{- end -}} - {{- end -}} + {{- printf "%s-quorum-crt" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a secret containing the Keystore and Truststore password should be created for ZooKeeper quorum +*/}} +{{- define "zookeeper.quorum.createTlsPasswordsSecret" -}} +{{- if and .Values.tls.quorum.enabled (not .Values.tls.quorum.passwordsSecretName) }} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret containing the Keystore and Truststore password +*/}} +{{- define "zookeeper.quorum.tlsPasswordsSecret" -}} +{{- $secretName := .Values.tls.quorum.passwordsSecretName -}} +{{- if $secretName -}} + {{- printf "%s" (tpl $secretName $) -}} +{{- else -}} + {{- printf "%s-quorum-tls-pass" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a TLS secret should be created for ZooKeeper client +*/}} +{{- define "zookeeper.client.createTlsSecret" -}} +{{- if and .Values.tls.client.enabled .Values.tls.client.autoGenerated (not .Values.tls.client.existingSecret) }} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Return the secret containing ZooKeeper client TLS certificates +*/}} +{{- define "zookeeper.client.tlsSecretName" -}} +{{- $secretName := .Values.tls.client.existingSecret -}} +{{- if $secretName -}} + {{- printf "%s" (tpl $secretName $) -}} +{{- else -}} + {{- printf "%s-client-crt" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Get the quorum keystore key to be retrieved from tls.quorum.existingSecret. +*/}} +{{- define "zookeeper.quorum.tlsKeystoreKey" -}} +{{- if and .Values.tls.quorum.existingSecret .Values.tls.quorum.existingSecretKeystoreKey -}} + {{- printf "%s" .Values.tls.quorum.existingSecretKeystoreKey -}} +{{- else -}} + {{- printf "zookeeper.keystore.jks" -}} +{{- end -}} +{{- end -}} + +{{/* +Get the quorum truststore key to be retrieved from tls.quorum.existingSecret. +*/}} +{{- define "zookeeper.quorum.tlsTruststoreKey" -}} +{{- if and .Values.tls.quorum.existingSecret .Values.tls.quorum.existingSecretTruststoreKey -}} + {{- printf "%s" .Values.tls.quorum.existingSecretTruststoreKey -}} +{{- else -}} + {{- printf "zookeeper.truststore.jks" -}} +{{- end -}} +{{- end -}} + +{{/* +Get the client keystore key to be retrieved from tls.client.existingSecret. +*/}} +{{- define "zookeeper.client.tlsKeystoreKey" -}} +{{- if and .Values.tls.client.existingSecret .Values.tls.client.existingSecretKeystoreKey -}} + {{- printf "%s" .Values.tls.client.existingSecretKeystoreKey -}} +{{- else -}} + {{- printf "zookeeper.keystore.jks" -}} +{{- end -}} +{{- end -}} + +{{/* +Get the client truststore key to be retrieved from tls.client.existingSecret. +*/}} +{{- define "zookeeper.client.tlsTruststoreKey" -}} +{{- if and .Values.tls.client.existingSecret .Values.tls.client.existingSecretTruststoreKey -}} + {{- printf "%s" .Values.tls.client.existingSecretTruststoreKey -}} +{{- else -}} + {{- printf "zookeeper.truststore.jks" -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a secret containing the Keystore and Truststore password should be created for ZooKeeper client +*/}} +{{- define "zookeeper.client.createTlsPasswordsSecret" -}} +{{- if and .Values.tls.client.enabled (not .Values.tls.client.passwordsSecretName) }} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret containing the Keystore and Truststore password +*/}} +{{- define "zookeeper.client.tlsPasswordsSecret" -}} +{{- $secretName := .Values.tls.client.passwordsSecretName -}} +{{- if $secretName -}} + {{- printf "%s" (tpl $secretName $) -}} +{{- else -}} + {{- printf "%s-client-tls-pass" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Get the quorum keystore password key to be retrieved from tls.quorum.passwordSecretName. +*/}} +{{- define "zookeeper.quorum.tlsPasswordKeystoreKey" -}} +{{- if and .Values.tls.quorum.passwordsSecretName .Values.tls.quorum.passwordsSecretKeystoreKey -}} + {{- printf "%s" .Values.tls.quorum.passwordsSecretKeystoreKey -}} +{{- else -}} + {{- printf "keystore-password" -}} +{{- end -}} +{{- end -}} + +{{/* +Get the quorum truststore password key to be retrieved from tls.quorum.passwordSecretName. +*/}} +{{- define "zookeeper.quorum.tlsPasswordTruststoreKey" -}} +{{- if and .Values.tls.quorum.passwordsSecretName .Values.tls.quorum.passwordsSecretTruststoreKey -}} + {{- printf "%s" .Values.tls.quorum.passwordsSecretTruststoreKey -}} +{{- else -}} + {{- printf "truststore-password" -}} +{{- end -}} +{{- end -}} + +{{/* +Get the client keystore password key to be retrieved from tls.client.passwordSecretName. +*/}} +{{- define "zookeeper.client.tlsPasswordKeystoreKey" -}} +{{- if and .Values.tls.client.passwordsSecretName .Values.tls.client.passwordsSecretKeystoreKey -}} + {{- printf "%s" .Values.tls.client.passwordsSecretKeystoreKey -}} +{{- else -}} + {{- printf "keystore-password" -}} +{{- end -}} +{{- end -}} + +{{/* +Get the client truststore password key to be retrieved from tls.client.passwordSecretName. +*/}} +{{- define "zookeeper.client.tlsPasswordTruststoreKey" -}} +{{- if and .Values.tls.client.passwordsSecretName .Values.tls.client.passwordsSecretTruststoreKey -}} + {{- printf "%s" .Values.tls.client.passwordsSecretTruststoreKey -}} +{{- else -}} + {{- printf "truststore-password" -}} +{{- end -}} +{{- end -}} + +{{/* +Compile all warnings into a single message. +*/}} +{{- define "zookeeper.validateValues" -}} +{{- $messages := list -}} +{{- $messages := append $messages (include "zookeeper.validateValues.client.auth" .) -}} +{{- $messages := append $messages (include "zookeeper.validateValues.quorum.auth" .) -}} +{{- $messages := append $messages (include "zookeeper.validateValues.client.tls" .) -}} +{{- $messages := append $messages (include "zookeeper.validateValues.quorum.tls" .) -}} +{{- $messages := without $messages "" -}} +{{- $message := join "\n" $messages -}} + +{{- if $message -}} +{{- printf "\nVALUES VALIDATION:\n%s" $message | fail -}} +{{- end -}} +{{- end -}} + +{{/* +Validate values of ZooKeeper - Authentication enabled +*/}} +{{- define "zookeeper.validateValues.client.auth" -}} +{{- if and .Values.auth.client.enabled (not .Values.auth.client.existingSecret) (or (not .Values.auth.client.clientUser) (not .Values.auth.client.serverUsers)) }} +zookeeper: auth.client.enabled + In order to enable client-server authentication, you need to provide the list + of users to be created and the user to use for clients authentication. +{{- end -}} +{{- end -}} + +{{/* +Validate values of ZooKeeper - Authentication enabled +*/}} +{{- define "zookeeper.validateValues.quorum.auth" -}} +{{- if and .Values.auth.quorum.enabled (not .Values.auth.quorum.existingSecret) (or (not .Values.auth.quorum.learnerUser) (not .Values.auth.quorum.serverUsers)) }} +zookeeper: auth.quorum.enabled + In order to enable server-server authentication, you need to provide the list + of users to be created and the user to use for quorum authentication. +{{- end -}} +{{- end -}} + +{{/* +Validate values of ZooKeeper - Client TLS enabled +*/}} +{{- define "zookeeper.validateValues.client.tls" -}} +{{- if and .Values.tls.client.enabled (not .Values.tls.client.autoGenerated) (not .Values.tls.client.existingSecret) }} +zookeeper: tls.client.enabled + In order to enable Client TLS encryption, you also need to provide + an existing secret containing the Keystore and Truststore or + enable auto-generated certificates. +{{- end -}} +{{- end -}} + +{{/* +Validate values of ZooKeeper - Quorum TLS enabled +*/}} +{{- define "zookeeper.validateValues.quorum.tls" -}} +{{- if and .Values.tls.quorum.enabled (not .Values.tls.quorum.autoGenerated) (not .Values.tls.quorum.existingSecret) }} +zookeeper: tls.quorum.enabled + In order to enable Quorum TLS, you also need to provide + an existing secret containing the Keystore and Truststore or + enable auto-generated certificates. {{- end -}} {{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/configmap.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/configmap.yaml old mode 100755 new mode 100644 index 1a4061565..12b4f489f --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/configmap.yaml +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/configmap.yaml @@ -1,17 +1,17 @@ -{{- if .Values.config }} +{{- if (include "zookeeper.createConfigmap" .) }} apiVersion: v1 kind: ConfigMap metadata: - name: {{ template "zookeeper.fullname" . }} - namespace: {{ .Release.Namespace }} - labels: {{- include "zookeeper.labels" . | nindent 4 }} + name: {{ template "common.names.fullname" . }} + namespace: {{ template "zookeeper.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} {{- if .Values.commonLabels }} - {{- include "zookeeper.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "zookeeper.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} data: zoo.cfg: |- -{{ .Values.config | indent 4 }} -{{- end -}} + {{- include "common.tplvalues.render" ( dict "value" .Values.configuration "context" $ ) | nindent 4 }} +{{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/extra-list.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/extra-list.yaml new file mode 100644 index 000000000..9ac65f9e1 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/extra-list.yaml @@ -0,0 +1,4 @@ +{{- range .Values.extraDeploy }} +--- +{{ include "common.tplvalues.render" (dict "value" . "context" $) }} +{{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/metrics-svc.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/metrics-svc.yaml old mode 100755 new mode 100644 index 3e26ed6c8..5afc4b3e5 --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/metrics-svc.yaml +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/metrics-svc.yaml @@ -2,20 +2,20 @@ apiVersion: v1 kind: Service metadata: - name: {{ template "zookeeper.fullname" . }}-metrics - namespace: {{ .Release.Namespace }} - labels: {{- include "zookeeper.labels" . | nindent 4 }} - app.kubernetes.io/component: zookeeper + name: {{ template "common.names.fullname" . }}-metrics + namespace: {{ template "zookeeper.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: metrics {{- if .Values.commonLabels }} - {{- include "zookeeper.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if or .Values.metrics.service.annotations .Values.commonAnnotations }} annotations: {{- if .Values.metrics.service.annotations }} - {{ include "zookeeper.tplValue" ( dict "value" .Values.metrics.service.annotations "context" $) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.service.annotations "context" $) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - {{- include "zookeeper.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} {{- end }} spec: @@ -24,6 +24,6 @@ spec: - name: tcp-metrics port: {{ .Values.metrics.service.port }} targetPort: metrics - selector: {{- include "zookeeper.matchLabels" . | nindent 4 }} + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} app.kubernetes.io/component: zookeeper {{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/networkpolicy.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/networkpolicy.yaml old mode 100755 new mode 100644 index f7e30b4bc..63532832c --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/networkpolicy.yaml +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/networkpolicy.yaml @@ -1,43 +1,41 @@ {{- if .Values.networkPolicy.enabled }} kind: NetworkPolicy -apiVersion: networking.k8s.io/v1 +apiVersion: {{ include "common.capabilities.networkPolicy.apiVersion" . }} metadata: - name: {{ include "zookeeper.fullname" . }} - namespace: {{ .Release.Namespace }} - labels: {{- include "zookeeper.labels" . | nindent 4 }} + name: {{ include "common.names.fullname" . }} + namespace: {{ template "zookeeper.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} {{- if .Values.commonLabels }} - {{- include "zookeeper.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "zookeeper.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} spec: podSelector: - matchLabels: {{- include "zookeeper.matchLabels" . | nindent 6 }} + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + policyTypes: + - Ingress ingress: - # Allow inbound connections to zookeeper + # Allow inbound connections to ZooKeeper - ports: - - port: {{ .Values.service.port }} - from: + - port: {{ .Values.containerPorts.client }} + {{- if .Values.metrics.enabled }} + - port: {{ .Values.metrics.containerPort }} + {{- end }} {{- if not .Values.networkPolicy.allowExternal }} + from: - podSelector: matchLabels: - {{ include "zookeeper.fullname" . }}-client: "true" + {{ include "common.names.fullname" . }}-client: "true" - podSelector: - matchLabels: {{- include "zookeeper.matchLabels" . | nindent 14 }} - {{- else }} - - podSelector: - matchLabels: {} + matchLabels: {{- include "common.labels.matchLabels" . | nindent 14 }} {{- end }} - # Internal ports - - ports: &intranodes_ports - - port: {{ .Values.service.followerPort }} - - port: {{ .Values.service.electionPort }} + # Allow internal communications between nodes + - ports: + - port: {{ .Values.containerPorts.follower }} + - port: {{ .Values.containerPorts.election }} from: - podSelector: - matchLabels: {{- include "zookeeper.matchLabels" . | nindent 14 }} - egress: - - ports: *intranodes_ports - # Allow outbound connections from zookeeper nodes - + matchLabels: {{- include "common.labels.matchLabels" . | nindent 14 }} {{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/pdb.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/pdb.yaml new file mode 100644 index 000000000..f7faf65f9 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/pdb.yaml @@ -0,0 +1,26 @@ +{{- $replicaCount := int .Values.replicaCount }} +{{- if and .Values.pdb.create (gt $replicaCount 1) }} +apiVersion: {{ include "common.capabilities.policy.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ template "zookeeper.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: zookeeper + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + {{- if .Values.pdb.minAvailable }} + minAvailable: {{ .Values.pdb.minAvailable }} + {{- end }} + {{- if .Values.pdb.maxUnavailable }} + maxUnavailable: {{ .Values.pdb.maxUnavailable }} + {{- end }} + selector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: zookeeper +{{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/poddisruptionbudget.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/poddisruptionbudget.yaml deleted file mode 100755 index 818950c66..000000000 --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/poddisruptionbudget.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- $replicaCount := int .Values.replicaCount }} -{{- if gt $replicaCount 1 }} -apiVersion: policy/v1beta1 -kind: PodDisruptionBudget -metadata: - name: {{ template "zookeeper.fullname" . }} - namespace: {{ .Release.Namespace }} - labels: {{- include "zookeeper.labels" . | nindent 4 }} - app.kubernetes.io/component: zookeeper - {{- if .Values.commonLabels }} - {{- include "zookeeper.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} - {{- end }} - {{- if .Values.commonAnnotations }} - annotations: {{- include "zookeeper.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} - {{- end }} -spec: - selector: - matchLabels: {{- include "zookeeper.matchLabels" . | nindent 6 }} - app.kubernetes.io/component: zookeeper - {{- toYaml .Values.podDisruptionBudget | nindent 2 }} -{{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/prometheusrule.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/prometheusrule.yaml new file mode 100644 index 000000000..87dcd3565 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/prometheusrule.yaml @@ -0,0 +1,27 @@ +{{- if and .Values.metrics.enabled .Values.metrics.prometheusRule.enabled .Values.metrics.prometheusRule.rules }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ include "common.names.fullname" . }} + {{- if .Values.metrics.prometheusRule.namespace }} + namespace: {{ .Values.metrics.prometheusRule.namespace }} + {{- else }} + namespace: {{ .Release.Namespace }} + {{- end }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: metrics + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.metrics.prometheusRule.additionalLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.prometheusRule.additionalLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + groups: + - name: {{ include "common.names.fullname" . }} + rules: {{- toYaml .Values.metrics.prometheusRule.rules | nindent 8 }} +{{- end }} + diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/prometheusrules.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/prometheusrules.yaml deleted file mode 100755 index 9cda3985c..000000000 --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/prometheusrules.yaml +++ /dev/null @@ -1,27 +0,0 @@ -{{- if and .Values.metrics.enabled .Values.metrics.prometheusRule.enabled .Values.metrics.prometheusRule.rules }} -apiVersion: monitoring.coreos.com/v1 -kind: PrometheusRule -metadata: - name: {{ include "zookeeper.fullname" . }} - {{- if .Values.metrics.prometheusRule.namespace }} - namespace: {{ .Values.metrics.prometheusRule.namespace }} - {{- else }} - namespace: {{ .Release.Namespace }} - {{- end }} - labels: {{- include "zookeeper.labels" . | nindent 4 }} - app.kubernetes.io/component: zookeeper - {{- range $key, $value := .Values.metrics.prometheusRule.selector }} - {{ $key }}: {{ $value | quote }} - {{- end }} - {{- if .Values.commonLabels }} - {{- include "zookeeper.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} - {{- end }} - {{- if .Values.commonAnnotations }} - annotations: {{- include "zookeeper.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} - {{- end }} -spec: - groups: - - name: {{ include "zookeeper.fullname" . }} - rules: {{- toYaml .Values.metrics.prometheusRule.rules | nindent 6 }} -{{- end }} - diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/scripts-configmap.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/scripts-configmap.yaml new file mode 100644 index 000000000..d0a7ddb49 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/scripts-configmap.yaml @@ -0,0 +1,102 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ printf "%s-scripts" (include "common.names.fullname" .) }} + namespace: {{ template "zookeeper.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: zookeeper + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: + init-certs.sh: |- + #!/bin/bash + + {{- if .Values.tls.client.enabled }} + if [[ -f "/certs/client/tls.key" ]] && [[ -f "/certs/client/tls.crt" ]] && [[ -f "/certs/client/ca.crt" ]]; then + if [[ -f "/opt/bitnami/zookeeper/config/certs/client/.initialized" ]]; then + exit 0 + fi + openssl pkcs12 -export -in "/certs/client/tls.crt" \ + -passout pass:"${ZOO_TLS_CLIENT_KEYSTORE_PASSWORD}" \ + -inkey "/certs/client/tls.key" \ + -out "/tmp/keystore.p12" + keytool -importkeystore -srckeystore "/tmp/keystore.p12" \ + -srcstoretype PKCS12 \ + -srcstorepass "${ZOO_TLS_CLIENT_KEYSTORE_PASSWORD}" \ + -deststorepass "${ZOO_TLS_CLIENT_KEYSTORE_PASSWORD}" \ + -destkeystore "/opt/bitnami/zookeeper/config/certs/client/zookeeper.keystore.jks" + rm "/tmp/keystore.p12" + keytool -import -file "/certs/client/ca.crt" \ + -keystore "/opt/bitnami/zookeeper/config/certs/client/zookeeper.truststore.jks" \ + -storepass "${ZOO_TLS_CLIENT_TRUSTSTORE_PASSWORD}" \ + -noprompt + touch /opt/bitnami/zookeeper/config/certs/client/.initialized + {{- if .Values.tls.client.autoGenerated }} + else + echo "Couldn't find the expected PEM certificates! They are mandatory when Client encryption via TLS is enabled." + exit 1 + fi + {{- else }} + elif [[ -f {{ printf "/certs/client/%s" (include "zookeeper.client.tlsTruststoreKey" .) | quote }} ]] && [[ -f {{ printf "/certs/client/%s" (include "zookeeper.client.tlsKeystoreKey" .) | quote }} ]]; then + cp {{ printf "/certs/client/%s" (include "zookeeper.client.tlsTruststoreKey" .) | quote }} "/opt/bitnami/zookeeper/config/certs/client/zookeeper.truststore.jks" + cp {{ printf "/certs/client/%s" (include "zookeeper.client.tlsKeystoreKey" .) | quote }} "/opt/bitnami/zookeeper/config/certs/client/zookeeper.keystore.jks" + else + echo "Couldn't find the expected Java Key Stores (JKS) files! They are mandatory when Client encryption via TLS is enabled." + exit 1 + fi + {{- end }} + {{- end }} + {{- if .Values.tls.quorum.enabled }} + if [[ -f "/certs/quorum/tls.key" ]] && [[ -f "/certs/quorum/tls.crt" ]] && [[ -f "/certs/quorum/ca.crt" ]]; then + openssl pkcs12 -export -in "/certs/quorum/tls.crt" \ + -passout pass:"${ZOO_TLS_QUORUM_KEYSTORE_PASSWORD}" \ + -inkey "/certs/quorum/tls.key" \ + -out "/tmp/keystore.p12" + keytool -importkeystore -srckeystore "/tmp/keystore.p12" \ + -srcstoretype PKCS12 \ + -srcstorepass "${ZOO_TLS_QUORUM_KEYSTORE_PASSWORD}" \ + -deststorepass "${ZOO_TLS_QUORUM_KEYSTORE_PASSWORD}" \ + -destkeystore "/opt/bitnami/zookeeper/config/certs/quorum/zookeeper.keystore.jks" + rm "/tmp/keystore.p12" + keytool -import -file "/certs/quorum/ca.crt" \ + -keystore "/opt/bitnami/zookeeper/config/certs/quorum/zookeeper.truststore.jks" \ + -storepass "${ZOO_TLS_QUORUM_TRUSTSTORE_PASSWORD}" \ + -noprompt + {{- if .Values.tls.quorum.autoGenerated }} + else + echo "Couldn't find the expected PEM certificates! They are mandatory when encryption Quorum via TLS is enabled." + exit 1 + fi + {{- else }} + elif [[ -f {{ printf "/certs/quorum/%s" (include "zookeeper.quorum.tlsTruststoreKey" .) | quote }} ]] && [[ -f {{ printf "/certs/quorum/%s" (include "zookeeper.quorum.tlsKeystoreKey" .) | quote }} ]]; then + cp {{ printf "/certs/quorum/%s" (include "zookeeper.quorum.tlsTruststoreKey" .) | quote }} "/opt/bitnami/zookeeper/config/certs/quorum/zookeeper.truststore.jks" + cp {{ printf "/certs/quorum/%s" (include "zookeeper.quorum.tlsKeystoreKey" .) | quote }} "/opt/bitnami/zookeeper/config/certs/quorum/zookeeper.keystore.jks" + else + echo "Couldn't find the expected Java Key Stores (JKS) files! They are mandatory when Quorum encryption via TLS is enabled." + exit 1 + fi + {{- end }} + {{- end }} + setup.sh: |- + #!/bin/bash + + # Execute entrypoint as usual after obtaining ZOO_SERVER_ID + # check ZOO_SERVER_ID in persistent volume via myid + # if not present, set based on POD hostname + if [[ -f "/bitnami/zookeeper/data/myid" ]]; then + export ZOO_SERVER_ID="$(cat /bitnami/zookeeper/data/myid)" + else + HOSTNAME="$(hostname -s)" + if [[ $HOSTNAME =~ (.*)-([0-9]+)$ ]]; then + ORD=${BASH_REMATCH[2]} + export ZOO_SERVER_ID="$((ORD + {{ .Values.minServerId }} ))" + else + echo "Failed to get index from hostname $HOST" + exit 1 + fi + fi + exec /entrypoint.sh /run.sh diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/secrets.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/secrets.yaml old mode 100755 new mode 100644 index b3d727fec..82ebc2eed --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/secrets.yaml +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/secrets.yaml @@ -1,18 +1,77 @@ -{{- if and .Values.auth.enabled (not .Values.auth.existingSecret) -}} +{{- if (include "zookeeper.client.createSecret" .) }} apiVersion: v1 kind: Secret metadata: - name: {{ template "zookeeper.fullname" . }} - namespace: {{ .Release.Namespace }} - labels: {{- include "zookeeper.labels" . | nindent 4 }} + name: {{ printf "%s-client-auth" (include "common.names.fullname" .) }} + namespace: {{ template "zookeeper.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: zookeeper {{- if .Values.commonLabels }} - {{- include "zookeeper.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "zookeeper.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} type: Opaque data: - client-password: {{ include "zookeeper.clientPassword" . | b64enc | quote }} - server-password: {{ include "zookeeper.serverPasswords" . | b64enc | quote }} + client-password: {{ include "common.secrets.passwords.manage" (dict "secret" (printf "%s-client-auth" (include "common.names.fullname" .)) "key" "client-password" "providedValues" (list "auth.client.clientPassword") "context" $) }} + server-password: {{ include "common.secrets.passwords.manage" (dict "secret" (printf "%s-client-auth" (include "common.names.fullname" .)) "key" "server-password" "providedValues" (list "auth.client.serverPasswords") "context" $) }} +{{- end }} +{{- if (include "zookeeper.quorum.createSecret" .) }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ printf "%s-quorum-auth" (include "common.names.fullname" .) }} + namespace: {{ template "zookeeper.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: zookeeper + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: Opaque +data: + quorum-learner-password: {{ include "common.secrets.passwords.manage" (dict "secret" (printf "%s-quorum-auth" (include "common.names.fullname" .)) "key" "quorum-learner-password" "providedValues" (list "auth.quorum.learnerPassword") "context" $) }} + quorum-server-password: {{ include "common.secrets.passwords.manage" (dict "secret" (printf "%s-quorum-auth" (include "common.names.fullname" .)) "key" "quorum-server-password" "providedValues" (list "auth.quorum.serverPasswords") "context" $) }} +{{- end }} +{{- if (include "zookeeper.client.createTlsPasswordsSecret" .) }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "common.names.fullname" . }}-client-tls-pass + namespace: {{ template "zookeeper.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: Opaque +data: + keystore-password: {{ default (randAlphaNum 10) .Values.tls.client.keystorePassword | b64enc | quote }} + truststore-password: {{ default (randAlphaNum 10) .Values.tls.client.truststorePassword | b64enc | quote }} +{{- end }} +{{- if (include "zookeeper.quorum.createTlsPasswordsSecret" .) }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "common.names.fullname" . }}-quorum-tls-pass + namespace: {{ template "zookeeper.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: Opaque +data: + keystore-password: {{ default (randAlphaNum 10) .Values.tls.quorum.keystorePassword | b64enc | quote }} + truststore-password: {{ default (randAlphaNum 10) .Values.tls.quorum.truststorePassword | b64enc | quote }} {{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/serviceaccount.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/serviceaccount.yaml old mode 100755 new mode 100644 index 3f7ef39fd..958a57ac2 --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/serviceaccount.yaml +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/serviceaccount.yaml @@ -3,13 +3,19 @@ apiVersion: v1 kind: ServiceAccount metadata: name: {{ template "zookeeper.serviceAccountName" . }} - namespace: {{ .Release.Namespace }} - labels: {{- include "zookeeper.labels" . | nindent 4 }} + namespace: {{ template "zookeeper.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: zookeeper role: zookeeper {{- if .Values.commonLabels }} - {{- include "zookeeper.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} - {{- if .Values.commonAnnotations }} - annotations: {{- include "zookeeper.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} - {{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.serviceAccount.annotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.serviceAccount.annotations "context" $ ) | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} {{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/servicemonitor.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/servicemonitor.yaml old mode 100755 new mode 100644 index 5782dad59..2c8af3350 --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/servicemonitor.yaml +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/servicemonitor.yaml @@ -2,27 +2,33 @@ apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: - name: {{ template "zookeeper.fullname" . }} + name: {{ template "common.names.fullname" . }} {{- if .Values.metrics.serviceMonitor.namespace }} namespace: {{ .Values.metrics.serviceMonitor.namespace }} {{- else }} namespace: {{ .Release.Namespace }} {{- end }} - labels: {{- include "zookeeper.labels" . | nindent 4 }} - app.kubernetes.io/component: zookeeper - {{- range $key, $value := .Values.metrics.serviceMonitor.selector }} - {{ $key }}: {{ $value | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: metrics + {{- if .Values.metrics.serviceMonitor.additionalLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.serviceMonitor.additionalLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonLabels }} - {{- include "zookeeper.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "zookeeper.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} spec: + {{- if .Values.metrics.serviceMonitor.jobLabel }} + jobLabel: {{ .Values.metrics.serviceMonitor.jobLabel }} + {{- end }} selector: - matchLabels: {{- include "zookeeper.matchLabels" . | nindent 6 }} - app.kubernetes.io/component: zookeeper + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + {{- if .Values.metrics.serviceMonitor.selector }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.serviceMonitor.selector "context" $) | nindent 6 }} + {{- end }} + app.kubernetes.io/component: metrics endpoints: - port: tcp-metrics path: "/metrics" @@ -32,7 +38,16 @@ spec: {{- if .Values.metrics.serviceMonitor.scrapeTimeout }} scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }} {{- end }} + {{- if .Values.metrics.serviceMonitor.relabelings }} + relabelings: {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.serviceMonitor.relabelings "context" $) | nindent 6 }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.metricRelabelings }} + metricRelabelings: {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.serviceMonitor.metricRelabelings "context" $) | nindent 6 }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.honorLabels }} + honorLabels: {{ .Values.metrics.serviceMonitor.honorLabels }} + {{- end }} namespaceSelector: matchNames: - - {{ .Release.Namespace }} + - {{ template "zookeeper.namespace" . }} {{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/statefulset.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/statefulset.yaml old mode 100755 new mode 100644 index fa1e5231f..aa4f1a971 --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/statefulset.yaml +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/statefulset.yaml @@ -1,81 +1,100 @@ -apiVersion: apps/v1 +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} kind: StatefulSet metadata: - name: {{ template "zookeeper.fullname" . }} - namespace: {{ .Release.Namespace }} - labels: {{- include "zookeeper.labels" . | nindent 4 }} + name: {{ template "common.names.fullname" . }} + namespace: {{ template "zookeeper.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} app.kubernetes.io/component: zookeeper role: zookeeper {{- if .Values.commonLabels }} - {{- include "zookeeper.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "zookeeper.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} spec: - serviceName: {{ template "zookeeper.fullname" . }}-headless replicas: {{ .Values.replicaCount }} podManagementPolicy: {{ .Values.podManagementPolicy }} - updateStrategy: - type: {{ .Values.updateStrategy }} - {{- if (eq "Recreate" .Values.updateStrategy) }} - rollingUpdate: null - {{- else if .Values.rollingUpdatePartition }} - rollingUpdate: - partition: {{ .Values.rollingUpdatePartition }} - {{- end }} selector: - matchLabels: {{- include "zookeeper.matchLabels" . | nindent 6 }} + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} app.kubernetes.io/component: zookeeper + serviceName: {{ printf "%s-%s" (include "common.names.fullname" .) (default "headless" .Values.service.headless.servicenameOverride) | trunc 63 | trimSuffix "-" }} + {{- if .Values.updateStrategy }} + updateStrategy: {{- toYaml .Values.updateStrategy | nindent 4 }} + {{- end }} template: metadata: - name: {{ template "zookeeper.fullname" . }} - labels: {{- include "zookeeper.labels" . | nindent 8 }} + annotations: + {{- if .Values.podAnnotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) | nindent 8 }} + {{- end }} + {{- if (include "zookeeper.createConfigmap" .) }} + checksum/configuration: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- end }} + {{- if or (include "zookeeper.quorum.createSecret" .) (include "zookeeper.client.createSecret" .) (include "zookeeper.client.createTlsPasswordsSecret" .) (include "zookeeper.quorum.createTlsPasswordsSecret" .) }} + checksum/secrets: {{ include (print $.Template.BasePath "/secrets.yaml") . | sha256sum }} + {{- end }} + {{- if or (include "zookeeper.client.createTlsSecret" .) (include "zookeeper.quorum.createTlsSecret" .) }} + checksum/tls-secrets: {{ include (print $.Template.BasePath "/tls-secrets.yaml") . | sha256sum }} + {{- end }} + labels: {{- include "common.labels.standard" . | nindent 8 }} app.kubernetes.io/component: zookeeper {{- if .Values.podLabels }} - {{- include "zookeeper.tplValue" (dict "value" .Values.podLabels "context" $) | nindent 8 }} + {{- include "common.tplvalues.render" (dict "value" .Values.podLabels "context" $) | nindent 8 }} {{- end }} - {{- if .Values.podAnnotations }} - annotations: {{- include "zookeeper.tplValue" ( dict "value" .Values.podAnnotations "context" $) | nindent 8 }} - {{- end }} spec: - {{- if .Values.schedulerName }} - schedulerName: {{ .Values.schedulerName }} - {{- end }} - {{- include "zookeeper.imagePullSecrets" . | nindent 6 }} serviceAccountName: {{ template "zookeeper.serviceAccountName" . }} - {{- if .Values.securityContext.enabled }} - securityContext: - fsGroup: {{ .Values.securityContext.fsGroup }} + {{- include "zookeeper.imagePullSecrets" . | nindent 6 }} + {{- if .Values.hostAliases }} + hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.hostAliases "context" $) | nindent 8 }} {{- end }} {{- if .Values.affinity }} - affinity: {{- include "zookeeper.tplValue" (dict "value" .Values.affinity "context" $) | nindent 8 }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAffinityPreset "component" "zookeeper" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAntiAffinityPreset "component" "zookeeper" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.nodeAffinityPreset.type "key" .Values.nodeAffinityPreset.key "values" .Values.nodeAffinityPreset.values) | nindent 10 }} {{- end }} {{- if .Values.nodeSelector }} - nodeSelector: {{- include "zookeeper.tplValue" (dict "value" .Values.nodeSelector "context" $) | nindent 8 }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.nodeSelector "context" $) | nindent 8 }} {{- end }} {{- if .Values.tolerations }} - tolerations: {{- include "zookeeper.tplValue" (dict "value" .Values.tolerations "context" $) | nindent 8 }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "common.tplvalues.render" (dict "value" .Values.topologySpreadConstraints "context" .) | nindent 8 }} {{- end }} {{- if .Values.priorityClassName }} priorityClassName: {{ .Values.priorityClassName }} {{- end }} - {{- if and .Values.volumePermissions.enabled .Values.persistence.enabled }} + {{- if .Values.schedulerName }} + schedulerName: {{ .Values.schedulerName }} + {{- end }} + {{- if .Values.podSecurityContext.enabled }} + securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} initContainers: + {{- if and .Values.volumePermissions.enabled .Values.persistence.enabled }} - name: volume-permissions image: {{ template "zookeeper.volumePermissions.image" . }} imagePullPolicy: {{ default "" .Values.volumePermissions.image.pullPolicy | quote }} command: - - chown + - /bin/bash args: - - -R - - {{ .Values.securityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} - - /bitnami/zookeeper - {{- if .Values.dataLogDir }} - - {{ .Values.dataLogDir }} - {{- end }} - securityContext: - runAsUser: 0 + - -ec + - | + mkdir -p /bitnami/zookeeper + chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.fsGroup }} /bitnami/zookeeper + find /bitnami/zookeeper -mindepth 1 -maxdepth 1 -not -name ".snapshot" -not -name "lost+found" | xargs -r chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.fsGroup }} + {{- if .Values.dataLogDir }} + mkdir -p {{ .Values.dataLogDir }} + chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.fsGroup }} {{ .Values.dataLogDir }} + find {{ .Values.dataLogDir }} -mindepth 1 -maxdepth 1 -not -name ".snapshot" -not -name "lost+found" | xargs -r chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.fsGroup }} + {{- end }} + {{- if .Values.volumePermissions.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.volumePermissions.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} {{- if .Values.volumePermissions.resources }} resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }} {{- end }} @@ -86,43 +105,106 @@ spec: - name: data-log mountPath: {{ .Values.dataLogDir }} {{- end }} - {{- end }} + {{- end }} + {{- if or .Values.tls.client.enabled .Values.tls.quorum.enabled }} + - name: init-certs + image: {{ include "zookeeper.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + command: + - /scripts/init-certs.sh + env: + - name: MY_POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + {{- if or .Values.tls.client.passwordsSecretName (include "zookeeper.client.createTlsPasswordsSecret" .) }} + - name: ZOO_TLS_CLIENT_KEYSTORE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "zookeeper.client.tlsPasswordsSecret" . }} + key: {{ include "zookeeper.client.tlsPasswordKeystoreKey" . }} + - name: ZOO_TLS_CLIENT_TRUSTSTORE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "zookeeper.client.tlsPasswordsSecret" . }} + key: {{ include "zookeeper.client.tlsPasswordTruststoreKey" . }} + {{- end }} + {{- if or .Values.tls.quorum.passwordsSecretName (include "zookeeper.quorum.createTlsPasswordsSecret" .) }} + - name: ZOO_TLS_QUORUM_KEYSTORE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "zookeeper.quorum.tlsPasswordsSecret" . }} + key: {{ include "zookeeper.quorum.tlsPasswordKeystoreKey" . }} + - name: ZOO_TLS_QUORUM_TRUSTSTORE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "zookeeper.quorum.tlsPasswordsSecret" . }} + key: {{ include "zookeeper.quorum.tlsPasswordTruststoreKey" . }} + {{- end }} + {{- if .Values.tls.resources }} + resources: {{- toYaml .Values.tls.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: scripts + mountPath: /scripts/init-certs.sh + subPath: init-certs.sh + {{- if or .Values.tls.client.enabled }} + - name: client-certificates + mountPath: /certs/client + - name: client-shared-certs + mountPath: /opt/bitnami/zookeeper/config/certs/client + {{- end }} + {{- if or .Values.tls.quorum.enabled }} + - name: quorum-certificates + mountPath: /certs/quorum + - name: quorum-shared-certs + mountPath: /opt/bitnami/zookeeper/config/certs/quorum + {{- end }} + {{- end }} + {{- if .Values.initContainers }} + {{- include "common.tplvalues.render" (dict "value" .Values.initContainers "context" $) | trim | nindent 8 }} + {{- end }} containers: - name: zookeeper image: {{ template "zookeeper.image" . }} imagePullPolicy: {{ .Values.image.pullPolicy | quote }} - {{- if .Values.securityContext.enabled }} - securityContext: - runAsUser: {{ .Values.securityContext.runAsUser }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else if .Values.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else if .Values.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.args "context" $) | nindent 12 }} {{- end }} - command: - - bash - - -ec - - | - # Execute entrypoint as usual after obtaining ZOO_SERVER_ID based on POD hostname - HOSTNAME=`hostname -s` - if [[ $HOSTNAME =~ (.*)-([0-9]+)$ ]]; then - ORD=${BASH_REMATCH[2]} - export ZOO_SERVER_ID=$((ORD+1)) - else - echo "Failed to get index from hostname $HOST" - exit 1 - fi - exec /entrypoint.sh /run.sh {{- if .Values.resources }} resources: {{- toYaml .Values.resources | nindent 12 }} {{- end }} env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" (or .Values.image.debug .Values.diagnosticMode.enabled) | quote }} - name: ZOO_DATA_LOG_DIR value: {{ .Values.dataLogDir | quote }} - name: ZOO_PORT_NUMBER - value: {{ .Values.service.port | quote }} + value: {{ .Values.containerPorts.client | quote }} - name: ZOO_TICK_TIME value: {{ .Values.tickTime | quote }} - name: ZOO_INIT_LIMIT value: {{ .Values.initLimit | quote }} - name: ZOO_SYNC_LIMIT value: {{ .Values.syncLimit | quote }} + - name: ZOO_PRE_ALLOC_SIZE + value: {{ .Values.preAllocSize | quote }} + - name: ZOO_SNAPCOUNT + value: {{ .Values.snapCount | quote }} - name: ZOO_MAX_CLIENT_CNXNS value: {{ .Values.maxClientCnxns | quote }} - name: ZOO_4LW_COMMANDS_WHITELIST @@ -137,37 +219,56 @@ spec: value: {{ .Values.maxSessionTimeout | quote }} - name: ZOO_SERVERS {{- $replicaCount := int .Values.replicaCount }} - {{- $followerPort := int .Values.service.followerPort }} - {{- $electionPort := int .Values.service.electionPort }} - {{- $releaseNamespace := .Release.Namespace }} - {{- $zookeeperFullname := include "zookeeper.fullname" . }} + {{- $minServerId := int .Values.minServerId }} + {{- $followerPort := int .Values.containerPorts.follower }} + {{- $electionPort := int .Values.containerPorts.election }} + {{- $releaseNamespace := include "zookeeper.namespace" . }} + {{- $zookeeperFullname := include "common.names.fullname" . }} {{- $zookeeperHeadlessServiceName := printf "%s-%s" $zookeeperFullname "headless" | trunc 63 }} {{- $clusterDomain := .Values.clusterDomain }} - value: {{ range $i, $e := until $replicaCount }}{{ $zookeeperFullname }}-{{ $e }}.{{ $zookeeperHeadlessServiceName }}.{{ $releaseNamespace }}.svc.{{ $clusterDomain }}:{{ $followerPort }}:{{ $electionPort }} {{ end }} + value: {{ range $i, $e := until $replicaCount }}{{ $zookeeperFullname }}-{{ $e }}.{{ $zookeeperHeadlessServiceName }}.{{ $releaseNamespace }}.svc.{{ $clusterDomain }}:{{ $followerPort }}:{{ $electionPort }}::{{ add $e $minServerId }} {{ end }} - name: ZOO_ENABLE_AUTH - value: {{ ternary "yes" "no" .Values.auth.enabled | quote }} - {{- if .Values.auth.enabled }} + value: {{ ternary "yes" "no" .Values.auth.client.enabled | quote }} + {{- if .Values.auth.client.enabled }} - name: ZOO_CLIENT_USER - value: {{ .Values.auth.clientUser | quote }} + value: {{ .Values.auth.client.clientUser | quote }} - name: ZOO_CLIENT_PASSWORD valueFrom: secretKeyRef: - name: {{ if .Values.auth.existingSecret }}{{ .Values.auth.existingSecret }}{{ else }}{{ template "zookeeper.fullname" . }}{{ end }} + name: {{ include "zookeeper.client.secretName" . }} key: client-password - name: ZOO_SERVER_USERS - value: {{ .Values.auth.serverUsers | quote }} + value: {{ .Values.auth.client.serverUsers | quote }} - name: ZOO_SERVER_PASSWORDS valueFrom: secretKeyRef: - name: {{ if .Values.auth.existingSecret }}{{ .Values.auth.existingSecret }}{{ else }}{{ template "zookeeper.fullname" . }}{{ end }} + name: {{ include "zookeeper.client.secretName" . }} key: server-password {{- end }} + - name: ZOO_ENABLE_QUORUM_AUTH + value: {{ ternary "yes" "no" .Values.auth.quorum.enabled | quote }} + {{- if .Values.auth.quorum.enabled }} + - name: ZOO_QUORUM_LEARNER_USER + value: {{ .Values.auth.quorum.learnerUser | quote }} + - name: ZOO_QUORUM_LEARNER_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "zookeeper.quorum.secretName" . }} + key: quorum-learner-password + - name: ZOO_QUORUM_SERVER_USERS + value: {{ .Values.auth.quorum.serverUsers | quote }} + - name: ZOO_QUORUM_SERVER_PASSWORDS + valueFrom: + secretKeyRef: + name: {{ include "zookeeper.quorum.secretName" . }} + key: quorum-server-password + {{- end }} - name: ZOO_HEAP_SIZE value: {{ .Values.heapSize | quote }} - name: ZOO_LOG_LEVEL value: {{ .Values.logLevel | quote }} - name: ALLOW_ANONYMOUS_LOGIN - value: {{ ternary "yes" "no" .Values.allowAnonymousLogin | quote }} + value: {{ ternary "no" "yes" .Values.auth.client.enabled | quote }} {{- if .Values.jvmFlags }} - name: JVMFLAGS value: {{ .Values.jvmFlags | quote }} @@ -178,103 +279,174 @@ spec: - name: ZOO_PROMETHEUS_METRICS_PORT_NUMBER value: {{ .Values.metrics.containerPort | quote }} {{- end }} - {{- if .Values.service.tls.client_enable }} + {{- if .Values.tls.client.enabled }} + - name: ZOO_TLS_PORT_NUMBER + value: {{ .Values.containerPorts.tls | quote }} - name: ZOO_TLS_CLIENT_ENABLE - value: {{ .Values.service.tls.client_enable | quote }} + value: {{ .Values.tls.client.enabled | quote }} + - name: ZOO_TLS_CLIENT_AUTH + value: {{ .Values.tls.client.auth | quote }} - name: ZOO_TLS_CLIENT_KEYSTORE_FILE - value: {{ .Values.service.tls.client_keystore_path | quote }} - - name: ZOO_TLS_CLIENT_KEYSTORE_PASSWORD - value: {{ .Values.service.tls.client_keystore_password | quote }} + value: {{ .Values.tls.client.keystorePath | quote }} - name: ZOO_TLS_CLIENT_TRUSTSTORE_FILE - value: {{ .Values.service.tls.client_truststore_path | quote }} + value: {{ .Values.tls.client.truststorePath | quote }} + {{- if or .Values.tls.client.keystorePassword .Values.tls.client.passwordsSecretName .Values.tls.client.autoGenerated }} + - name: ZOO_TLS_CLIENT_KEYSTORE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "zookeeper.client.tlsPasswordsSecret" . }} + key: {{ include "zookeeper.client.tlsPasswordKeystoreKey" . }} + {{- end }} + {{- if or .Values.tls.client.truststorePassword .Values.tls.client.passwordsSecretName .Values.tls.client.autoGenerated }} - name: ZOO_TLS_CLIENT_TRUSTSTORE_PASSWORD - value: {{ .Values.service.tls.client_truststore_password | quote }} - {{ end }} - {{- if .Values.service.tls.quorum_enable }} + valueFrom: + secretKeyRef: + name: {{ include "zookeeper.client.tlsPasswordsSecret" . }} + key: {{ include "zookeeper.client.tlsPasswordTruststoreKey" . }} + {{- end }} + {{- end }} + {{- if .Values.tls.quorum.enabled }} - name: ZOO_TLS_QUORUM_ENABLE - value: {{ .Values.service.tls.quorum_enable | quote }} + value: {{ .Values.tls.quorum.enabled | quote }} + - name: ZOO_TLS_QUORUM_CLIENT_AUTH + value: {{ .Values.tls.quorum.auth | quote }} - name: ZOO_TLS_QUORUM_KEYSTORE_FILE - value: {{ .Values.service.tls.quorum_keystore_path | quote }} - - name: ZOO_TLS_QUORUM_KEYSTORE_PASSWORD - value: {{ .Values.service.tls.quorum_keystore_password | quote }} + value: {{ .Values.tls.quorum.keystorePath | quote }} - name: ZOO_TLS_QUORUM_TRUSTSTORE_FILE - value: {{ .Values.service.tls.quorum_truststore_path | quote }} + value: {{ .Values.tls.quorum.truststorePath | quote }} + {{- if or .Values.tls.quorum.keystorePassword .Values.tls.quorum.passwordsSecretName .Values.tls.quorum.autoGenerated }} + - name: ZOO_TLS_QUORUM_KEYSTORE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "zookeeper.quorum.tlsPasswordsSecret" . }} + key: {{ include "zookeeper.quorum.tlsPasswordKeystoreKey" . }} + {{- end }} + {{- if or .Values.tls.quorum.truststorePassword .Values.tls.quorum.passwordsSecretName .Values.tls.quorum.autoGenerated }} - name: ZOO_TLS_QUORUM_TRUSTSTORE_PASSWORD - value: {{ .Values.service.tls.quorum_truststore_password | quote }} - {{ end }} + valueFrom: + secretKeyRef: + name: {{ include "zookeeper.quorum.tlsPasswordsSecret" . }} + key: {{ include "zookeeper.quorum.tlsPasswordTruststoreKey" . }} + {{- end }} + {{- end }} - name: POD_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.name {{- if .Values.extraEnvVars }} - {{- toYaml .Values.extraEnvVars | nindent 12 }} + {{- include "common.tplvalues.render" (dict "value" .Values.extraEnvVars "context" $) | nindent 12 }} {{- end }} + {{- if or .Values.extraEnvVarsCM .Values.extraEnvVarsSecret }} + envFrom: + {{- if .Values.extraEnvVarsCM }} + - configMapRef: + name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsCM "context" $) }} + {{- end }} + {{- if .Values.extraEnvVarsSecret }} + - secretRef: + name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsSecret "context" $) }} + {{- end }} + {{- end }} ports: - {{ if not .Values.service.tls.disable_base_client_port }} + {{- if not .Values.service.disableBaseClientPort }} - name: client - containerPort: {{ .Values.service.port }} - {{ end }} - {{ if .Values.service.tls.client_enable }} + containerPort: {{ .Values.containerPorts.client }} + {{- end }} + {{- if .Values.tls.client.enabled }} - name: client-tls - containerPort: {{ .Values.service.tls.client_port }} - {{ end }} + containerPort: {{ .Values.containerPorts.tls }} + {{- end }} - name: follower - containerPort: {{ .Values.service.followerPort }} + containerPort: {{ .Values.containerPorts.follower }} - name: election - containerPort: {{ .Values.service.electionPort }} + containerPort: {{ .Values.containerPorts.election }} {{- if .Values.metrics.enabled }} - name: metrics containerPort: {{ .Values.metrics.containerPort }} {{- end }} - {{- if .Values.livenessProbe.enabled }} - livenessProbe: + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.customLivenessProbe }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customLivenessProbe "context" $) | nindent 12 }} + {{- else if .Values.livenessProbe.enabled }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" (omit .Values.livenessProbe "enabled" "probeCommandTimeout") "context" $) | nindent 12 }} exec: - {{- if not .Values.service.tls.disable_base_client_port }} - command: ['/bin/bash', '-c', 'echo "ruok" | timeout {{ .Values.livenessProbe.probeCommandTimeout }} nc -w {{ .Values.livenessProbe.probeCommandTimeout }} localhost {{ .Values.service.port }} | grep imok'] + {{- if not .Values.service.disableBaseClientPort }} + command: ['/bin/bash', '-c', 'echo "ruok" | timeout {{ .Values.livenessProbe.probeCommandTimeout }} nc -w {{ .Values.livenessProbe.probeCommandTimeout }} localhost {{ .Values.containerPorts.client }} | grep imok'] + {{- else if not .Values.tls.client.enabled }} + command: ['/bin/bash', '-c', 'echo "ruok" | timeout {{ .Values.livenessProbe.probeCommandTimeout }} openssl s_client -quiet -crlf -connect localhost:{{ .Values.containerPorts.tls }} | grep imok'] {{- else }} - command: ['/bin/bash', '-c', 'echo "ruok" | timeout {{ .Values.livenessProbe.probeCommandTimeout }} openssl s_client -quiet -crlf -connect localhost:{{ .Values.service.tls.client_port }} | grep imok'] + command: ['/bin/bash', '-c', 'echo "ruok" | timeout {{ .Values.livenessProbe.probeCommandTimeout }} openssl s_client -quiet -crlf -connect localhost:{{ .Values.containerPorts.tls }} -cert {{ .Values.service.tls.client_cert_pem_path }} -key {{ .Values.service.tls.client_key_pem_path }} | grep imok'] {{- end }} - initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.livenessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} - successThreshold: {{ .Values.livenessProbe.successThreshold }} - failureThreshold: {{ .Values.livenessProbe.failureThreshold }} {{- end }} - {{- if .Values.readinessProbe.enabled }} - readinessProbe: + {{- if .Values.customReadinessProbe }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customReadinessProbe "context" $) | nindent 12 }} + {{- else if .Values.readinessProbe.enabled }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" (omit .Values.readinessProbe "enabled" "probeCommandTimeout") "context" $) | nindent 12 }} exec: - {{- if not .Values.service.tls.disable_base_client_port }} - command: ['/bin/bash', '-c', 'echo "ruok" | timeout {{ .Values.readinessProbe.probeCommandTimeout }} nc -w {{ .Values.readinessProbe.probeCommandTimeout }} localhost {{ .Values.service.port }} | grep imok'] + {{- if not .Values.service.disableBaseClientPort }} + command: ['/bin/bash', '-c', 'echo "ruok" | timeout {{ .Values.readinessProbe.probeCommandTimeout }} nc -w {{ .Values.readinessProbe.probeCommandTimeout }} localhost {{ .Values.containerPorts.client }} | grep imok'] + {{- else if not .Values.tls.client.enabled }} + command: ['/bin/bash', '-c', 'echo "ruok" | timeout {{ .Values.readinessProbe.probeCommandTimeout }} openssl s_client -quiet -crlf -connect localhost:{{ .Values.containerPorts.tls }} | grep imok'] {{- else }} - command: ['/bin/bash', '-c', 'echo "ruok" | timeout {{ .Values.readinessProbe.probeCommandTimeout }} openssl s_client -quiet -crlf -connect localhost:{{ .Values.service.tls.client_port }} | grep imok'] + command: ['/bin/bash', '-c', 'echo "ruok" | timeout {{ .Values.readinessProbe.probeCommandTimeout }} openssl s_client -quiet -crlf -connect localhost:{{ .Values.containerPorts.tls }} -cert {{ .Values.service.tls.client_cert_pem_path }} -key {{ .Values.service.tls.client_key_pem_path }} | grep imok'] {{- end }} - initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.readinessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} - successThreshold: {{ .Values.readinessProbe.successThreshold }} - failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- end }} + {{- if .Values.customStartupProbe }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customStartupProbe "context" $) | nindent 12 }} + {{- else if .Values.startupProbe.enabled }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" (omit .Values.startupProbe "enabled") "context" $) | nindent 12 }} + tcpSocket: + {{- if not .Values.service.disableBaseClientPort }} + port: client + {{- else }} + port: follower + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.lifecycleHooks }} + lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.lifecycleHooks "context" $) | nindent 12 }} {{- end }} volumeMounts: + - name: scripts + mountPath: /scripts/setup.sh + subPath: setup.sh - name: data mountPath: /bitnami/zookeeper {{- if .Values.dataLogDir }} - name: data-log mountPath: {{ .Values.dataLogDir }} {{- end }} - {{- if .Values.config }} + {{- if or .Values.configuration .Values.existingConfigmap }} - name: config mountPath: /opt/bitnami/zookeeper/conf/zoo.cfg subPath: zoo.cfg {{- end }} - {{- if .Values.extraVolumeMounts }} - {{- toYaml .Values.extraVolumeMounts | nindent 12 }} + {{- if .Values.tls.client.enabled }} + - name: client-shared-certs + mountPath: /opt/bitnami/zookeeper/config/certs/client + readOnly: true {{- end }} + {{- if .Values.tls.quorum.enabled }} + - name: quorum-shared-certs + mountPath: /opt/bitnami/zookeeper/config/certs/quorum + readOnly: true + {{- end }} + {{- if .Values.extraVolumeMounts }} + {{- include "common.tplvalues.render" ( dict "value" .Values.extraVolumeMounts "context" $ ) | nindent 12 }} + {{- end }} + {{- if .Values.sidecars }} + {{- include "common.tplvalues.render" ( dict "value" .Values.sidecars "context" $ ) | nindent 8 }} + {{- end }} volumes: - {{- if .Values.config }} + - name: scripts + configMap: + name: {{ printf "%s-scripts" (include "common.names.fullname" .) }} + defaultMode: 0755 + {{- if or .Values.configuration .Values.existingConfigmap }} - name: config configMap: - name: {{ template "zookeeper.fullname" . }} + name: {{ include "zookeeper.configmapName" . }} {{- end }} {{- if and .Values.persistence.enabled .Values.persistence.existingClaim }} - name: data @@ -292,17 +464,35 @@ spec: - name: data-log emptyDir: {} {{- end }} - {{- if .Values.extraVolumes }} - {{- toYaml .Values.extraVolumes | nindent 8 }} + {{- if .Values.tls.client.enabled }} + - name: client-certificates + secret: + secretName: {{ include "zookeeper.client.tlsSecretName" . }} + defaultMode: 256 + - name: client-shared-certs + emptyDir: {} {{- end }} - {{- if and .Values.persistence.enabled (not (and .Values.persistence.existingClaim .Values.persistence.dataLogDir.existingClaim) )}} + {{- if .Values.tls.quorum.enabled }} + - name: quorum-certificates + secret: + secretName: {{ include "zookeeper.quorum.tlsSecretName" . }} + defaultMode: 256 + - name: quorum-shared-certs + emptyDir: {} + {{- end }} + {{- if .Values.extraVolumes }} + {{- include "common.tplvalues.render" (dict "value" .Values.extraVolumes "context" $) | nindent 8 }} + {{- end }} + {{- if and .Values.persistence.enabled (not (and .Values.persistence.existingClaim .Values.persistence.dataLogDir.existingClaim) ) }} volumeClaimTemplates: - {{- if not .Values.persistence.existingClaim }} + {{- if not .Values.persistence.existingClaim }} - metadata: name: data - annotations: - {{- range $key, $value := .Values.persistence.annotations }} - {{ $key }}: {{ $value }} + {{- if .Values.persistence.annotations }} + annotations: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.annotations "context" $) | nindent 10 }} + {{- end }} + {{- if .Values.persistence.labels }} + labels: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.labels "context" $) | nindent 10 }} {{- end }} spec: accessModes: @@ -312,14 +502,19 @@ spec: resources: requests: storage: {{ .Values.persistence.size | quote }} - {{- include "zookeeper.storageClass" . | nindent 8 }} + {{- include "common.storage.class" (dict "persistence" .Values.persistence "global" .Values.global) | nindent 8 }} + {{- if .Values.persistence.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.selector "context" $) | nindent 10 }} + {{- end }} {{- end }} {{- if and (not .Values.persistence.dataLogDir.existingClaim) .Values.dataLogDir }} - metadata: name: data-log - annotations: - {{- range $key, $value := .Values.persistence.annotations }} - {{ $key }}: {{ $value }} + {{- if .Values.persistence.annotations }} + annotations: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.annotations "context" $) | nindent 10 }} + {{- end }} + {{- if .Values.persistence.labels }} + labels: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.labels "context" $) | nindent 10 }} {{- end }} spec: accessModes: @@ -329,6 +524,9 @@ spec: resources: requests: storage: {{ .Values.persistence.dataLogDir.size | quote }} - {{- include "zookeeper.storageClass" . | nindent 8 }} + {{- include "common.storage.class" (dict "persistence" .Values.persistence "global" .Values.global) | nindent 8 }} + {{- if .Values.persistence.dataLogDir.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.dataLogDir.selector "context" $) | nindent 10 }} + {{- end }} {{- end }} {{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/svc-headless.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/svc-headless.yaml old mode 100755 new mode 100644 index 972efb51d..e7ab496cf --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/svc-headless.yaml +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/svc-headless.yaml @@ -1,42 +1,42 @@ apiVersion: v1 kind: Service metadata: - name: {{ template "zookeeper.fullname" . }}-headless - namespace: {{ .Release.Namespace }} - labels: {{- include "zookeeper.labels" . | nindent 4 }} + name: {{ printf "%s-%s" (include "common.names.fullname" .) (default "headless" .Values.service.headless.servicenameOverride) | trunc 63 | trimSuffix "-" }} + namespace: {{ template "zookeeper.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} app.kubernetes.io/component: zookeeper {{- if .Values.commonLabels }} - {{- include "zookeeper.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} - {{- if or .Values.commonAnnotations .Values.service.annotations }} + {{- if or .Values.commonAnnotations .Values.service.headless.annotations }} annotations: {{- if .Values.service.headless.annotations }} - {{- include "zookeeper.tplValue" ( dict "value" .Values.service.headless.annotations "context" $ ) | nindent 4 }}\ + {{- include "common.tplvalues.render" ( dict "value" .Values.service.headless.annotations "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - {{- include "zookeeper.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} {{- end }} spec: type: ClusterIP clusterIP: None - publishNotReadyAddresses: {{ .Values.service.publishNotReadyAddresses }} + publishNotReadyAddresses: {{ .Values.service.headless.publishNotReadyAddresses }} ports: - {{ if not .Values.service.tls.disable_base_client_port }} + {{- if not .Values.service.disableBaseClientPort }} - name: tcp-client - port: 2181 + port: {{ .Values.service.ports.client }} targetPort: client - {{ end }} - {{ if .Values.service.tls.client_enable }} + {{- end }} + {{- if .Values.tls.client.enabled }} - name: tcp-client-tls - port: {{ .Values.service.tls.client_port }} + port: {{ .Values.service.ports.tls }} targetPort: client-tls - {{ end }} - - name: follower - port: 2888 + {{- end }} + - name: tcp-follower + port: {{ .Values.service.ports.follower }} targetPort: follower - name: tcp-election - port: 3888 + port: {{ .Values.service.ports.election }} targetPort: election - selector: {{- include "zookeeper.matchLabels" . | nindent 4 }} + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} app.kubernetes.io/component: zookeeper diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/svc.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/svc.yaml old mode 100755 new mode 100644 index da3a2895a..6ad0b1096 --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/svc.yaml +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/svc.yaml @@ -1,40 +1,71 @@ apiVersion: v1 kind: Service metadata: - name: {{ template "zookeeper.fullname" . }} - namespace: {{ .Release.Namespace }} - labels: {{- include "zookeeper.labels" . | nindent 4 }} + name: {{ template "common.names.fullname" . }} + namespace: {{ template "zookeeper.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} app.kubernetes.io/component: zookeeper {{- if .Values.commonLabels }} - {{- include "zookeeper.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if or .Values.commonAnnotations .Values.service.annotations }} annotations: {{- if .Values.service.annotations }} - {{- include "zookeeper.tplValue" ( dict "value" .Values.service.annotations "context" $ ) | nindent 4 }}\ + {{- include "common.tplvalues.render" ( dict "value" .Values.service.annotations "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - {{- include "zookeeper.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} {{- end }} spec: type: {{ .Values.service.type }} + {{- if and .Values.service.clusterIP (eq .Values.service.type "ClusterIP") }} + clusterIP: {{ .Values.service.clusterIP }} + {{- end }} + {{- if or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort") }} + externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy | quote }} + {{- end }} + {{- if and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerSourceRanges)) }} + loadBalancerSourceRanges: {{ .Values.service.loadBalancerSourceRanges }} + {{- end }} + {{- if and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerIP)) }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} + {{- end }} + {{- if .Values.service.sessionAffinity }} + sessionAffinity: {{ .Values.service.sessionAffinity }} + {{- end }} + {{- if .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: {{- include "common.tplvalues.render" (dict "value" .Values.service.sessionAffinityConfig "context" $) | nindent 4 }} + {{- end }} ports: - {{ if not .Values.service.tls.disable_base_client_port }} + {{- if not .Values.service.disableBaseClientPort }} - name: tcp-client - port: 2181 + port: {{ .Values.service.ports.client }} targetPort: client - {{ end }} - {{ if .Values.service.tls.client_enable }} + {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePorts.client)) }} + nodePort: {{ .Values.service.nodePorts.client }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + {{- end }} + {{- if .Values.tls.client.enabled }} - name: tcp-client-tls - port: {{ .Values.service.tls.client_port }} + port: {{ .Values.service.ports.tls }} targetPort: client-tls - {{ end }} - - name: follower - port: 2888 + {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePorts.tls)) }} + nodePort: {{ .Values.service.nodePorts.tls }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + {{- end }} + - name: tcp-follower + port: {{ .Values.service.ports.follower }} targetPort: follower - name: tcp-election - port: 3888 + port: {{ .Values.service.ports.election }} targetPort: election - selector: {{- include "zookeeper.matchLabels" . | nindent 4 }} + {{- if .Values.service.extraPorts }} + {{- include "common.tplvalues.render" (dict "value" .Values.service.extraPorts "context" $) | nindent 4 }} + {{- end }} + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} app.kubernetes.io/component: zookeeper diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/tls-secrets.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/tls-secrets.yaml new file mode 100644 index 000000000..a07480d55 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/templates/tls-secrets.yaml @@ -0,0 +1,55 @@ +{{- if (include "zookeeper.client.createTlsSecret" .) }} +{{- $ca := genCA "zookeeper-client-ca" 365 }} +{{- $releaseNamespace := .Release.Namespace }} +{{- $clusterDomain := .Values.clusterDomain }} +{{- $fullname := include "common.names.fullname" . }} +{{- $serviceName := include "common.names.fullname" . }} +{{- $headlessServiceName := printf "%s-headless" (include "common.names.fullname" .) }} +{{- $altNames := list (printf "*.%s.%s.svc.%s" $headlessServiceName $releaseNamespace $clusterDomain) (printf "%s.%s.svc.%s" $headlessServiceName $releaseNamespace $clusterDomain) (printf "*.%s.%s.svc.%s" $serviceName $releaseNamespace $clusterDomain) (printf "%s.%s.svc.%s" $serviceName $releaseNamespace $clusterDomain) "127.0.0.1" "localhost" $fullname }} +{{- $crt := genSignedCert $fullname nil $altNames 365 $ca }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "common.names.fullname" . }}-client-crt + namespace: {{ template "zookeeper.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $crt.Cert | b64enc | quote }} + tls.key: {{ $crt.Key | b64enc | quote }} +{{- end }} +{{- if (include "zookeeper.quorum.createTlsSecret" .) }} +{{- $ca := genCA "zookeeper-quorum-ca" 365 }} +{{- $releaseNamespace := .Release.Namespace }} +{{- $clusterDomain := .Values.clusterDomain }} +{{- $fullname := include "common.names.fullname" . }} +{{- $serviceName := include "common.names.fullname" . }} +{{- $headlessServiceName := printf "%s-headless" (include "common.names.fullname" .) }} +{{- $altNames := list (printf "*.%s.%s.svc.%s" $headlessServiceName $releaseNamespace $clusterDomain) (printf "%s.%s.svc.%s" $headlessServiceName $releaseNamespace $clusterDomain) (printf "*.%s.%s.svc.%s" $serviceName $releaseNamespace $clusterDomain) (printf "%s.%s.svc.%s" $serviceName $releaseNamespace $clusterDomain) $fullname }} +{{- $crt := genSignedCert $fullname nil $altNames 365 $ca }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "common.names.fullname" . }}-quorum-crt + namespace: {{ template "zookeeper.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $crt.Cert | b64enc | quote }} + tls.key: {{ $crt.Key | b64enc | quote }} +{{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/values-production.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/values-production.yaml deleted file mode 100755 index 7d678603f..000000000 --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/values-production.yaml +++ /dev/null @@ -1,430 +0,0 @@ -## Global Docker image parameters -## Please, note that this will override the image parameters, including dependencies, configured to use the global value -## Current available global Docker image parameters: imageRegistry and imagePullSecrets -## -# global: -# imageRegistry: myRegistryName -# imagePullSecrets: -# - myRegistryKeySecretName -# storageClass: myStorageClass - -## Bitnami Zookeeper image version -## ref: https://hub.docker.com/r/bitnami/zookeeper/tags/ -## -image: - registry: docker.io - repository: bitnami/zookeeper - tag: 3.6.2-debian-10-r10 - - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - ## Set to true if you would like to see extra information on logs - ## It turns BASH and NAMI debugging in minideb - ## ref: https://github.com/bitnami/minideb-extras/#turn-on-bash-debugging - ## - debug: false - -## String to partially override zookeeper.fullname template (will maintain the release name) -# nameOverride: - -## String to fully override zookeeper.fullname template -# fullnameOverride: - -## Kubernetes Cluster Domain -## -clusterDomain: cluster.local - -## Add labels to all the deployed resources -## -commonLabels: {} - -## Add annotations to all the deployed resources -## -commonAnnotations: {} - -## Init containers parameters: -## volumePermissions: Change the owner and group of the persistent volume mountpoint to runAsUser:fsGroup values from the securityContext section. -## -volumePermissions: - enabled: false - image: - registry: docker.io - repository: bitnami/minideb - tag: buster - pullPolicy: Always - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - resources: {} - -## extraVolumes and extraVolumeMounts allows you to mount other volumes -## Example Use Cases: -## mount certificates to enable tls -# extraVolumes: -# - name: zookeeper-keystore -# secret: -# defaultMode: 288 -# secretName: zookeeper-keystore -# - name: zookeeper-trustsore -# secret: -# defaultMode: 288 -# secretName: zookeeper-truststore -# extraVolumeMounts: -# - name: zookeeper-keystore -# mountPath: /certs/keystore -# readOnly: true -# - name: zookeeper-truststore -# mountPath: /certs/truststore -# readOnly: true - - -## StatefulSet controller supports automated updates. There are two valid update strategies: RollingUpdate and OnDelete -## ref: https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets -## -updateStrategy: RollingUpdate - -## Limits the number of pods of the replicated application that are down simultaneously from voluntary disruptions -## The PDB will only be created if replicaCount is greater than 1 -## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions -## -podDisruptionBudget: - maxUnavailable: 1 - -## Partition update strategy -## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions -## -# rollingUpdatePartition: - -## StatefulSet controller supports relax its ordering guarantees while preserving its uniqueness and identity guarantees. There are two valid pod management policies: OrderedReady and Parallel -## ref: https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#pod-management-policy -## -podManagementPolicy: Parallel - -## Number of ZooKeeper nodes -## -replicaCount: 3 - -## Basic time unit in milliseconds used by ZooKeeper for heartbeats -## -tickTime: 2000 - -## ZooKeeper uses to limit the length of time the ZooKeeper servers in quorum have to connect to a leader -## -initLimit: 10 - -## How far out of date a server can be from a leader -## -syncLimit: 5 - -## Limits the number of concurrent connections that a single client may make to a single member of the ZooKeeper ensemble -## -maxClientCnxns: 60 - -## Maximum session timeout in milliseconds that the server will allow the client to negotiate. Defaults to 20 times the tickTime. -## -maxSessionTimeout: 40000 - -## A list of comma separated Four Letter Words commands to use -## -fourlwCommandsWhitelist: srvr, mntr, ruok - -## Allow zookeeper to listen for peers on all IPs -## -listenOnAllIPs: false - -## Allow to accept connections from unauthenticated users -## -allowAnonymousLogin: true - -autopurge: - ## Retains the snapRetainCount most recent snapshots and the corresponding transaction logs and deletes the rest - ## - snapRetainCount: 3 - ## The time interval in hours for which the purge task has to be triggered. Set to a positive integer (1 and above) to enable the auto purging. - ## - purgeInterval: 0 - -auth: - ## Use existing secret (ignores previous password) - ## - # existingSecret: - ## Enable Zookeeper auth. It uses SASL/Digest-MD5 - ## - enabled: false - ## User that will use Zookeeper clients to auth - ## - clientUser: - ## Password that will use Zookeeper clients to auth - ## - clientPassword: - ## Comma, semicolon or whitespace separated list of user to be created. Specify them as a string, for example: "user1,user2,admin" - ## - serverUsers: - ## Comma, semicolon or whitespace separated list of passwords to assign to users when created. Specify them as a string, for example: "pass4user1, pass4user2, pass4admin" - ## - serverPasswords: - -## Size in MB for the Java Heap options (Xmx and XMs). This env var is ignored if Xmx an Xms are configured via JVMFLAGS -## -heapSize: 1024 - -## Log level for the Zookeeper server. ERROR by default. Have in mind if you set it to INFO or WARN the ReadinessProve will produce a lot of logs. -## -logLevel: ERROR - -## Data log directory. Specifying this option will direct zookeeper to write the transaction log to the dataLogDir rather than the dataDir. -## This allows a dedicated log device to be used, and helps avoid competition between logging and snaphots. -## Example: -## dataLogDir: /bitnami/zookeeper/dataLog -## -dataLogDir: "" - -## Default JVMFLAGS for the ZooKeeper process -## -# jvmFlags: - -## Configure ZooKeeper with a custom zoo.cfg file -## -# config: - -## Kubernetes configuration -## For minikube, set this to NodePort, elsewhere use LoadBalancer -## -service: - type: ClusterIP - port: 2181 - followerPort: 2888 - electionPort: 3888 - publishNotReadyAddresses: true - tls: - client_enable: true - quorum_enable: true - disable_base_client_port: true - - client_port: 3181 - - client_keystore_path: /tls_key_store/key_store_file - client_keystore_password: "" - client_truststore_path: /tls_trust_store/trust_store_file - client_truststore_password: "" - - quorum_keystore_path: /tls_key_store/key_store_file - quorum_keystore_password: "" - quorum_truststore_path: /tls_trust_store/trust_store_file - quorum_truststore_password: "" - annotations: {} - headless: - annotations: {} - -## Service account for Zookeeper to use. -## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ -## -serviceAccount: - ## Specifies whether a ServiceAccount should be created - ## - create: false - ## The name of the ServiceAccount to use. - ## If not set and create is true, a name is generated using the zookeeper.fullname template - # name: - -## Zookeeper Pod Security Context -securityContext: - enabled: true - fsGroup: 1001 - runAsUser: 1001 - -## Zookeeper data Persistent Volume Storage Class -## If defined, storageClassName: <storageClass> -## If set to "-", storageClassName: "", which disables dynamic provisioning -## If undefined (the default) or set to null, no storageClassName spec is -## set, choosing the default provisioner. (gp2 on AWS, standard on -## GKE, AWS & OpenStack) -## -persistence: - enabled: true - ## A manually managed Persistent Volume and Claim - ## If defined, PVC must be created manually before volume will be bound - ## The value is evaluated as a template - ## - # existingClaim: - - # storageClass: "-" - accessModes: - - ReadWriteOnce - size: 8Gi - annotations: {} - dataLogDir: - size: 8Gi - ## A manually managed Persistent Volume and Claim - ## If defined, PVC must be created manually before volume will be bound - ## The value is evaluated as a template - ## - # existingClaim: - -## Node labels for pod assignment -## Ref: https://kubernetes.io/docs/user-guide/node-selection/ -## -nodeSelector: {} - -## Tolerations for pod assignment -## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ -## -tolerations: [] - -## Labels -## Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ -podLabels: {} - -## Annotations -## Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ -podAnnotations: {} - -## Name of the priority class to be used by zookeeper pods, priority class needs to be created beforehand -## Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ -priorityClassName: "" - -## Affinity for pod assignment -## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity -## -affinity: {} - -## Scheduler name -## https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ -## -# schedulerName: stork - -## Configure resource requests and limits -## ref: http://kubernetes.io/docs/user-guide/compute-resources/ -## -resources: - requests: - memory: 256Mi - cpu: 250m - -## Configure extra options for liveness and readiness probes -## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) -## -livenessProbe: - enabled: true - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 6 - successThreshold: 1 - probeCommandTimeout: 2 - -readinessProbe: - enabled: true - initialDelaySeconds: 5 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 6 - successThreshold: 1 - probeCommandTimeout: 2 - -## Network policies -## Ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ -## -networkPolicy: - ## Specifies whether a NetworkPolicy should be created - ## - enabled: true - - ## The Policy model to apply. When set to false, only pods with the correct - ## client label will have network access to the port Redis is listening - ## on. When true, zookeeper accept connections from any source - ## (with the correct destination port). - ## - allowExternal: true - -## Zookeeper Prometheus Exporter configuration -## -metrics: - enabled: false - - ## Zookeeper Prometheus Exporter container port - ## - containerPort: 9141 - - ## Service configuration - ## - service: - ## Zookeeper Prometheus Exporter service type - ## - type: ClusterIP - ## Zookeeper Prometheus Exporter service port - ## - port: 9141 - ## Annotations for the Zookeeper Prometheus Exporter metrics service - ## - annotations: - prometheus.io/scrape: "true" - prometheus.io/port: "{{ .Values.metrics.service.port }}" - prometheus.io/path: "/metrics" - - ## Prometheus Operator ServiceMonitor configuration - ## - serviceMonitor: - enabled: false - ## Namespace for the ServiceMonitor Resource (defaults to the Release Namespace) - ## - namespace: - - ## Interval at which metrics should be scraped. - ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#endpoint - ## - # interval: 10s - - ## Timeout after which the scrape is ended - ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#endpoint - ## - # scrapeTimeout: 10s - - ## ServiceMonitor selector labels - ## ref: https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#prometheus-configuration - ## - # selector: - # prometheus: my-prometheus - - ## Prometheus Operator PrometheusRule configuration - ## - prometheusRule: - enabled: false - ## Namespace for the PrometheusRule Resource (defaults to the Release Namespace) - ## - namespace: - - ## PrometheusRule selector labels - ## ref: https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#prometheus-configuration - ## - # selector: - # prometheus: my-prometheus - - ## Some example rules. - rules: [] - # - alert: ZookeeperSyncedFollowers - # annotations: - # message: The number of synced followers for the leader node in Zookeeper deployment my-release is less than 2. This usually means that some of the Zookeeper nodes aren't communicating properly. If it doesn't resolve itself you can try killing the pods (one by one). - # expr: max(synced_followers{service="my-release-metrics"}) < 2 - # for: 5m - # labels: - # severity: critical - # - alert: ZookeeperOutstandingRequests - # annotations: - # message: The number of outstanding requests for Zookeeper pod {{ $labels.pod }} is greater than 10. This can indicate a performance issue with the Pod or cluster a whole. - # expr: outstanding_requests{service="my-release-metrics"} > 10 - # for: 5m - # labels: - # severity: critical diff --git a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/values.yaml b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/values.yaml old mode 100755 new mode 100644 index a40decb54..b9d59000d --- a/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/values.yaml +++ b/scripts/helmcharts/databases/charts/kafka/charts/zookeeper/values.yaml @@ -1,320 +1,262 @@ +## @section Global parameters ## Global Docker image parameters ## Please, note that this will override the image parameters, including dependencies, configured to use the global value -## Current available global Docker image parameters: imageRegistry and imagePullSecrets +## Current available global Docker image parameters: imageRegistry, imagePullSecrets and storageClass ## -# global: -# imageRegistry: myRegistryName -# imagePullSecrets: -# - myRegistryKeySecretName -# storageClass: myStorageClass -## Bitnami Zookeeper image version +## @param global.imageRegistry Global Docker image registry +## @param global.imagePullSecrets Global Docker registry secret names as an array +## @param global.storageClass Global StorageClass for Persistent Volume(s) +## +global: + imageRegistry: "" + ## E.g. + ## imagePullSecrets: + ## - myRegistryKeySecretName + ## + imagePullSecrets: [] + storageClass: "" + +## @section Common parameters +## + +## @param kubeVersion Override Kubernetes version +## +kubeVersion: "" +## @param nameOverride String to partially override common.names.fullname template (will maintain the release name) +## +nameOverride: "" +## @param fullnameOverride String to fully override common.names.fullname template +## +fullnameOverride: "" +## @param clusterDomain Kubernetes Cluster Domain +## +clusterDomain: cluster.local +## @param extraDeploy Extra objects to deploy (evaluated as a template) +## +extraDeploy: [] +## @param commonLabels Add labels to all the deployed resources +## +commonLabels: {} +## @param commonAnnotations Add annotations to all the deployed resources +## +commonAnnotations: {} +## @param namespaceOverride Override namespace for ZooKeeper resources +## Useful when including ZooKeeper as a chart dependency, so it can be released into a different namespace than the parent +## +namespaceOverride: "" + +## Enable diagnostic mode in the statefulset +## +diagnosticMode: + ## @param diagnosticMode.enabled Enable diagnostic mode (all probes will be disabled and the command will be overridden) + ## + enabled: false + ## @param diagnosticMode.command Command to override all containers in the statefulset + ## + command: + - sleep + ## @param diagnosticMode.args Args to override all containers in the statefulset + ## + args: + - infinity + +## @section ZooKeeper chart parameters + +## Bitnami ZooKeeper image version ## ref: https://hub.docker.com/r/bitnami/zookeeper/tags/ +## @param image.registry ZooKeeper image registry +## @param image.repository ZooKeeper image repository +## @param image.tag ZooKeeper image tag (immutable tags are recommended) +## @param image.digest ZooKeeper image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag +## @param image.pullPolicy ZooKeeper image pull policy +## @param image.pullSecrets Specify docker-registry secret names as an array +## @param image.debug Specify if debug values should be set ## image: registry: docker.io repository: bitnami/zookeeper - tag: 3.6.2-debian-10-r10 - + tag: 3.8.0-debian-11-r74 + digest: "" ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## ref: https://kubernetes.io/docs/user-guide/images/#pre-pulling-images ## pullPolicy: IfNotPresent ## Optionally specify an array of imagePullSecrets. ## Secrets must be manually created in the namespace. ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## Example: + ## pullSecrets: + ## - myRegistryKeySecretName ## - # pullSecrets: - # - myRegistryKeySecretName + pullSecrets: [] ## Set to true if you would like to see extra information on logs - ## It turns BASH and NAMI debugging in minideb - ## ref: https://github.com/bitnami/minideb-extras/#turn-on-bash-debugging ## debug: false - -## String to partially override zookeeper.fullname template (will maintain the release name) -# nameOverride: - -## String to fully override zookeeper.fullname template -# fullnameOverride: - -## Kubernetes Cluster Domain +## Authentication parameters ## -clusterDomain: cluster.local - -## Add labels to all the deployed resources -## -commonLabels: {} - -## Add annotations to all the deployed resources -## -commonAnnotations: {} - -## Init containers parameters: -## volumePermissions: Change the owner and group of the persistent volume mountpoint to runAsUser:fsGroup values from the securityContext section. -## -volumePermissions: - enabled: false - image: - registry: docker.io - repository: bitnami/minideb - tag: buster - pullPolicy: Always - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ +auth: + client: + ## @param auth.client.enabled Enable ZooKeeper client-server authentication. It uses SASL/Digest-MD5 ## - # pullSecrets: - # - myRegistryKeySecretName - resources: {} - -## extraVolumes and extraVolumeMounts allows you to mount other volumes -## Example Use Cases: -## mount certificates to enable tls -# extraVolumes: -# - name: zookeeper-keystore -# secret: -# defaultMode: 288 -# secretName: zookeeper-keystore -# - name: zookeeper-trustsore -# secret: -# defaultMode: 288 -# secretName: zookeeper-truststore -# extraVolumeMounts: -# - name: zookeeper-keystore -# mountPath: /certs/keystore -# readOnly: true -# - name: zookeeper-truststore -# mountPath: /certs/truststore -# readOnly: true - -## StatefulSet controller supports automated updates. There are two valid update strategies: RollingUpdate and OnDelete -## ref: https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets -## -updateStrategy: RollingUpdate - -## Limits the number of pods of the replicated application that are down simultaneously from voluntary disruptions -## The PDB will only be created if replicaCount is greater than 1 -## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions -## -podDisruptionBudget: - maxUnavailable: 1 - -## Partition update strategy -## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions -## -# rollingUpdatePartition: - -## StatefulSet controller supports relax its ordering guarantees while preserving its uniqueness and identity guarantees. There are two valid pod management policies: OrderedReady and Parallel -## ref: https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#pod-management-policy -## -podManagementPolicy: Parallel - -## Number of ZooKeeper nodes -## -replicaCount: 1 - -## Basic time unit in milliseconds used by ZooKeeper for heartbeats + enabled: false + ## @param auth.client.clientUser User that will use ZooKeeper clients to auth + ## + clientUser: "" + ## @param auth.client.clientPassword Password that will use ZooKeeper clients to auth + ## + clientPassword: "" + ## @param auth.client.serverUsers Comma, semicolon or whitespace separated list of user to be created + ## Specify them as a string, for example: "user1,user2,admin" + ## + serverUsers: "" + ## @param auth.client.serverPasswords Comma, semicolon or whitespace separated list of passwords to assign to users when created + ## Specify them as a string, for example: "pass4user1, pass4user2, pass4admin" + ## + serverPasswords: "" + ## @param auth.client.existingSecret Use existing secret (ignores previous passwords) + ## + existingSecret: "" + quorum: + ## @param auth.quorum.enabled Enable ZooKeeper server-server authentication. It uses SASL/Digest-MD5 + ## + enabled: false + ## @param auth.quorum.learnerUser User that the ZooKeeper quorumLearner will use to authenticate to quorumServers. + ## Note: Make sure the user is included in auth.quorum.serverUsers + ## + learnerUser: "" + ## @param auth.quorum.learnerPassword Password that the ZooKeeper quorumLearner will use to authenticate to quorumServers. + ## + learnerPassword: "" + ## @param auth.quorum.serverUsers Comma, semicolon or whitespace separated list of users for the quorumServers. + ## Specify them as a string, for example: "user1,user2,admin" + ## + serverUsers: "" + ## @param auth.quorum.serverPasswords Comma, semicolon or whitespace separated list of passwords to assign to users when created + ## Specify them as a string, for example: "pass4user1, pass4user2, pass4admin" + ## + serverPasswords: "" + ## @param auth.quorum.existingSecret Use existing secret (ignores previous passwords) + ## + existingSecret: "" +## @param tickTime Basic time unit (in milliseconds) used by ZooKeeper for heartbeats ## tickTime: 2000 - -## ZooKeeper uses to limit the length of time the ZooKeeper servers in quorum have to connect to a leader +## @param initLimit ZooKeeper uses to limit the length of time the ZooKeeper servers in quorum have to connect to a leader ## initLimit: 10 - -## How far out of date a server can be from a leader +## @param syncLimit How far out of date a server can be from a leader ## syncLimit: 5 - -## Limits the number of concurrent connections that a single client may make to a single member of the ZooKeeper ensemble +## @param preAllocSize Block size for transaction log file +## +preAllocSize: 65536 +## @param snapCount The number of transactions recorded in the transaction log before a snapshot can be taken (and the transaction log rolled) +## +snapCount: 100000 +## @param maxClientCnxns Limits the number of concurrent connections that a single client may make to a single member of the ZooKeeper ensemble ## maxClientCnxns: 60 - -## A list of comma separated Four Letter Words commands to use -## -fourlwCommandsWhitelist: srvr, mntr, ruok - -## Allow zookeeper to listen for peers on all IPs -## -listenOnAllIPs: false - -## Allow to accept connections from unauthenticated users -## -allowAnonymousLogin: true - -autopurge: - ## Retains the snapRetainCount most recent snapshots and the corresponding transaction logs and deletes the rest - ## - snapRetainCount: 3 - ## The time interval in hours for which the purge task has to be triggered. Set to a positive integer (1 and above) to enable the auto purging. - ## - purgeInterval: 0 - -## Maximum session timeout in milliseconds that the server will allow the client to negotiate. Defaults to 20 times the tickTime. +## @param maxSessionTimeout Maximum session timeout (in milliseconds) that the server will allow the client to negotiate +## Defaults to 20 times the tickTime ## maxSessionTimeout: 40000 - -auth: - ## Use existing secret (ignores previous password) - ## - # existingSecret: - ## Enable Zookeeper auth. It uses SASL/Digest-MD5 - ## - enabled: false - ## User that will use Zookeeper clients to auth - ## - clientUser: - ## Password that will use Zookeeper clients to auth - ## - clientPassword: - ## Comma, semicolon or whitespace separated list of user to be created. Specify them as a string, for example: "user1,user2,admin" - ## - serverUsers: - ## Comma, semicolon or whitespace separated list of passwords to assign to users when created. Specify them as a string, for example: "pass4user1, pass4user2, pass4admin" - ## - serverPasswords: - -## Size in MB for the Java Heap options (Xmx and XMs). This env var is ignored if Xmx an Xms are configured via JVMFLAGS +## @param heapSize Size (in MB) for the Java Heap options (Xmx and Xms) +## This env var is ignored if Xmx an Xms are configured via `jvmFlags` ## heapSize: 1024 - -## Log level for the Zookeeper server. ERROR by default. Have in mind if you set it to INFO or WARN the ReadinessProve will produce a lot of logs. +## @param fourlwCommandsWhitelist A list of comma separated Four Letter Words commands that can be executed +## +fourlwCommandsWhitelist: srvr, mntr, ruok +## @param minServerId Minimal SERVER_ID value, nodes increment their IDs respectively +## Servers increment their ID starting at this minimal value. +## E.g., with `minServerId=10` and 3 replicas, server IDs will be 10, 11, 12 for z-0, z-1 and z-2 respectively. +## +minServerId: 1 +## @param listenOnAllIPs Allow ZooKeeper to listen for connections from its peers on all available IP addresses +## +listenOnAllIPs: false +## Ongoing data directory cleanup configuration +## +autopurge: + ## @param autopurge.snapRetainCount The most recent snapshots amount (and corresponding transaction logs) to retain + ## + snapRetainCount: 3 + ## @param autopurge.purgeInterval The time interval (in hours) for which the purge task has to be triggered + ## Set to a positive integer to enable the auto purging + ## + purgeInterval: 0 +## @param logLevel Log level for the ZooKeeper server. ERROR by default +## Have in mind if you set it to INFO or WARN the ReadinessProve will produce a lot of logs ## logLevel: ERROR - -## Data log directory. Specifying this option will direct zookeeper to write the transaction log to the dataLogDir rather than the dataDir. -## This allows a dedicated log device to be used, and helps avoid competition between logging and snaphots. -## Example: +## @param jvmFlags Default JVM flags for the ZooKeeper process +## +jvmFlags: "" +## @param dataLogDir Dedicated data log directory +## This allows a dedicated log device to be used, and helps avoid competition between logging and snapshots. +## E.g. ## dataLogDir: /bitnami/zookeeper/dataLog ## dataLogDir: "" - -## Default JVMFLAGS for the ZooKeeper process +## @param configuration Configure ZooKeeper with a custom zoo.cfg file +## e.g: +## configuration: |- +## deploy-working-dir=/bitnami/geode/data +## log-level=info +## ... ## -# jvmFlags: - -## Configure ZooKeeper with a custom zoo.cfg file +configuration: "" +## @param existingConfigmap The name of an existing ConfigMap with your custom configuration for ZooKeeper +## NOTE: When it's set the `configuration` parameter is ignored ## -# config: - -## Kubernetes configuration -## For minikube, set this to NodePort, elsewhere use LoadBalancer +existingConfigmap: "" +## @param extraEnvVars Array with extra environment variables to add to ZooKeeper nodes +## e.g: +## extraEnvVars: +## - name: FOO +## value: "bar" ## -service: - type: ClusterIP - port: 2181 - followerPort: 2888 - electionPort: 3888 - publishNotReadyAddresses: true - tls: - client_enable: false - quorum_enable: false - disable_base_client_port: false - - client_port: 3181 - - client_keystore_path: /tls_key_store/key_store_file - client_keystore_password: "" - client_truststore_path: /tls_trust_store/trust_store_file - client_truststore_password: "" - - quorum_keystore_path: /tls_key_store/key_store_file - quorum_keystore_password: "" - quorum_truststore_path: /tls_trust_store/trust_store_file - quorum_truststore_password: "" - annotations: {} - headless: - annotations: {} - -## Service account for Zookeeper to use. -## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ +extraEnvVars: [] +## @param extraEnvVarsCM Name of existing ConfigMap containing extra env vars for ZooKeeper nodes ## -serviceAccount: - ## Specifies whether a ServiceAccount should be created - ## - create: false - ## The name of the ServiceAccount to use. - ## If not set and create is true, a name is generated using the zookeeper.fullname template - # name: - -## Zookeeper Pod Security Context -securityContext: - enabled: true - fsGroup: 1001 - runAsUser: 1001 - -## Zookeeper data Persistent Volume Storage Class -## If defined, storageClassName: <storageClass> -## If set to "-", storageClassName: "", which disables dynamic provisioning -## If undefined (the default) or set to null, no storageClassName spec is -## set, choosing the default provisioner. (gp2 on AWS, standard on -## GKE, AWS & OpenStack) +extraEnvVarsCM: "" +## @param extraEnvVarsSecret Name of existing Secret containing extra env vars for ZooKeeper nodes ## -persistence: - ## A manually managed Persistent Volume and Claim - ## If defined, PVC must be created manually before volume will be bound - ## The value is evaluated as a template - ## - # existingClaim: - - enabled: true - # storageClass: "-" - accessModes: - - ReadWriteOnce - size: 8Gi - annotations: {} - dataLogDir: - size: 8Gi - ## A manually managed Persistent Volume and Claim - ## If defined, PVC must be created manually before volume will be bound - ## The value is evaluated as a template - ## - # existingClaim: - - -## Node labels for pod assignment -## Ref: https://kubernetes.io/docs/user-guide/node-selection/ +extraEnvVarsSecret: "" +## @param command Override default container command (useful when using custom images) ## -nodeSelector: {} - -## Tolerations for pod assignment -## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +command: + - /scripts/setup.sh +## @param args Override default container args (useful when using custom images) ## -tolerations: [] +args: [] -## Labels -## Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ -podLabels: {} +## @section Statefulset parameters -## Annotations -## Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ -podAnnotations: {} - -## Name of the priority class to be used by zookeeper pods, priority class needs to be created beforehand -## Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ -priorityClassName: "" - -## Affinity for pod assignment -## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +## @param replicaCount Number of ZooKeeper nodes ## -affinity: {} - -## Scheduler name -## https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ +replicaCount: 1 +## @param containerPorts.client ZooKeeper client container port +## @param containerPorts.tls ZooKeeper TLS container port +## @param containerPorts.follower ZooKeeper follower container port +## @param containerPorts.election ZooKeeper election container port ## -# schedulerName: stork - -## Configure resource requests and limits -## ref: http://kubernetes.io/docs/user-guide/compute-resources/ -## -resources: - requests: - memory: 256Mi - cpu: 250m - -## Configure extra options for liveness and readiness probes -## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) +containerPorts: + client: 2181 + tls: 3181 + follower: 2888 + election: 3888 +## Configure extra options for ZooKeeper containers' liveness, readiness and startup probes +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes +## @param livenessProbe.enabled Enable livenessProbe on ZooKeeper containers +## @param livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe +## @param livenessProbe.periodSeconds Period seconds for livenessProbe +## @param livenessProbe.timeoutSeconds Timeout seconds for livenessProbe +## @param livenessProbe.failureThreshold Failure threshold for livenessProbe +## @param livenessProbe.successThreshold Success threshold for livenessProbe +## @param livenessProbe.probeCommandTimeout Probe command timeout for livenessProbe ## livenessProbe: enabled: true @@ -324,7 +266,14 @@ livenessProbe: failureThreshold: 6 successThreshold: 1 probeCommandTimeout: 2 - +## @param readinessProbe.enabled Enable readinessProbe on ZooKeeper containers +## @param readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe +## @param readinessProbe.periodSeconds Period seconds for readinessProbe +## @param readinessProbe.timeoutSeconds Timeout seconds for readinessProbe +## @param readinessProbe.failureThreshold Failure threshold for readinessProbe +## @param readinessProbe.successThreshold Success threshold for readinessProbe +## @param readinessProbe.probeCommandTimeout Probe command timeout for readinessProbe +## readinessProbe: enabled: true initialDelaySeconds: 5 @@ -333,98 +282,598 @@ readinessProbe: failureThreshold: 6 successThreshold: 1 probeCommandTimeout: 2 +## @param startupProbe.enabled Enable startupProbe on ZooKeeper containers +## @param startupProbe.initialDelaySeconds Initial delay seconds for startupProbe +## @param startupProbe.periodSeconds Period seconds for startupProbe +## @param startupProbe.timeoutSeconds Timeout seconds for startupProbe +## @param startupProbe.failureThreshold Failure threshold for startupProbe +## @param startupProbe.successThreshold Success threshold for startupProbe +## +startupProbe: + enabled: false + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 15 + successThreshold: 1 +## @param customLivenessProbe Custom livenessProbe that overrides the default one +## +customLivenessProbe: {} +## @param customReadinessProbe Custom readinessProbe that overrides the default one +## +customReadinessProbe: {} +## @param customStartupProbe Custom startupProbe that overrides the default one +## +customStartupProbe: {} +## @param lifecycleHooks for the ZooKeeper container(s) to automate configuration before or after startup +## +lifecycleHooks: {} +## ZooKeeper resource requests and limits +## ref: https://kubernetes.io/docs/user-guide/compute-resources/ +## @param resources.limits The resources limits for the ZooKeeper containers +## @param resources.requests.memory The requested memory for the ZooKeeper containers +## @param resources.requests.cpu The requested cpu for the ZooKeeper containers +## +resources: + limits: {} + requests: + memory: 256Mi + cpu: 250m +## Configure Pods Security Context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod +## @param podSecurityContext.enabled Enabled ZooKeeper pods' Security Context +## @param podSecurityContext.fsGroup Set ZooKeeper pod's Security Context fsGroup +## +podSecurityContext: + enabled: true + fsGroup: 1001 +## Configure Container Security Context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container +## @param containerSecurityContext.enabled Enabled ZooKeeper containers' Security Context +## @param containerSecurityContext.runAsUser Set ZooKeeper containers' Security Context runAsUser +## @param containerSecurityContext.runAsNonRoot Set ZooKeeper containers' Security Context runAsNonRoot +## @param containerSecurityContext.allowPrivilegeEscalation Force the child process to be run as nonprivilege +## +containerSecurityContext: + enabled: true + runAsUser: 1001 + runAsNonRoot: true + allowPrivilegeEscalation: false +## @param hostAliases ZooKeeper pods host aliases +## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ +## +hostAliases: [] +## @param podLabels Extra labels for ZooKeeper pods +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +## +podLabels: {} +## @param podAnnotations Annotations for ZooKeeper pods +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +## +podAnnotations: {} +## @param podAffinityPreset Pod affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` +## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity +## +podAffinityPreset: "" +## @param podAntiAffinityPreset Pod anti-affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` +## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity +## +podAntiAffinityPreset: soft +## Node affinity preset +## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity +## +nodeAffinityPreset: + ## @param nodeAffinityPreset.type Node affinity preset type. Ignored if `affinity` is set. Allowed values: `soft` or `hard` + ## + type: "" + ## @param nodeAffinityPreset.key Node label key to match Ignored if `affinity` is set. + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## @param nodeAffinityPreset.values Node label values to match. Ignored if `affinity` is set. + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] +## @param affinity Affinity for pod assignment +## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +## Note: podAffinityPreset, podAntiAffinityPreset, and nodeAffinityPreset will be ignored when it's set +## +affinity: {} +## @param nodeSelector Node labels for pod assignment +## Ref: https://kubernetes.io/docs/user-guide/node-selection/ +## +nodeSelector: {} +## @param tolerations Tolerations for pod assignment +## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +## +tolerations: [] +## @param topologySpreadConstraints Topology Spread Constraints for pod assignment spread across your cluster among failure-domains. Evaluated as a template +## Ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/#spread-constraints-for-pods +## +topologySpreadConstraints: [] +## @param podManagementPolicy StatefulSet controller supports relax its ordering guarantees while preserving its uniqueness and identity guarantees. There are two valid pod management policies: `OrderedReady` and `Parallel` +## ref: https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#pod-management-policy +## +podManagementPolicy: Parallel +## @param priorityClassName Name of the existing priority class to be used by ZooKeeper pods, priority class needs to be created beforehand +## Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ +## +priorityClassName: "" +## @param schedulerName Kubernetes pod scheduler registry +## https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ +## +schedulerName: "" +## @param updateStrategy.type ZooKeeper statefulset strategy type +## @param updateStrategy.rollingUpdate ZooKeeper statefulset rolling update configuration parameters +## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies +## +updateStrategy: + type: RollingUpdate + rollingUpdate: {} +## @param extraVolumes Optionally specify extra list of additional volumes for the ZooKeeper pod(s) +## Example Use Case: mount certificates to enable TLS +## e.g: +## extraVolumes: +## - name: zookeeper-keystore +## secret: +## defaultMode: 288 +## secretName: zookeeper-keystore +## - name: zookeeper-truststore +## secret: +## defaultMode: 288 +## secretName: zookeeper-truststore +## +extraVolumes: [] +## @param extraVolumeMounts Optionally specify extra list of additional volumeMounts for the ZooKeeper container(s) +## Example Use Case: mount certificates to enable TLS +## e.g: +## extraVolumeMounts: +## - name: zookeeper-keystore +## mountPath: /certs/keystore +## readOnly: true +## - name: zookeeper-truststore +## mountPath: /certs/truststore +## readOnly: true +## +extraVolumeMounts: [] +## @param sidecars Add additional sidecar containers to the ZooKeeper pod(s) +## e.g: +## sidecars: +## - name: your-image-name +## image: your-image +## imagePullPolicy: Always +## ports: +## - name: portname +## containerPort: 1234 +## +sidecars: [] +## @param initContainers Add additional init containers to the ZooKeeper pod(s) +## Example: +## initContainers: +## - name: your-image-name +## image: your-image +## imagePullPolicy: Always +## ports: +## - name: portname +## containerPort: 1234 +## +initContainers: [] +## ZooKeeper Pod Disruption Budget +## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ +## @param pdb.create Deploy a pdb object for the ZooKeeper pod +## @param pdb.minAvailable Minimum available ZooKeeper replicas +## @param pdb.maxUnavailable Maximum unavailable ZooKeeper replicas +## +pdb: + create: false + minAvailable: "" + maxUnavailable: 1 +## @section Traffic Exposure parameters + +service: + ## @param service.type Kubernetes Service type + ## + type: ClusterIP + ## @param service.ports.client ZooKeeper client service port + ## @param service.ports.tls ZooKeeper TLS service port + ## @param service.ports.follower ZooKeeper follower service port + ## @param service.ports.election ZooKeeper election service port + ## + ports: + client: 2181 + tls: 3181 + follower: 2888 + election: 3888 + ## Node ports to expose + ## NOTE: choose port between <30000-32767> + ## @param service.nodePorts.client Node port for clients + ## @param service.nodePorts.tls Node port for TLS + ## + nodePorts: + client: "" + tls: "" + ## @param service.disableBaseClientPort Remove client port from service definitions. + ## + disableBaseClientPort: false + ## @param service.sessionAffinity Control where client requests go, to the same pod or round-robin + ## Values: ClientIP or None + ## ref: https://kubernetes.io/docs/user-guide/services/ + ## + sessionAffinity: None + ## @param service.sessionAffinityConfig Additional settings for the sessionAffinity + ## sessionAffinityConfig: + ## clientIP: + ## timeoutSeconds: 300 + ## + sessionAffinityConfig: {} + ## @param service.clusterIP ZooKeeper service Cluster IP + ## e.g.: + ## clusterIP: None + ## + clusterIP: "" + ## @param service.loadBalancerIP ZooKeeper service Load Balancer IP + ## ref: https://kubernetes.io/docs/user-guide/services/#type-loadbalancer + ## + loadBalancerIP: "" + ## @param service.loadBalancerSourceRanges ZooKeeper service Load Balancer sources + ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## e.g: + ## loadBalancerSourceRanges: + ## - 10.10.10.0/24 + ## + loadBalancerSourceRanges: [] + ## @param service.externalTrafficPolicy ZooKeeper service external traffic policy + ## ref https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip + ## + externalTrafficPolicy: Cluster + ## @param service.annotations Additional custom annotations for ZooKeeper service + ## + annotations: {} + ## @param service.extraPorts Extra ports to expose in the ZooKeeper service (normally used with the `sidecar` value) + ## + extraPorts: [] + ## @param service.headless.annotations Annotations for the Headless Service + ## @param service.headless.publishNotReadyAddresses If the ZooKeeper headless service should publish DNS records for not ready pods + ## @param service.headless.servicenameOverride String to partially override headless service name + ## + headless: + publishNotReadyAddresses: true + annotations: {} + servicenameOverride: "" ## Network policies ## Ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ ## networkPolicy: - ## Specifies whether a NetworkPolicy should be created + ## @param networkPolicy.enabled Specifies whether a NetworkPolicy should be created ## enabled: false - - ## The Policy model to apply. When set to false, only pods with the correct - ## client label will have network access to the port Redis is listening - ## on. When true, zookeeper accept connections from any source - ## (with the correct destination port). + ## @param networkPolicy.allowExternal Don't require client label for connections + ## When set to false, only pods with the correct client label will have network access to the port Redis® is + ## listening on. When true, zookeeper accept connections from any source (with the correct destination port). ## - # allowExternal: true + allowExternal: true -## Zookeeper Prometheus Exporter configuration +## @section Other Parameters + +## Service account for ZooKeeper to use. +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ +## +serviceAccount: + ## @param serviceAccount.create Enable creation of ServiceAccount for ZooKeeper pod + ## + create: false + ## @param serviceAccount.name The name of the ServiceAccount to use. + ## If not set and create is true, a name is generated using the common.names.fullname template + ## + name: "" + ## @param serviceAccount.automountServiceAccountToken Allows auto mount of ServiceAccountToken on the serviceAccount created + ## Can be set to false if pods using this serviceAccount do not need to use K8s API + ## + automountServiceAccountToken: true + ## @param serviceAccount.annotations Additional custom annotations for the ServiceAccount + ## + annotations: {} + +## @section Persistence parameters + +## Enable persistence using Persistent Volume Claims +## ref: https://kubernetes.io/docs/user-guide/persistent-volumes/ +## +persistence: + ## @param persistence.enabled Enable ZooKeeper data persistence using PVC. If false, use emptyDir + ## + enabled: true + ## @param persistence.existingClaim Name of an existing PVC to use (only when deploying a single replica) + ## + existingClaim: "" + ## @param persistence.storageClass PVC Storage Class for ZooKeeper data volume + ## If defined, storageClassName: <storageClass> + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + storageClass: "" + ## @param persistence.accessModes PVC Access modes + ## + accessModes: + - ReadWriteOnce + ## @param persistence.size PVC Storage Request for ZooKeeper data volume + ## + size: 8Gi + ## @param persistence.annotations Annotations for the PVC + ## + annotations: {} + ## @param persistence.labels Labels for the PVC + ## + labels: {} + ## @param persistence.selector Selector to match an existing Persistent Volume for ZooKeeper's data PVC + ## If set, the PVC can't have a PV dynamically provisioned for it + ## E.g. + ## selector: + ## matchLabels: + ## app: my-app + ## + selector: {} + ## Persistence for a dedicated data log directory + ## + dataLogDir: + ## @param persistence.dataLogDir.size PVC Storage Request for ZooKeeper's dedicated data log directory + ## + size: 8Gi + ## @param persistence.dataLogDir.existingClaim Provide an existing `PersistentVolumeClaim` for ZooKeeper's data log directory + ## If defined, PVC must be created manually before volume will be bound + ## The value is evaluated as a template + ## + existingClaim: "" + ## @param persistence.dataLogDir.selector Selector to match an existing Persistent Volume for ZooKeeper's data log PVC + ## If set, the PVC can't have a PV dynamically provisioned for it + ## E.g. + ## selector: + ## matchLabels: + ## app: my-app + ## + selector: {} + +## @section Volume Permissions parameters +## + +## Init containers parameters: +## volumePermissions: Change the owner and group of the persistent volume(s) mountpoint(s) to 'runAsUser:fsGroup' on each node +## +volumePermissions: + ## @param volumePermissions.enabled Enable init container that changes the owner and group of the persistent volume + ## + enabled: false + ## @param volumePermissions.image.registry Init container volume-permissions image registry + ## @param volumePermissions.image.repository Init container volume-permissions image repository + ## @param volumePermissions.image.tag Init container volume-permissions image tag (immutable tags are recommended) + ## @param volumePermissions.image.digest Init container volume-permissions image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag + ## @param volumePermissions.image.pullPolicy Init container volume-permissions image pull policy + ## @param volumePermissions.image.pullSecrets Init container volume-permissions image pull secrets + ## + image: + registry: docker.io + repository: bitnami/bitnami-shell + tag: 11-debian-11-r69 + digest: "" + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## Example: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## Init container resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param volumePermissions.resources.limits Init container volume-permissions resource limits + ## @param volumePermissions.resources.requests Init container volume-permissions resource requests + ## + resources: + limits: {} + requests: {} + ## Init container' Security Context + ## Note: the chown of the data folder is done to containerSecurityContext.runAsUser + ## and not the below volumePermissions.containerSecurityContext.runAsUser + ## @param volumePermissions.containerSecurityContext.enabled Enabled init container Security Context + ## @param volumePermissions.containerSecurityContext.runAsUser User ID for the init container + ## + containerSecurityContext: + enabled: true + runAsUser: 0 + +## @section Metrics parameters +## + +## ZooKeeper Prometheus Exporter configuration ## metrics: + ## @param metrics.enabled Enable Prometheus to access ZooKeeper metrics endpoint + ## enabled: false - - ## Zookeeper Prometheus Exporter container port + ## @param metrics.containerPort ZooKeeper Prometheus Exporter container port ## containerPort: 9141 - ## Service configuration ## service: - ## Zookeeper Prometheus Exporter service type + ## @param metrics.service.type ZooKeeper Prometheus Exporter service type ## type: ClusterIP - ## Zookeeper Prometheus Exporter service port + ## @param metrics.service.port ZooKeeper Prometheus Exporter service port ## port: 9141 - ## Annotations for the Zookeeper Prometheus Exporter metrics service + ## @param metrics.service.annotations [object] Annotations for Prometheus to auto-discover the metrics endpoint ## annotations: prometheus.io/scrape: "true" prometheus.io/port: "{{ .Values.metrics.service.port }}" prometheus.io/path: "/metrics" - ## Prometheus Operator ServiceMonitor configuration ## serviceMonitor: + ## @param metrics.serviceMonitor.enabled Create ServiceMonitor Resource for scraping metrics using Prometheus Operator + ## enabled: false - ## Namespace for the ServiceMonitor Resource (defaults to the Release Namespace) + ## @param metrics.serviceMonitor.namespace Namespace for the ServiceMonitor Resource (defaults to the Release Namespace) ## - namespace: - - ## Interval at which metrics should be scraped. + namespace: "" + ## @param metrics.serviceMonitor.interval Interval at which metrics should be scraped. ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#endpoint ## - # interval: 10s - - ## Timeout after which the scrape is ended + interval: "" + ## @param metrics.serviceMonitor.scrapeTimeout Timeout after which the scrape is ended ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#endpoint ## - # scrapeTimeout: 10s - - ## ServiceMonitor selector labels - ## ref: https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#prometheus-configuration + scrapeTimeout: "" + ## @param metrics.serviceMonitor.additionalLabels Additional labels that can be used so ServiceMonitor will be discovered by Prometheus ## - # selector: - # prometheus: my-prometheus - + additionalLabels: {} + ## @param metrics.serviceMonitor.selector Prometheus instance selector labels + ## ref: https://github.com/bitnami/charts/tree/main/bitnami/prometheus-operator#prometheus-configuration + ## + selector: {} + ## @param metrics.serviceMonitor.relabelings RelabelConfigs to apply to samples before scraping + ## + relabelings: [] + ## @param metrics.serviceMonitor.metricRelabelings MetricRelabelConfigs to apply to samples before ingestion + ## + metricRelabelings: [] + ## @param metrics.serviceMonitor.honorLabels Specify honorLabels parameter to add the scrape endpoint + ## + honorLabels: false + ## @param metrics.serviceMonitor.jobLabel The name of the label on the target service to use as the job name in prometheus. + ## + jobLabel: "" ## Prometheus Operator PrometheusRule configuration ## prometheusRule: + ## @param metrics.prometheusRule.enabled Create a PrometheusRule for Prometheus Operator + ## enabled: false - ## Namespace for the PrometheusRule Resource (defaults to the Release Namespace) + ## @param metrics.prometheusRule.namespace Namespace for the PrometheusRule Resource (defaults to the Release Namespace) ## - namespace: - - ## PrometheusRule selector labels - ## ref: https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#prometheus-configuration + namespace: "" + ## @param metrics.prometheusRule.additionalLabels Additional labels that can be used so PrometheusRule will be discovered by Prometheus + ## + additionalLabels: {} + ## @param metrics.prometheusRule.rules PrometheusRule definitions + ## - alert: ZooKeeperSyncedFollowers + ## annotations: + ## message: The number of synced followers for the leader node in ZooKeeper deployment my-release is less than 2. This usually means that some of the ZooKeeper nodes aren't communicating properly. If it doesn't resolve itself you can try killing the pods (one by one). + ## expr: max(synced_followers{service="my-release-metrics"}) < 2 + ## for: 5m + ## labels: + ## severity: critical + ## - alert: ZooKeeperOutstandingRequests + ## annotations: + ## message: The number of outstanding requests for ZooKeeper pod {{ $labels.pod }} is greater than 10. This can indicate a performance issue with the Pod or cluster a whole. + ## expr: outstanding_requests{service="my-release-metrics"} > 10 + ## for: 5m + ## labels: + ## severity: critical ## - # selector: - # prometheus: my-prometheus - - ## Some example rules. rules: [] - # - alert: ZookeeperSyncedFollowers - # annotations: - # message: The number of synced followers for the leader node in Zookeeper deployment my-release is less than 2. This usually means that some of the Zookeeper nodes aren't communicating properly. If it doesn't resolve itself you can try killing the pods (one by one). - # expr: max(synced_followers{service="my-release-metrics"}) < 2 - # for: 5m - # labels: - # severity: critical - # - alert: ZookeeperOutstandingRequests - # annotations: - # message: The number of outstanding requests for Zookeeper pod {{ $labels.pod }} is greater than 10. This can indicate a performance issue with the Pod or cluster a whole. - # expr: outstanding_requests{service="my-release-metrics"} > 10 - # for: 5m - # labels: - # severity: critical + +## @section TLS/SSL parameters +## + +## Enable SSL/TLS encryption +## +tls: + client: + ## @param tls.client.enabled Enable TLS for client connections + ## + enabled: false + ## @param tls.client.auth SSL Client auth. Can be "none", "want" or "need". + ## + auth: "none" + ## @param tls.client.autoGenerated Generate automatically self-signed TLS certificates for ZooKeeper client communications + ## Currently only supports PEM certificates + ## + autoGenerated: false + ## @param tls.client.existingSecret Name of the existing secret containing the TLS certificates for ZooKeeper client communications + ## + existingSecret: "" + ## @param tls.client.existingSecretKeystoreKey The secret key from the tls.client.existingSecret containing the Keystore. + ## + existingSecretKeystoreKey: "" + ## @param tls.client.existingSecretTruststoreKey The secret key from the tls.client.existingSecret containing the Truststore. + ## + existingSecretTruststoreKey: "" + ## @param tls.client.keystorePath Location of the KeyStore file used for Client connections + ## + keystorePath: /opt/bitnami/zookeeper/config/certs/client/zookeeper.keystore.jks + ## @param tls.client.truststorePath Location of the TrustStore file used for Client connections + ## + truststorePath: /opt/bitnami/zookeeper/config/certs/client/zookeeper.truststore.jks + ## @param tls.client.passwordsSecretName Existing secret containing Keystore and truststore passwords + ## + passwordsSecretName: "" + ## @param tls.client.passwordsSecretKeystoreKey The secret key from the tls.client.passwordsSecretName containing the password for the Keystore. + ## + passwordsSecretKeystoreKey: "" + ## @param tls.client.passwordsSecretTruststoreKey The secret key from the tls.client.passwordsSecretName containing the password for the Truststore. + ## + passwordsSecretTruststoreKey: "" + ## @param tls.client.keystorePassword Password to access KeyStore if needed + ## + keystorePassword: "" + ## @param tls.client.truststorePassword Password to access TrustStore if needed + ## + truststorePassword: "" + quorum: + ## @param tls.quorum.enabled Enable TLS for quorum protocol + ## + enabled: false + ## @param tls.quorum.auth SSL Quorum Client auth. Can be "none", "want" or "need". + ## + auth: "none" + ## @param tls.quorum.autoGenerated Create self-signed TLS certificates. Currently only supports PEM certificates. + ## + autoGenerated: false + ## @param tls.quorum.existingSecret Name of the existing secret containing the TLS certificates for ZooKeeper quorum protocol + ## + existingSecret: "" + ## @param tls.quorum.existingSecretKeystoreKey The secret key from the tls.quorum.existingSecret containing the Keystore. + ## + existingSecretKeystoreKey: "" + ## @param tls.quorum.existingSecretTruststoreKey The secret key from the tls.quorum.existingSecret containing the Truststore. + ## + existingSecretTruststoreKey: "" + ## @param tls.quorum.keystorePath Location of the KeyStore file used for Quorum protocol + ## + keystorePath: /opt/bitnami/zookeeper/config/certs/quorum/zookeeper.keystore.jks + ## @param tls.quorum.truststorePath Location of the TrustStore file used for Quorum protocol + ## + truststorePath: /opt/bitnami/zookeeper/config/certs/quorum/zookeeper.truststore.jks + ## @param tls.quorum.passwordsSecretName Existing secret containing Keystore and truststore passwords + ## + passwordsSecretName: "" + ## @param tls.quorum.passwordsSecretKeystoreKey The secret key from the tls.quorum.passwordsSecretName containing the password for the Keystore. + ## + passwordsSecretKeystoreKey: "" + ## @param tls.quorum.passwordsSecretTruststoreKey The secret key from the tls.quorum.passwordsSecretName containing the password for the Truststore. + ## + passwordsSecretTruststoreKey: "" + ## @param tls.quorum.keystorePassword Password to access KeyStore if needed + ## + keystorePassword: "" + ## @param tls.quorum.truststorePassword Password to access TrustStore if needed + ## + truststorePassword: "" + ## Init container resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param tls.resources.limits The resources limits for the TLS init container + ## @param tls.resources.requests The requested resources for the TLS init container + ## + resources: + limits: {} + requests: {} diff --git a/scripts/helmcharts/databases/charts/kafka/files/jks/README.md b/scripts/helmcharts/databases/charts/kafka/files/jks/README.md deleted file mode 100755 index e110a8825..000000000 --- a/scripts/helmcharts/databases/charts/kafka/files/jks/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Java Key Stores - -You can copy here your Java Key Stores (JKS) files so a secret is created including them. Remember to use a truststore (`kafka.truststore.jks`) and one keystore (`kafka.keystore.jks`) per Kafka broker you have in the cluster. For instance, if you have 3 brokers you need to copy here the following files: - -- kafka.truststore.jks -- kafka-0.keystore.jks -- kafka-1.keystore.jks -- kafka-2.keystore.jks - -Find more info in [this section](https://github.com/bitnami/charts/tree/master/bitnami/kafka#enable-security-for-kafka-and-zookeeper) of the README.md file. diff --git a/scripts/helmcharts/databases/charts/kafka/kafka.yaml b/scripts/helmcharts/databases/charts/kafka/kafka.yaml deleted file mode 100644 index acd718957..000000000 --- a/scripts/helmcharts/databases/charts/kafka/kafka.yaml +++ /dev/null @@ -1,521 +0,0 @@ ---- -# Source: kafka/templates/serviceaccount.yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - name: kafka - labels: - app.kubernetes.io/name: kafka - helm.sh/chart: kafka-11.8.6 - app.kubernetes.io/instance: kafka - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/component: kafka ---- -# Source: kafka/templates/scripts-configmap.yaml -apiVersion: v1 -kind: ConfigMap -metadata: - name: kafka-scripts - labels: - app.kubernetes.io/name: kafka - helm.sh/chart: kafka-11.8.6 - app.kubernetes.io/instance: kafka - app.kubernetes.io/managed-by: Helm -data: - setup.sh: |- - #!/bin/bash - - ID="${MY_POD_NAME#"kafka-"}" - export KAFKA_CFG_BROKER_ID="$ID" - - exec /entrypoint.sh /run.sh ---- -# Source: kafka/charts/zookeeper/templates/svc-headless.yaml -apiVersion: v1 -kind: Service -metadata: - name: kafka-zookeeper-headless - namespace: db - labels: - app.kubernetes.io/name: zookeeper - helm.sh/chart: zookeeper-5.21.9 - app.kubernetes.io/instance: kafka - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/component: zookeeper -spec: - type: ClusterIP - clusterIP: None - publishNotReadyAddresses: true - ports: - - - name: tcp-client - port: 2181 - targetPort: client - - - - name: follower - port: 2888 - targetPort: follower - - name: tcp-election - port: 3888 - targetPort: election - selector: - app.kubernetes.io/name: zookeeper - app.kubernetes.io/instance: kafka - app.kubernetes.io/component: zookeeper ---- -# Source: kafka/charts/zookeeper/templates/svc.yaml -apiVersion: v1 -kind: Service -metadata: - name: kafka-zookeeper - namespace: db - labels: - app.kubernetes.io/name: zookeeper - helm.sh/chart: zookeeper-5.21.9 - app.kubernetes.io/instance: kafka - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/component: zookeeper -spec: - type: ClusterIP - ports: - - - name: tcp-client - port: 2181 - targetPort: client - - - - name: follower - port: 2888 - targetPort: follower - - name: tcp-election - port: 3888 - targetPort: election - selector: - app.kubernetes.io/name: zookeeper - app.kubernetes.io/instance: kafka - app.kubernetes.io/component: zookeeper ---- -# Source: kafka/templates/kafka-metrics-svc.yaml -apiVersion: v1 -kind: Service -metadata: - name: kafka-metrics - labels: - app.kubernetes.io/name: kafka - helm.sh/chart: kafka-11.8.6 - app.kubernetes.io/instance: kafka - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/component: metrics - annotations: - - prometheus.io/path: /metrics - prometheus.io/port: '9308' - prometheus.io/scrape: "true" -spec: - type: ClusterIP - ports: - - name: http-metrics - port: 9308 - protocol: TCP - targetPort: metrics - nodePort: null - selector: - app.kubernetes.io/name: kafka - app.kubernetes.io/instance: kafka - app.kubernetes.io/component: metrics ---- -# Source: kafka/templates/svc-headless.yaml -apiVersion: v1 -kind: Service -metadata: - name: kafka-headless - labels: - app.kubernetes.io/name: kafka - helm.sh/chart: kafka-11.8.6 - app.kubernetes.io/instance: kafka - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/component: kafka -spec: - type: ClusterIP - clusterIP: None - ports: - - name: tcp-client - port: 9092 - protocol: TCP - targetPort: kafka-client - - name: tcp-internal - port: 9093 - protocol: TCP - targetPort: kafka-internal - selector: - app.kubernetes.io/name: kafka - app.kubernetes.io/instance: kafka - app.kubernetes.io/component: kafka ---- -# Source: kafka/templates/svc.yaml -apiVersion: v1 -kind: Service -metadata: - name: kafka - labels: - app.kubernetes.io/name: kafka - helm.sh/chart: kafka-11.8.6 - app.kubernetes.io/instance: kafka - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/component: kafka -spec: - type: ClusterIP - ports: - - name: tcp-client - port: 9092 - protocol: TCP - targetPort: kafka-client - nodePort: null - selector: - app.kubernetes.io/name: kafka - app.kubernetes.io/instance: kafka - app.kubernetes.io/component: kafka ---- -# Source: kafka/templates/kafka-metrics-deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: kafka-exporter - labels: - app.kubernetes.io/name: kafka - helm.sh/chart: kafka-11.8.6 - app.kubernetes.io/instance: kafka - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/component: metrics -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: kafka - app.kubernetes.io/instance: kafka - app.kubernetes.io/component: metrics - template: - metadata: - labels: - app.kubernetes.io/name: kafka - helm.sh/chart: kafka-11.8.6 - app.kubernetes.io/instance: kafka - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/component: metrics - spec: - containers: - - name: kafka-exporter - image: docker.io/bitnami/kafka-exporter:1.2.0-debian-10-r220 - imagePullPolicy: "IfNotPresent" - command: - - /bin/bash - - -ec - - | - read -r -a sasl_passwords <<< "$(tr ',;' ' ' <<< "${SASL_USER_PASSWORD}")" - kafka_exporter \ - --kafka.server=kafka-0.kafka-headless.db.svc.cluster.local:9092 \ - --kafka.server=kafka-1.kafka-headless.db.svc.cluster.local:9092 \ - --web.listen-address=:9308 - ports: - - name: metrics - containerPort: 9308 - resources: - limits: {} - requests: {} ---- -# Source: kafka/charts/zookeeper/templates/statefulset.yaml -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: kafka-zookeeper - namespace: db - labels: - app.kubernetes.io/name: zookeeper - helm.sh/chart: zookeeper-5.21.9 - app.kubernetes.io/instance: kafka - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/component: zookeeper - role: zookeeper -spec: - serviceName: kafka-zookeeper-headless - replicas: 1 - podManagementPolicy: Parallel - updateStrategy: - type: RollingUpdate - selector: - matchLabels: - app.kubernetes.io/name: zookeeper - app.kubernetes.io/instance: kafka - app.kubernetes.io/component: zookeeper - template: - metadata: - name: kafka-zookeeper - labels: - app.kubernetes.io/name: zookeeper - helm.sh/chart: zookeeper-5.21.9 - app.kubernetes.io/instance: kafka - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/component: zookeeper - spec: - - serviceAccountName: default - securityContext: - fsGroup: 1001 - containers: - - name: zookeeper - image: docker.io/bitnami/zookeeper:3.6.2-debian-10-r10 - imagePullPolicy: "IfNotPresent" - securityContext: - runAsUser: 1001 - command: - - bash - - -ec - - | - # Execute entrypoint as usual after obtaining ZOO_SERVER_ID based on POD hostname - HOSTNAME=`hostname -s` - if [[ $HOSTNAME =~ (.*)-([0-9]+)$ ]]; then - ORD=${BASH_REMATCH[2]} - export ZOO_SERVER_ID=$((ORD+1)) - else - echo "Failed to get index from hostname $HOST" - exit 1 - fi - exec /entrypoint.sh /run.sh - resources: - requests: - cpu: 250m - memory: 256Mi - env: - - name: ZOO_DATA_LOG_DIR - value: "" - - name: ZOO_PORT_NUMBER - value: "2181" - - name: ZOO_TICK_TIME - value: "2000" - - name: ZOO_INIT_LIMIT - value: "10" - - name: ZOO_SYNC_LIMIT - value: "5" - - name: ZOO_MAX_CLIENT_CNXNS - value: "60" - - name: ZOO_4LW_COMMANDS_WHITELIST - value: "srvr, mntr, ruok" - - name: ZOO_LISTEN_ALLIPS_ENABLED - value: "no" - - name: ZOO_AUTOPURGE_INTERVAL - value: "0" - - name: ZOO_AUTOPURGE_RETAIN_COUNT - value: "3" - - name: ZOO_MAX_SESSION_TIMEOUT - value: "40000" - - name: ZOO_SERVERS - value: kafka-zookeeper-0.kafka-zookeeper-headless.db.svc.cluster.local:2888:3888 - - name: ZOO_ENABLE_AUTH - value: "no" - - name: ZOO_HEAP_SIZE - value: "1024" - - name: ZOO_LOG_LEVEL - value: "ERROR" - - name: ALLOW_ANONYMOUS_LOGIN - value: "yes" - - name: POD_NAME - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.name - ports: - - - name: client - containerPort: 2181 - - - - name: follower - containerPort: 2888 - - name: election - containerPort: 3888 - livenessProbe: - exec: - command: ['/bin/bash', '-c', 'echo "ruok" | timeout 2 nc -w 2 localhost 2181 | grep imok'] - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 6 - readinessProbe: - exec: - command: ['/bin/bash', '-c', 'echo "ruok" | timeout 2 nc -w 2 localhost 2181 | grep imok'] - initialDelaySeconds: 5 - periodSeconds: 10 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 6 - volumeMounts: - - name: data - mountPath: /bitnami/zookeeper - volumes: - volumeClaimTemplates: - - metadata: - name: data - annotations: - spec: - accessModes: - - "ReadWriteOnce" - resources: - requests: - storage: "8Gi" ---- -# Source: kafka/templates/statefulset.yaml -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: kafka - labels: - app.kubernetes.io/name: kafka - helm.sh/chart: kafka-11.8.6 - app.kubernetes.io/instance: kafka - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/component: kafka -spec: - podManagementPolicy: Parallel - replicas: 2 - selector: - matchLabels: - app.kubernetes.io/name: kafka - app.kubernetes.io/instance: kafka - app.kubernetes.io/component: kafka - serviceName: kafka-headless - updateStrategy: - type: "RollingUpdate" - template: - metadata: - labels: - app.kubernetes.io/name: kafka - helm.sh/chart: kafka-11.8.6 - app.kubernetes.io/instance: kafka - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/component: kafka - spec: - securityContext: - fsGroup: 1001 - runAsUser: 1001 - serviceAccountName: kafka - containers: - - name: kafka - image: docker.io/bitnami/kafka:2.6.0-debian-10-r30 - imagePullPolicy: "IfNotPresent" - command: - - /scripts/setup.sh - env: - - name: BITNAMI_DEBUG - value: "false" - - name: MY_POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - - name: MY_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: KAFKA_CFG_ZOOKEEPER_CONNECT - value: "kafka-zookeeper" - - name: KAFKA_INTER_BROKER_LISTENER_NAME - value: "INTERNAL" - - name: KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP - value: "INTERNAL:PLAINTEXT,CLIENT:PLAINTEXT" - - name: KAFKA_CFG_LISTENERS - value: "INTERNAL://:9093,CLIENT://:9092" - - name: KAFKA_CFG_ADVERTISED_LISTENERS - value: "INTERNAL://$(MY_POD_NAME).kafka-headless.db.svc.cluster.local:9093,CLIENT://$(MY_POD_NAME).kafka-headless.db.svc.cluster.local:9092" - - name: ALLOW_PLAINTEXT_LISTENER - value: "yes" - - name: KAFKA_CFG_DELETE_TOPIC_ENABLE - value: "false" - - name: KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE - value: "true" - - name: KAFKA_HEAP_OPTS - value: "-Xmx1024m -Xms1024m" - - name: KAFKA_CFG_LOG_FLUSH_INTERVAL_MESSAGES - value: "10000" - - name: KAFKA_CFG_LOG_FLUSH_INTERVAL_MS - value: "1000" - - name: KAFKA_CFG_LOG_RETENTION_BYTES - value: "1073741824" - - name: KAFKA_CFG_LOG_RETENTION_CHECK_INTERVALS_MS - value: "300000" - - name: KAFKA_CFG_LOG_RETENTION_HOURS - value: "168" - - name: KAFKA_CFG_MESSAGE_MAX_BYTES - value: "1000012" - - name: KAFKA_CFG_LOG_SEGMENT_BYTES - value: "1073741824" - - name: KAFKA_CFG_LOG_DIRS - value: "/bitnami/kafka/data" - - name: KAFKA_CFG_DEFAULT_REPLICATION_FACTOR - value: "1" - - name: KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR - value: "1" - - name: KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR - value: "1" - - name: KAFKA_CFG_TRANSACTION_STATE_LOG_MIN_ISR - value: "1" - - name: KAFKA_CFG_NUM_IO_THREADS - value: "8" - - name: KAFKA_CFG_NUM_NETWORK_THREADS - value: "3" - - name: KAFKA_CFG_NUM_PARTITIONS - value: "1" - - name: KAFKA_CFG_NUM_RECOVERY_THREADS_PER_DATA_DIR - value: "1" - - name: KAFKA_CFG_SOCKET_RECEIVE_BUFFER_BYTES - value: "102400" - - name: KAFKA_CFG_SOCKET_REQUEST_MAX_BYTES - value: "104857600" - - name: KAFKA_CFG_SOCKET_SEND_BUFFER_BYTES - value: "102400" - - name: KAFKA_CFG_ZOOKEEPER_CONNECTION_TIMEOUT_MS - value: "6000" - ports: - - name: kafka-client - containerPort: 9092 - - name: kafka-internal - containerPort: 9093 - livenessProbe: - tcpSocket: - port: kafka-client - initialDelaySeconds: 10 - timeoutSeconds: 5 - failureThreshold: - periodSeconds: - successThreshold: - readinessProbe: - tcpSocket: - port: kafka-client - initialDelaySeconds: 5 - timeoutSeconds: 5 - failureThreshold: 6 - periodSeconds: - successThreshold: - resources: - limits: {} - requests: {} - volumeMounts: - - name: data - mountPath: /bitnami/kafka - - name: scripts - mountPath: /scripts/setup.sh - subPath: setup.sh - volumes: - - name: scripts - configMap: - name: kafka-scripts - defaultMode: 0755 - volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: - - "ReadWriteOnce" - resources: - requests: - storage: "8Gi" diff --git a/scripts/helmcharts/databases/charts/kafka/requirements.lock b/scripts/helmcharts/databases/charts/kafka/requirements.lock deleted file mode 100755 index 115d0b229..000000000 --- a/scripts/helmcharts/databases/charts/kafka/requirements.lock +++ /dev/null @@ -1,6 +0,0 @@ -dependencies: -- name: zookeeper - repository: https://charts.bitnami.com/bitnami - version: 5.21.9 -digest: sha256:2f3c43ce02e3966648b8c89be121fe39537f62ea1d161ad908f51ddc90e4243e -generated: "2020-09-29T07:43:56.483358254Z" diff --git a/scripts/helmcharts/databases/charts/kafka/requirements.yaml b/scripts/helmcharts/databases/charts/kafka/requirements.yaml deleted file mode 100755 index 533875258..000000000 --- a/scripts/helmcharts/databases/charts/kafka/requirements.yaml +++ /dev/null @@ -1,5 +0,0 @@ -dependencies: - - name: zookeeper - version: 5.x.x - repository: https://charts.bitnami.com/bitnami - condition: zookeeper.enabled diff --git a/scripts/helmcharts/databases/charts/kafka/templates/NOTES.txt b/scripts/helmcharts/databases/charts/kafka/templates/NOTES.txt old mode 100755 new mode 100644 index 0347c21c4..f34c2563f --- a/scripts/helmcharts/databases/charts/kafka/templates/NOTES.txt +++ b/scripts/helmcharts/databases/charts/kafka/templates/NOTES.txt @@ -1,43 +1,40 @@ -{{- $replicaCount := int .Values.replicaCount -}} -{{- $releaseNamespace := .Release.Namespace -}} -{{- $clusterDomain := .Values.clusterDomain -}} -{{- $fullname := include "kafka.fullname" . -}} -{{- $clientProtocol := include "kafka.listenerType" ( dict "protocol" .Values.auth.clientProtocol ) -}} -{{- $servicePort := int .Values.service.port -}} -{{- $loadBalancerIPListLength := len .Values.externalAccess.service.loadBalancerIPs -}} -{{- if and .Values.externalAccess.enabled (not .Values.externalAccess.autoDiscovery.enabled) (not (eq $replicaCount $loadBalancerIPListLength )) (eq .Values.externalAccess.service.type "LoadBalancer") }} +CHART NAME: {{ .Chart.Name }} +CHART VERSION: {{ .Chart.Version }} +APP VERSION: {{ .Chart.AppVersion }} -############################################################################### -### ERROR: You enabled external access to Kafka brokers without specifying ### -### the array of load balancer IPs for Kafka brokers. ### -############################################################################### +{{- if .Values.diagnosticMode.enabled }} +The chart has been deployed in diagnostic mode. All probes have been disabled and the command has been overwritten with: -This deployment will be incomplete until you configure the array of load balancer -IPs for Kafka brokers. To complete your deployment follow the steps below: + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 4 }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 4 }} -1. Wait for the load balancer IPs (it may take a few minutes for them to be available): +Get the list of pods by executing: - kubectl get svc --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ template "kafka.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=kafka,pod" -w + kubectl get pods --namespace {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} -2. Obtain the load balancer IPs and upgrade your chart: +Access the pod you want to debug by executing - {{- range $i, $e := until $replicaCount }} - LOAD_BALANCER_IP_{{ add $i 1 }}="$(kubectl get svc --namespace {{ $releaseNamespace }} {{ $fullname }}-{{ $i }}-external -o jsonpath='{.status.loadBalancer.ingress[0].ip}')" - {{- end }} + kubectl exec --namespace {{ .Release.Namespace }} -ti <NAME OF THE POD> -- bash -3. Upgrade you chart: +In order to replicate the container startup scripts execute this command: - helm upgrade {{ .Release.Name }} bitnami/{{ .Chart.Name }} \ - --set replicaCount={{ $replicaCount }} \ - --set externalAccess.enabled=true \ - {{- range $i, $e := until $replicaCount }} - --set externalAccess.service.loadBalancerIPs[{{ $i }}]=$LOAD_BALANCER_IP_{{ add $i 1 }} \ - {{- end }} - --set externalAccess.service.type=LoadBalancer + /opt/bitnami/scripts/kafka/entrypoint.sh /opt/bitnami/scripts/kafka/run.sh {{- else }} -{{- if and (or (eq .Values.service.type "LoadBalancer") .Values.externalAccess.enabled) (eq .Values.auth.clientProtocol "plaintext") }} +{{- $replicaCount := int .Values.replicaCount -}} +{{- $releaseNamespace := .Release.Namespace -}} +{{- $clusterDomain := .Values.clusterDomain -}} +{{- $fullname := include "common.names.fullname" . -}} +{{- $clientProtocol := include "kafka.listenerType" (dict "protocol" .Values.auth.clientProtocol) -}} +{{- $externalClientProtocol := include "kafka.listenerType" (dict "protocol" (include "kafka.externalClientProtocol" . )) -}} +{{- $saslMechanisms := .Values.auth.sasl.mechanisms -}} +{{- $tlsEndpointIdentificationAlgorithm := default "" .Values.auth.tls.endpointIdentificationAlgorithm -}} +{{- $tlsPasswordSecret := printf "$(kubectl get secret %s --namespace %s -o jsonpath='{.data.password}' | base64 -d | cut -d , -f 1)" .Values.auth.tls.existingSecret $releaseNamespace -}} +{{- $tlsPassword := ternary .Values.auth.tls.password $tlsPasswordSecret (eq .Values.auth.tls.existingSecret "") -}} +{{- $servicePort := int .Values.service.ports.client -}} + +{{- if and (or (eq .Values.service.type "LoadBalancer") .Values.externalAccess.enabled) (eq $externalClientProtocol "PLAINTEXT") }} --------------------------------------------------------------------------------------------- WARNING @@ -64,57 +61,100 @@ Each Kafka broker can be accessed by producers via port {{ $servicePort }} on th {{- $brokerList = append $brokerList (printf "%s-%d.%s-headless.%s.svc.%s:%d" $fullname $i $fullname $releaseNamespace $clusterDomain $servicePort) }} {{- end }} {{ join "\n" $brokerList | nindent 4 }} - - {{- if (include "kafka.client.saslAuthentication" .) }} -You need to configure your Kafka client to access using SASL authentication. To do so, you need to create the 'kafka_jaas.conf' and 'client.properties' configuration files by executing these commands: +You need to configure your Kafka client to access using SASL authentication. To do so, you need to create the 'kafka_jaas.conf' and 'client.properties' configuration files with the content below: - kafka_jaas.conf: -cat > kafka_jaas.conf <<EOF KafkaClient { -{{- if .Values.auth.saslMechanisms | regexFind "scram" }} +{{- if $saslMechanisms | regexFind "scram" }} org.apache.kafka.common.security.scram.ScramLoginModule required {{- else }} org.apache.kafka.common.security.plain.PlainLoginModule required {{- end }} -username="{{ index .Values.auth.jaas.clientUsers 0 }}" -password="$(kubectl get secret {{ $fullname }}-jaas -n {{ $releaseNamespace }} -o jsonpath='{.data.client-passwords}' | base64 --decode | cut -d , -f 1)"; +username="{{ index .Values.auth.sasl.jaas.clientUsers 0 }}" +password="$(kubectl get secret {{ $fullname }}-jaas --namespace {{ $releaseNamespace }} -o jsonpath='{.data.client-passwords}' | base64 -d | cut -d , -f 1)"; }; -EOF - client.properties: -cat > client.properties <<EOF security.protocol={{ $clientProtocol }} -{{- if .Values.auth.saslMechanisms | regexFind "scram-sha-256" }} +{{- if $saslMechanisms | regexFind "scram-sha-256" }} sasl.mechanism=SCRAM-SHA-256 -{{- else if .Values.auth.saslMechanisms | regexFind "scram-sha-512" }} +{{- else if $saslMechanisms | regexFind "scram-sha-512" }} sasl.mechanism=SCRAM-SHA-512 -{{- else -}} +{{- else }} sasl.mechanism=PLAIN {{- end }} -{{- if eq .Values.auth.clientProtocol "sasl_tls" }} +{{- if eq $clientProtocol "SASL_SSL" }} +ssl.truststore.type={{ upper .Values.auth.tls.type }} + {{- if eq .Values.auth.tls.type "jks" }} ssl.truststore.location=/tmp/kafka.truststore.jks -{{- if .Values.auth.jksPassword }} -ssl.truststore.password={{ .Values.auth.jksPassword }} + {{- if not (empty $tlsPassword) }} +ssl.truststore.password={{ $tlsPassword }} + {{- end }} + {{- else if eq .Values.auth.tls.type "pem" }} +ssl.truststore.certificates=-----BEGIN CERTIFICATE----- \ +... \ +-----END CERTIFICATE----- + {{- end }} + {{- if eq $tlsEndpointIdentificationAlgorithm "" }} +ssl.endpoint.identification.algorithm= + {{- end }} {{- end }} + +{{- else if (include "kafka.client.tlsEncryption" .) }} + +You need to configure your Kafka client to access using TLS authentication. To do so, you need to create the 'client.properties' configuration file with the content below: + +security.protocol={{ $clientProtocol }} +ssl.truststore.type={{ upper .Values.auth.tls.type }} +{{- if eq .Values.auth.tls.type "jks" }} +ssl.truststore.location=/tmp/kafka.truststore.{{ .Values.auth.tls.type }} + {{- if not (empty $tlsPassword) }} +ssl.truststore.password={{ $tlsPassword }} + {{- end }} +{{- else if eq .Values.auth.tls.type "pem" }} +ssl.truststore.certificates=-----BEGIN CERTIFICATE----- \ +... \ +-----END CERTIFICATE----- {{- end }} -{{- if eq .Values.auth.tlsEndpointIdentificationAlgorithm "" }} +{{- if eq .Values.auth.clientProtocol "mtls" }} +ssl.keystore.type={{ upper .Values.auth.tls.type }} + {{- if eq .Values.auth.tls.type "jks" }} +ssl.keystore.location=/tmp/client.keystore.jks + {{- if not (empty $tlsPassword) }} +ssl.keystore.password={{ $tlsPassword }} + {{- end }} + {{- else if eq .Values.auth.tls.type "pem" }} +ssl.keystore.certificate.chain=-----BEGIN CERTIFICATE----- \ +... \ +-----END CERTIFICATE----- +ssl.keystore.key=-----BEGIN ENCRYPTED PRIVATE KEY----- \ +... \ +-----END ENCRYPTED PRIVATE KEY----- + {{- end }} +{{- end }} +{{- if eq $tlsEndpointIdentificationAlgorithm "" }} ssl.endpoint.identification.algorithm= {{- end }} -EOF + {{- end }} To create a pod that you can use as a Kafka client run the following commands: kubectl run {{ $fullname }}-client --restart='Never' --image {{ template "kafka.image" . }} --namespace {{ $releaseNamespace }} --command -- sleep infinity - {{- if (include "kafka.client.saslAuthentication" .) }} + {{- if or (include "kafka.client.saslAuthentication" .) (include "kafka.client.tlsEncryption" .) }} kubectl cp --namespace {{ $releaseNamespace }} /path/to/client.properties {{ $fullname }}-client:/tmp/client.properties + {{- end }} + {{- if (include "kafka.client.saslAuthentication" .) }} kubectl cp --namespace {{ $releaseNamespace }} /path/to/kafka_jaas.conf {{ $fullname }}-client:/tmp/kafka_jaas.conf - {{- if eq .Values.auth.clientProtocol "sasl_tls" }} + {{- end }} + {{- if and (include "kafka.client.tlsEncryption" .) (eq .Values.auth.tls.type "jks") }} kubectl cp --namespace {{ $releaseNamespace }} ./kafka.truststore.jks {{ $fullname }}-client:/tmp/kafka.truststore.jks + {{- if eq .Values.auth.clientProtocol "mtls" }} + kubectl cp --namespace {{ $releaseNamespace }} ./client.keystore.jks {{ $fullname }}-client:/tmp/client.keystore.jks {{- end }} {{- end }} kubectl exec --tty -i {{ $fullname }}-client --namespace {{ $releaseNamespace }} -- bash @@ -124,14 +164,18 @@ To create a pod that you can use as a Kafka client run the following commands: PRODUCER: kafka-console-producer.sh \ - {{ if (include "kafka.client.saslAuthentication" .) }}--producer.config /tmp/client.properties \{{ end }} + {{- if or (include "kafka.client.saslAuthentication" .) (include "kafka.client.tlsEncryption" .) }} + --producer.config /tmp/client.properties \ + {{- end }} --broker-list {{ join "," $brokerList }} \ --topic test CONSUMER: kafka-console-consumer.sh \ - {{ if (include "kafka.client.saslAuthentication" .) }}--consumer.config /tmp/client.properties \{{ end }} - --bootstrap-server {{ $fullname }}.{{ $releaseNamespace }}.svc.{{ $clusterDomain }}:{{ .Values.service.port }} \ + {{- if or (include "kafka.client.saslAuthentication" .) (include "kafka.client.tlsEncryption" .) }} + --consumer.config /tmp/client.properties \ + {{- end }} + --bootstrap-server {{ $fullname }}.{{ $releaseNamespace }}.svc.{{ $clusterDomain }}:{{ .Values.service.ports.client }} \ --topic test \ --from-beginning @@ -171,8 +215,97 @@ To connect to your Kafka server from outside the cluster, follow the instruction echo "$(kubectl get svc --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ template "kafka.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=kafka,pod" -o jsonpath='{.items[*].status.loadBalancer.ingress[0].ip}' | tr ' ' '\n')" - Kafka Brokers port: {{ .Values.externalAccess.service.port }} + Kafka Brokers port: {{ .Values.externalAccess.service.ports.external }} +{{- else if eq "ClusterIP" .Values.externalAccess.service.type }} + + Kafka brokers domain: Use your provided hostname to reach Kafka brokers, {{ .Values.externalAccess.service.domain }} + + Kafka brokers port: You will have a different port for each Kafka broker starting at {{ .Values.externalAccess.service.ports.external }} + +{{- end }} + +{{- if not (eq $clientProtocol $externalClientProtocol) }} +{{- if (include "kafka.client.saslAuthentication" .) }} + +You need to configure your Kafka client to access using SASL authentication. To do so, you need to create the 'kafka_jaas.conf' and 'client.properties' configuration files with the content below: + + - kafka_jaas.conf: + +KafkaClient { +{{- if $saslMechanisms | regexFind "scram" }} +org.apache.kafka.common.security.scram.ScramLoginModule required +{{- else }} +org.apache.kafka.common.security.plain.PlainLoginModule required +{{- end }} +username="{{ index .Values.auth.sasl.jaas.clientUsers 0 }}" +password="$(kubectl get secret {{ $fullname }}-jaas --namespace {{ $releaseNamespace }} -o jsonpath='{.data.client-passwords}' | base64 -d | cut -d , -f 1)"; +}; + + - client.properties: + +security.protocol={{ $externalClientProtocol }} +{{- if $saslMechanisms | regexFind "scram-sha-256" }} +sasl.mechanism=SCRAM-SHA-256 +{{- else if $saslMechanisms | regexFind "scram-sha-512" }} +sasl.mechanism=SCRAM-SHA-512 +{{- else }} +sasl.mechanism=PLAIN +{{- end }} +{{- if eq $externalClientProtocol "SASL_SSL" }} +ssl.truststore.type={{ upper .Values.auth.tls.type }} + {{- if eq .Values.auth.tls.type "jks" }} +ssl.truststore.location=/tmp/kafka.truststore.jks + {{- if not (empty $tlsPassword) }} +ssl.truststore.password={{ $tlsPassword }} + {{- end }} + {{- else if eq .Values.auth.tls.type "pem" }} +ssl.truststore.certificates=-----BEGIN CERTIFICATE----- \ +... \ +-----END CERTIFICATE----- + {{- end }} + {{- if eq $tlsEndpointIdentificationAlgorithm "" }} +ssl.endpoint.identification.algorithm= + {{- end }} +{{- end }} + +{{- else if (include "kafka.externalClient.tlsEncryption" .) }} + +You need to configure your Kafka client to access using TLS authentication. To do so, you need to create the 'client.properties' configuration file with the content below: + +security.protocol={{ $externalClientProtocol }} +ssl.truststore.type={{ upper .Values.auth.tls.type }} +{{- if eq .Values.auth.tls.type "jks" }} +ssl.truststore.location=/tmp/kafka.truststore.{{ .Values.auth.tls.type }} + {{- if not (empty $tlsPassword) }} +ssl.truststore.password={{ $tlsPassword }} + {{- end }} +{{- else if eq .Values.auth.tls.type "pem" }} +ssl.truststore.certificates=-----BEGIN CERTIFICATE----- \ +... \ +-----END CERTIFICATE----- +{{- end }} +{{- if eq .Values.auth.externalClientProtocol "mtls" }} +ssl.keystore.type={{ upper .Values.auth.tls.type }} + {{- if eq .Values.auth.tls.type "jks" }} +ssl.keystore.location=/tmp/client.keystore.jks + {{- if not (empty $tlsPassword) }} +ssl.keystore.password={{ $tlsPassword }} + {{- end }} + {{- else if eq .Values.auth.tls.type "pem" }} +ssl.keystore.certificate.chain=-----BEGIN CERTIFICATE----- \ +... \ +-----END CERTIFICATE----- +ssl.keystore.key=-----BEGIN ENCRYPTED PRIVATE KEY----- \ +... \ +-----END ENCRYPTED PRIVATE KEY----- + {{- end }} +{{- end }} +{{- if eq $tlsEndpointIdentificationAlgorithm "" }} +ssl.endpoint.identification.algorithm= +{{- end }} + +{{- end }} {{- end }} {{- end }} {{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/_helpers.tpl b/scripts/helmcharts/databases/charts/kafka/templates/_helpers.tpl old mode 100755 new mode 100644 index 4dae244cb..51ec867d5 --- a/scripts/helmcharts/databases/charts/kafka/templates/_helpers.tpl +++ b/scripts/helmcharts/databases/charts/kafka/templates/_helpers.tpl @@ -1,4 +1,5 @@ {{/* vim: set filetype=mustache: */}} + {{/* Expand the name of the chart. */}} @@ -6,49 +7,6 @@ Expand the name of the chart. {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "kafka.fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- if contains $name .Release.Name -}} -{{- .Release.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "kafka.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Common labels -*/}} -{{- define "kafka.labels" -}} -app.kubernetes.io/name: {{ include "kafka.name" . }} -helm.sh/chart: {{ include "kafka.chart" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end -}} - -{{/* -Labels to use on deploy.spec.selector.matchLabels and svc.spec.selector -*/}} -{{- define "kafka.matchLabels" -}} -app.kubernetes.io/name: {{ include "kafka.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end -}} - {{/* Create a default fully qualified zookeeper name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). @@ -67,7 +25,7 @@ We truncate at 63 chars because some Kubernetes name fields are limited to this */}} {{- define "kafka.serviceAccountName" -}} {{- if .Values.serviceAccount.create -}} - {{ default (include "kafka.fullname" .) .Values.serviceAccount.name }} + {{ default (include "common.names.fullname" .) .Values.serviceAccount.name }} {{- else -}} {{ default "default" .Values.serviceAccount.name }} {{- end -}} @@ -77,68 +35,39 @@ We truncate at 63 chars because some Kubernetes name fields are limited to this Return the proper Kafka image name */}} {{- define "kafka.image" -}} -{{- $registryName := .Values.image.registry -}} -{{- $repositoryName := .Values.image.repository -}} -{{- $tag := .Values.image.tag | toString -}} -{{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. -Also, we can't use a single if because lazy evaluation is not an option -*/}} -{{- if .Values.global }} - {{- if .Values.global.imageRegistry }} - {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} - {{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} - {{- end -}} -{{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} -{{- end -}} +{{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }} {{- end -}} {{/* Return the proper image name (for the init container auto-discovery image) */}} {{- define "kafka.externalAccess.autoDiscovery.image" -}} -{{- $registryName := .Values.externalAccess.autoDiscovery.image.registry -}} -{{- $repositoryName := .Values.externalAccess.autoDiscovery.image.repository -}} -{{- $tag := .Values.externalAccess.autoDiscovery.image.tag | toString -}} -{{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. -Also, we can't use a single if because lazy evaluation is not an option -*/}} -{{- if .Values.global }} - {{- if .Values.global.imageRegistry }} - {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} - {{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} - {{- end -}} -{{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} -{{- end -}} +{{ include "common.images.image" (dict "imageRoot" .Values.externalAccess.autoDiscovery.image "global" .Values.global) }} {{- end -}} {{/* Return the proper image name (for the init container volume-permissions image) */}} {{- define "kafka.volumePermissions.image" -}} -{{- $registryName := .Values.volumePermissions.image.registry -}} -{{- $repositoryName := .Values.volumePermissions.image.repository -}} -{{- $tag := .Values.volumePermissions.image.tag | toString -}} +{{ include "common.images.image" (dict "imageRoot" .Values.volumePermissions.image "global" .Values.global) }} +{{- end -}} + {{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. -Also, we can't use a single if because lazy evaluation is not an option +Create a default fully qualified Kafka exporter name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). */}} -{{- if .Values.global }} - {{- if .Values.global.imageRegistry }} - {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} - {{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} - {{- end -}} +{{- define "kafka.metrics.kafka.fullname" -}} + {{- printf "%s-exporter" (include "common.names.fullname" .) | trunc 63 | trimSuffix "-" }} +{{- end -}} + +{{/* + Create the name of the service account to use for Kafka exporter pods + */}} +{{- define "kafka.metrics.kafka.serviceAccountName" -}} +{{- if .Values.metrics.kafka.serviceAccount.create -}} + {{ default (include "kafka.metrics.kafka.fullname" .) .Values.metrics.kafka.serviceAccount.name }} {{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{ default "default" .Values.metrics.kafka.serviceAccount.name }} {{- end -}} {{- end -}} @@ -146,99 +75,21 @@ Also, we can't use a single if because lazy evaluation is not an option Return the proper Kafka exporter image name */}} {{- define "kafka.metrics.kafka.image" -}} -{{- $registryName := .Values.metrics.kafka.image.registry -}} -{{- $repositoryName := .Values.metrics.kafka.image.repository -}} -{{- $tag := .Values.metrics.kafka.image.tag | toString -}} -{{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. -Also, we can't use a single if because lazy evaluation is not an option -*/}} -{{- if .Values.global }} - {{- if .Values.global.imageRegistry }} - {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} - {{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} - {{- end -}} -{{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} -{{- end -}} +{{ include "common.images.image" (dict "imageRoot" .Values.metrics.kafka.image "global" .Values.global) }} {{- end -}} {{/* Return the proper JMX exporter image name */}} {{- define "kafka.metrics.jmx.image" -}} -{{- $registryName := .Values.metrics.jmx.image.registry -}} -{{- $repositoryName := .Values.metrics.jmx.image.repository -}} -{{- $tag := .Values.metrics.jmx.image.tag | toString -}} -{{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. -Also, we can't use a single if because lazy evaluation is not an option -*/}} -{{- if .Values.global }} - {{- if .Values.global.imageRegistry }} - {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} - {{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} - {{- end -}} -{{- else -}} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} -{{- end -}} +{{ include "common.images.image" (dict "imageRoot" .Values.metrics.jmx.image "global" .Values.global) }} {{- end -}} {{/* Return the proper Docker Image Registry Secret Names */}} {{- define "kafka.imagePullSecrets" -}} -{{/* -Helm 2.11 supports the assignment of a value to a variable defined in a different scope, -but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic. -Also, we can not use a single if because lazy evaluation is not an option -*/}} -{{- if .Values.global }} -{{- if .Values.global.imagePullSecrets }} -imagePullSecrets: -{{- range .Values.global.imagePullSecrets }} - - name: {{ . }} -{{- end }} -{{- else if or .Values.image.pullSecrets .Values.externalAccess.autoDiscovery.image.pullSecrets .Values.volumePermissions.image.pullSecrets .Values.metrics.kafka.image.pullSecrets .Values.metrics.jmx.image.pullSecrets }} -imagePullSecrets: -{{- range .Values.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- range .Values.externalAccess.autoDiscovery.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- range .Values.volumePermissions.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- range .Values.metrics.kafka.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- range .Values.metrics.jmx.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- end -}} -{{- else if or .Values.image.pullSecrets .Values.externalAccess.autoDiscovery.image.pullSecrets .Values.volumePermissions.image.pullSecrets .Values.metrics.kafka.image.pullSecrets .Values.metrics.jmx.image.pullSecrets }} -imagePullSecrets: -{{- range .Values.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- range .Values.externalAccess.autoDiscovery.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- range .Values.volumePermissions.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- range .Values.metrics.kafka.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- range .Values.metrics.jmx.image.pullSecrets }} - - name: {{ . }} -{{- end }} -{{- end -}} +{{ include "common.images.pullSecrets" (dict "images" (list .Values.image .Values.externalAccess.autoDiscovery.image .Values.volumePermissions.image .Values.metrics.kafka.image .Values.metrics.jmx.image) "global" .Values.global) }} {{- end -}} {{/* @@ -296,12 +147,48 @@ Return true if authentication via SASL should be configured for inter-broker com {{- end -}} {{- end -}} +{{/* +Return true if encryption via TLS for client connections should be configured +*/}} +{{- define "kafka.client.tlsEncryption" -}} +{{- $tlsProtocols := list "tls" "mtls" "sasl_tls" -}} +{{- if (has .Values.auth.clientProtocol $tlsProtocols) -}} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Return the configured value for the external client protocol, defaults to the same value as clientProtocol +*/}} +{{- define "kafka.externalClientProtocol" -}} + {{- coalesce .Values.auth.externalClientProtocol .Values.auth.clientProtocol -}} +{{- end -}} + +{{/* +Return true if encryption via TLS for external client connections should be configured +*/}} +{{- define "kafka.externalClient.tlsEncryption" -}} +{{- $tlsProtocols := list "tls" "mtls" "sasl_tls" -}} +{{- if (has (include "kafka.externalClientProtocol" . ) $tlsProtocols) -}} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if encryption via TLS for inter broker communication connections should be configured +*/}} +{{- define "kafka.interBroker.tlsEncryption" -}} +{{- $tlsProtocols := list "tls" "mtls" "sasl_tls" -}} +{{- if (has .Values.auth.interBrokerProtocol $tlsProtocols) -}} + {{- true -}} +{{- end -}} +{{- end -}} + {{/* Return true if encryption via TLS should be configured */}} {{- define "kafka.tlsEncryption" -}} -{{- $tlsProtocols := list "tls" "mtls" "sasl_tls" -}} -{{- if or (has .Values.auth.clientProtocol $tlsProtocols) (has .Values.auth.interBrokerProtocol $tlsProtocols) -}} +{{- if or (include "kafka.client.tlsEncryption" .) (include "kafka.interBroker.tlsEncryption" .) (include "kafka.externalClient.tlsEncryption" .) -}} {{- true -}} {{- end -}} {{- end -}} @@ -324,33 +211,29 @@ SASL_PLAINTEXT {{- end -}} {{/* -Return the SASL type -Usage: -{{ include "kafka.auth.saslMechanisms" ( dict "type" .Values.path.to.the.Value ) }} +Return the protocol used with zookeeper */}} -{{- define "kafka.auth.saslMechanisms" -}} -{{- $mechanisms := list -}} -{{- if .type | regexFind "plain" -}} -{{- $mechanisms = append $mechanisms "PLAIN" -}} +{{- define "kafka.zookeeper.protocol" -}} +{{- if and .Values.auth.zookeeper.tls.enabled .Values.zookeeper.auth.client.enabled .Values.auth.sasl.jaas.zookeeperUser -}} +SASL_SSL +{{- else if and .Values.auth.zookeeper.tls.enabled -}} +SSL +{{- else if and .Values.zookeeper.auth.client.enabled .Values.auth.sasl.jaas.zookeeperUser -}} +SASL +{{- else -}} +PLAINTEXT {{- end -}} -{{- if .type | regexFind "scram-sha-256" -}} -{{- $mechanisms = append $mechanisms "SCRAM-SHA-256" -}} -{{- end -}} -{{- if .type | regexFind "scram-sha-512" -}} -{{- $mechanisms = append $mechanisms "SCRAM-SHA-512" -}} -{{- end -}} -{{- $mechanisms = join "," $mechanisms -}} -{{- printf "%s" $mechanisms -}} {{- end -}} {{/* Return the Kafka JAAS credentials secret */}} {{- define "kafka.jaasSecretName" -}} -{{- if .Values.auth.jaas.existingSecret -}} - {{- printf "%s" (tpl .Values.auth.jaas.existingSecret $) -}} +{{- $secretName := .Values.auth.sasl.jaas.existingSecret -}} +{{- if $secretName -}} + {{- printf "%s" (tpl $secretName $) -}} {{- else -}} - {{- printf "%s-jaas" (include "kafka.fullname" .) -}} + {{- printf "%s-jaas" (include "common.names.fullname" .) -}} {{- end -}} {{- end -}} @@ -358,27 +241,17 @@ Return the Kafka JAAS credentials secret Return true if a JAAS credentials secret object should be created */}} {{- define "kafka.createJaasSecret" -}} -{{- if and (or (include "kafka.client.saslAuthentication" .) (include "kafka.interBroker.saslAuthentication" .) .Values.auth.jaas.zookeeperUser) (not .Values.auth.jaas.existingSecret) -}} +{{- $secretName := .Values.auth.sasl.jaas.existingSecret -}} +{{- if and (or (include "kafka.client.saslAuthentication" .) (include "kafka.interBroker.saslAuthentication" .) (and .Values.zookeeper.auth.client.enabled .Values.auth.sasl.jaas.zookeeperUser)) (empty $secretName) -}} {{- true -}} {{- end -}} {{- end -}} {{/* -Return the Kafka JKS credentials secret +Return true if a TLS credentials secret object should be created */}} -{{- define "kafka.jksSecretName" -}} -{{- if .Values.auth.jksSecret -}} - {{- printf "%s" (tpl .Values.auth.jksSecret $) -}} -{{- else -}} - {{- printf "%s-jks" (include "kafka.fullname" .) -}} -{{- end -}} -{{- end -}} - -{{/* -Return true if a JAAS credentials secret object should be created -*/}} -{{- define "kafka.createJksSecret" -}} -{{- if and (.Files.Glob "files/jks/*.jks") (not .Values.auth.jksSecret) }} +{{- define "kafka.createTlsSecret" -}} +{{- if and (include "kafka.tlsEncryption" .) (empty .Values.auth.tls.existingSecrets) (eq .Values.auth.tls.type "pem") .Values.auth.tls.autoGenerated }} {{- true -}} {{- end -}} {{- end -}} @@ -390,7 +263,30 @@ Return the Kafka configuration configmap {{- if .Values.existingConfigmap -}} {{- printf "%s" (tpl .Values.existingConfigmap $) -}} {{- else -}} - {{- printf "%s-configuration" (include "kafka.fullname" .) -}} + {{- printf "%s-configuration" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + + +{{/* +Returns the secret name for the Kafka Provisioning client +*/}} +{{- define "kafka.client.passwordsSecretName" -}} +{{- if .Values.provisioning.auth.tls.passwordsSecret -}} + {{- printf "%s" (tpl .Values.provisioning.auth.tls.passwordsSecret $) -}} +{{- else -}} + {{- printf "%s-client-secret" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account to use for the Kafka Provisioning client +*/}} +{{- define "kafka.provisioning.serviceAccountName" -}} +{{- if .Values.provisioning.serviceAccount.create -}} + {{ default (include "common.names.fullname" .) .Values.provisioning.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.provisioning.serviceAccount.name }} {{- end -}} {{- end -}} @@ -410,7 +306,7 @@ Return the Kafka log4j ConfigMap name. {{- if .Values.existingLog4jConfigMap -}} {{- printf "%s" (tpl .Values.existingLog4jConfigMap $) -}} {{- else -}} - {{- printf "%s-log4j-configuration" (include "kafka.fullname" .) -}} + {{- printf "%s-log4j-configuration" (include "common.names.fullname" .) -}} {{- end -}} {{- end -}} @@ -423,6 +319,21 @@ Return true if a log4j ConfigMap object should be created. {{- end -}} {{- end -}} +{{/* +Return the SASL mechanism to use for the Kafka exporter to access Kafka +The exporter uses a different nomenclature so we need to do this hack +*/}} +{{- define "kafka.metrics.kafka.saslMechanism" -}} +{{- $saslMechanisms := .Values.auth.sasl.mechanisms }} +{{- if contains "scram-sha-512" $saslMechanisms }} + {{- print "scram-sha512" -}} +{{- else if contains "scram-sha-256" $saslMechanisms }} + {{- print "scram-sha256" -}} +{{- else -}} + {{- print "plain" -}} +{{- end -}} +{{- end -}} + {{/* Return the Kafka configuration configmap */}} @@ -430,7 +341,7 @@ Return the Kafka configuration configmap {{- if .Values.metrics.jmx.existingConfigmap -}} {{- printf "%s" (tpl .Values.metrics.jmx.existingConfigmap $) -}} {{- else -}} - {{- printf "%s-jmx-configuration" (include "kafka.fullname" .) -}} + {{- printf "%s-jmx-configuration" (include "common.names.fullname" .) -}} {{- end -}} {{- end -}} @@ -443,39 +354,15 @@ Return true if a configmap object should be created {{- end -}} {{- end -}} -{{/* -Renders a value that contains template. -Usage: -{{ include "kafka.tplValue" ( dict "value" .Values.path.to.the.Value "context" $) }} -*/}} -{{- define "kafka.tplValue" -}} - {{- if typeIs "string" .value }} - {{- tpl .value .context }} - {{- else }} - {{- tpl (.value | toYaml) .context }} - {{- end }} -{{- end -}} - {{/* Check if there are rolling tags in the images */}} {{- define "kafka.checkRollingTags" -}} -{{- if and (contains "bitnami/" .Values.image.repository) (not (.Values.image.tag | toString | regexFind "-r\\d+$|sha256:")) }} -WARNING: Rolling tag detected ({{ .Values.image.repository }}:{{ .Values.image.tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. -+info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ -{{- end }} -{{- if and (contains "bitnami/" .Values.externalAccess.autoDiscovery.image.repository) (not (.Values.externalAccess.autoDiscovery.image.tag | toString | regexFind "-r\\d+$|sha256:")) }} -WARNING: Rolling tag detected ({{ .Values.externalAccess.autoDiscovery.image.repository }}:{{ .Values.externalAccess.autoDiscovery.image.tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. -+info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ -{{- end }} -{{- if and (contains "bitnami/" .Values.metrics.kafka.image.repository) (not (.Values.metrics.kafka.image.tag | toString | regexFind "-r\\d+$|sha256:")) }} -WARNING: Rolling tag detected ({{ .Values.metrics.kafka.image.repository }}:{{ .Values.metrics.kafka.image.tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. -+info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ -{{- end }} -{{- if and (contains "bitnami/" .Values.metrics.jmx.image.repository) (not (.Values.metrics.jmx.image.tag | toString | regexFind "-r\\d+$|sha256:")) }} -WARNING: Rolling tag detected ({{ .Values.metrics.jmx.image.repository }}:{{ .Values.metrics.jmx.image.tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. -+info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ -{{- end }} +{{- include "common.warnings.rollingTag" .Values.image }} +{{- include "common.warnings.rollingTag" .Values.externalAccess.autoDiscovery.image }} +{{- include "common.warnings.rollingTag" .Values.metrics.kafka.image }} +{{- include "common.warnings.rollingTag" .Values.metrics.jmx.image }} +{{- include "common.warnings.rollingTag" .Values.volumePermissions.image }} {{- end -}} {{/* @@ -485,10 +372,17 @@ Compile all warnings into a single message, and call fail. {{- $messages := list -}} {{- $messages := append $messages (include "kafka.validateValues.authProtocols" .) -}} {{- $messages := append $messages (include "kafka.validateValues.nodePortListLength" .) -}} +{{- $messages := append $messages (include "kafka.validateValues.domainSpecified" .) -}} {{- $messages := append $messages (include "kafka.validateValues.externalAccessServiceType" .) -}} {{- $messages := append $messages (include "kafka.validateValues.externalAccessAutoDiscoveryRBAC" .) -}} -{{- $messages := append $messages (include "kafka.validateValues.jksSecret" .) -}} +{{- $messages := append $messages (include "kafka.validateValues.externalAccessAutoDiscoveryIPsOrNames" .) -}} +{{- $messages := append $messages (include "kafka.validateValues.externalAccessServiceList" (dict "element" "loadBalancerIPs" "context" .)) -}} +{{- $messages := append $messages (include "kafka.validateValues.externalAccessServiceList" (dict "element" "loadBalancerNames" "context" .)) -}} +{{- $messages := append $messages (include "kafka.validateValues.externalAccessServiceList" (dict "element" "loadBalancerAnnotations" "context" . )) -}} {{- $messages := append $messages (include "kafka.validateValues.saslMechanisms" .) -}} +{{- $messages := append $messages (include "kafka.validateValues.tlsSecrets" .) -}} +{{- $messages := append $messages (include "kafka.validateValues.tlsSecrets.length" .) -}} +{{- $messages := append $messages (include "kafka.validateValues.tlsPasswords" .) -}} {{- $messages := without $messages "" -}} {{- $message := join "\n" $messages -}} @@ -500,13 +394,13 @@ Compile all warnings into a single message, and call fail. {{/* Validate values of Kafka - Authentication protocols for Kafka */}} {{- define "kafka.validateValues.authProtocols" -}} {{- $authProtocols := list "plaintext" "tls" "mtls" "sasl" "sasl_tls" -}} -{{- if or (not (has .Values.auth.clientProtocol $authProtocols)) (not (has .Values.auth.interBrokerProtocol $authProtocols)) -}} -kafka: auth.clientProtocol auth.interBrokerProtocol +{{- if or (not (has .Values.auth.clientProtocol $authProtocols)) (not (has .Values.auth.interBrokerProtocol $authProtocols)) (not (has (include "kafka.externalClientProtocol" . ) $authProtocols)) -}} +kafka: auth.clientProtocol auth.externalClientProtocol auth.interBrokerProtocol Available authentication protocols are "plaintext", "tls", "mtls", "sasl" and "sasl_tls" {{- end -}} {{- end -}} -{{/* Validate values of Kafka - number of replicas must be the same than NodePort list */}} +{{/* Validate values of Kafka - number of replicas must be the same as NodePort list */}} {{- define "kafka.validateValues.nodePortListLength" -}} {{- $replicaCount := int .Values.replicaCount }} {{- $nodePortListLength := len .Values.externalAccess.service.nodePorts }} @@ -516,41 +410,100 @@ kafka: .Values.externalAccess.service.nodePorts {{- end -}} {{- end -}} +{{/* Validate values of Kafka - domain must be defined if external service type ClusterIP */}} +{{- define "kafka.validateValues.domainSpecified" -}} +{{- if and (eq .Values.externalAccess.service.type "ClusterIP") (eq .Values.externalAccess.service.domain "") -}} +kafka: .Values.externalAccess.service.domain + Domain must be specified if service type ClusterIP is set for external service +{{- end -}} +{{- end -}} + {{/* Validate values of Kafka - service type for external access */}} {{- define "kafka.validateValues.externalAccessServiceType" -}} -{{- if and (not (eq .Values.externalAccess.service.type "NodePort")) (not (eq .Values.externalAccess.service.type "LoadBalancer")) -}} +{{- if and (not (eq .Values.externalAccess.service.type "NodePort")) (not (eq .Values.externalAccess.service.type "LoadBalancer")) (not (eq .Values.externalAccess.service.type "ClusterIP")) -}} kafka: externalAccess.service.type - Available servive type for external access are NodePort or LoadBalancer. + Available service type for external access are NodePort, LoadBalancer or ClusterIP. {{- end -}} {{- end -}} {{/* Validate values of Kafka - RBAC should be enabled when autoDiscovery is enabled */}} {{- define "kafka.validateValues.externalAccessAutoDiscoveryRBAC" -}} -{{- if and .Values.externalAccess.enabled .Values.externalAccess.autoDiscovery.enabled (not .Values.rbac.create )}} +{{- if and .Values.externalAccess.enabled .Values.externalAccess.autoDiscovery.enabled (not .Values.rbac.create ) }} kafka: rbac.create By specifying "externalAccess.enabled=true" and "externalAccess.autoDiscovery.enabled=true" - an initContainer will be used to autodetect the external IPs/ports by querying the + an initContainer will be used to auto-detect the external IPs/ports by querying the K8s API. Please note this initContainer requires specific RBAC resources. You can create them by specifying "--set rbac.create=true". {{- end -}} {{- end -}} -{{/* Validate values of Kafka - A secret containing JKS files must be provided when TLS authentication is enabled */}} -{{- define "kafka.validateValues.jksSecret" -}} -{{- if and (include "kafka.tlsEncryption" .) (not .Values.auth.jksSecret) (not (.Files.Glob "files/jks/*.jks")) }} -kafka: auth.jksSecret - A secret containing the Kafka JKS files is required when TLS encryption in enabled +{{/* Validate values of Kafka - LoadBalancerIPs or LoadBalancerNames should be set when autoDiscovery is disabled */}} +{{- define "kafka.validateValues.externalAccessAutoDiscoveryIPsOrNames" -}} +{{- $loadBalancerNameListLength := len .Values.externalAccess.service.loadBalancerNames -}} +{{- $loadBalancerIPListLength := len .Values.externalAccess.service.loadBalancerIPs -}} +{{- if and .Values.externalAccess.enabled (eq .Values.externalAccess.service.type "LoadBalancer") (not .Values.externalAccess.autoDiscovery.enabled) (eq $loadBalancerNameListLength 0) (eq $loadBalancerIPListLength 0) }} +kafka: externalAccess.service.loadBalancerNames or externalAccess.service.loadBalancerIPs + By specifying "externalAccess.enabled=true", "externalAccess.autoDiscovery.enabled=false" and + "externalAccess.service.type=LoadBalancer" at least one of externalAccess.service.loadBalancerNames + or externalAccess.service.loadBalancerIPs must be set and the length of those arrays must be equal + to the number of replicas. +{{- end -}} +{{- end -}} + +{{/* Validate values of Kafka - number of replicas must be the same as loadBalancerIPs list */}} +{{- define "kafka.validateValues.externalAccessServiceList" -}} +{{- $replicaCount := int .context.Values.replicaCount }} +{{- $listLength := len (get .context.Values.externalAccess.service .element) -}} +{{- if and .context.Values.externalAccess.enabled (not .context.Values.externalAccess.autoDiscovery.enabled) (eq .context.Values.externalAccess.service.type "LoadBalancer") (gt $listLength 0) (not (eq $replicaCount $listLength)) }} +kafka: externalAccess.service.{{ .element }} + Number of replicas and {{ .element }} array length must be the same. Currently: replicaCount = {{ $replicaCount }} and {{ .element }} = {{ $listLength }} {{- end -}} {{- end -}} {{/* Validate values of Kafka - SASL mechanisms must be provided when using SASL */}} {{- define "kafka.validateValues.saslMechanisms" -}} -{{- if and (or (.Values.auth.clientProtocol | regexFind "sasl") (.Values.auth.interBrokerProtocol | regexFind "sasl") .Values.auth.jaas.zookeeperUser) (not .Values.auth.saslMechanisms) }} -kafka: auth.saslMechanisms +{{- if and (or (.Values.auth.clientProtocol | regexFind "sasl") (.Values.auth.interBrokerProtocol | regexFind "sasl") (and .Values.zookeeper.auth.client.enabled .Values.auth.sasl.jaas.zookeeperUser)) (not .Values.auth.sasl.mechanisms) }} +kafka: auth.sasl.mechanisms The SASL mechanisms are required when either auth.clientProtocol or auth.interBrokerProtocol use SASL or Zookeeper user is provided. {{- end }} -{{- if not (contains .Values.auth.saslInterBrokerMechanism .Values.auth.saslMechanisms) }} -kafka: auth.saslMechanisms - auth.saslInterBrokerMechanism must be provided and it should be one of the specified mechanisms at auth.saslMechanisms +{{- if not (contains .Values.auth.sasl.interBrokerMechanism .Values.auth.sasl.mechanisms) }} +kafka: auth.sasl.mechanisms + auth.sasl.interBrokerMechanism must be provided and it should be one of the specified mechanisms at auth.saslMechanisms +{{- end -}} +{{- end -}} + +{{/* Validate values of Kafka - Secrets containing TLS certs must be provided when TLS authentication is enabled */}} +{{- define "kafka.validateValues.tlsSecrets" -}} +{{- if and (include "kafka.tlsEncryption" .) (eq .Values.auth.tls.type "jks") (empty .Values.auth.tls.existingSecrets) }} +kafka: auth.tls.existingSecrets + A secret containing the Kafka JKS keystores and truststore is required + when TLS encryption in enabled and TLS format is "JKS" +{{- else if and (include "kafka.tlsEncryption" .) (eq .Values.auth.tls.type "pem") (empty .Values.auth.tls.existingSecrets) (not .Values.auth.tls.autoGenerated) }} +kafka: auth.tls.existingSecrets + A secret containing the Kafka TLS certificates and keys is required + when TLS encryption in enabled and TLS format is "PEM" +{{- end -}} +{{- end -}} + +{{/* Validate values of Kafka - The number of secrets containing TLS certs should be equal to the number of replicas */}} +{{- define "kafka.validateValues.tlsSecrets.length" -}} +{{- $replicaCount := int .Values.replicaCount }} +{{- if and (include "kafka.tlsEncryption" .) (not (empty .Values.auth.tls.existingSecrets)) }} +{{- $existingSecretsLength := len .Values.auth.tls.existingSecrets }} +{{- if ne $replicaCount $existingSecretsLength }} +kafka: .Values.auth.tls.existingSecrets + Number of replicas and existingSecrets array length must be the same. Currently: replicaCount = {{ $replicaCount }} and existingSecrets = {{ $existingSecretsLength }} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* Validate values of Kafka provisioning - keyPasswordSecretKey, keystorePasswordSecretKey or truststorePasswordSecretKey must not be used without passwordsSecret */}} +{{- define "kafka.validateValues.tlsPasswords" -}} +{{- if and (include "kafka.client.tlsEncryption" .) (not .Values.auth.tls.passwordsSecret) }} +{{- if or .Values.auth.tls.keyPasswordSecretKey .Values.auth.tls.keystorePasswordSecretKey .Values.auth.tls.truststorePasswordSecretKey }} +kafka: auth.tls.keyPasswordSecretKey,auth.tls.keystorePasswordSecretKey,auth.tls.truststorePasswordSecretKey + auth.tls.keyPasswordSecretKey,auth.tls.keystorePasswordSecretKey,auth.tls.truststorePasswordSecretKey + must not be used without passwordsSecret setted. +{{- end -}} {{- end -}} {{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/configmap.yaml b/scripts/helmcharts/databases/charts/kafka/templates/configmap.yaml old mode 100755 new mode 100644 index d24203530..509fd1c4f --- a/scripts/helmcharts/databases/charts/kafka/templates/configmap.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/configmap.yaml @@ -2,13 +2,14 @@ apiVersion: v1 kind: ConfigMap metadata: - name: {{ template "kafka.fullname" . }}-configuration - labels: {{- include "kafka.labels" . | nindent 4 }} + name: {{ printf "%s-configuration" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} data: server.properties: |- diff --git a/scripts/helmcharts/databases/charts/kafka/templates/extra-list.yaml b/scripts/helmcharts/databases/charts/kafka/templates/extra-list.yaml old mode 100755 new mode 100644 index b83eb30d2..9ac65f9e1 --- a/scripts/helmcharts/databases/charts/kafka/templates/extra-list.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/extra-list.yaml @@ -1,5 +1,4 @@ -{{- if .Values.extraDeploy }} -apiVersion: v1 -kind: List -items: {{- include "kafka.tplValue" (dict "value" .Values.extraDeploy "context" $) | nindent 2 }} +{{- range .Values.extraDeploy }} +--- +{{ include "common.tplvalues.render" (dict "value" . "context" $) }} {{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/jaas-secret.yaml b/scripts/helmcharts/databases/charts/kafka/templates/jaas-secret.yaml old mode 100755 new mode 100644 index 96eeb25ec..8f632e56e --- a/scripts/helmcharts/databases/charts/kafka/templates/jaas-secret.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/jaas-secret.yaml @@ -2,31 +2,39 @@ apiVersion: v1 kind: Secret metadata: - name: {{ template "kafka.fullname" . }}-jaas - labels: {{- include "kafka.labels" . | nindent 4 }} + name: {{ printf "%s-jaas" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} type: Opaque data: {{- if (include "kafka.client.saslAuthentication" .) }} - {{- if .Values.auth.jaas.clientPasswords }} - client-passwords: {{ join "," .Values.auth.jaas.clientPasswords | b64enc | quote }} + {{- $clientUsers := .Values.auth.sasl.jaas.clientUsers }} + {{- $clientPasswords := .Values.auth.sasl.jaas.clientPasswords }} + {{- if $clientPasswords }} + client-passwords: {{ join "," $clientPasswords | b64enc | quote }} + system-user-password: {{ index $clientPasswords 0 | b64enc | quote }} {{- else }} {{- $passwords := list }} - {{- range .Values.auth.jaas.clientUsers }} + {{- range $clientUsers }} {{- $passwords = append $passwords (randAlphaNum 10) }} {{- end }} client-passwords: {{ join "," $passwords | b64enc | quote }} + system-user-password: {{ index $passwords 0 | b64enc | quote }} {{- end }} {{- end }} - {{- if .Values.auth.jaas.zookeeperUser }} - zookeeper-password: {{ ternary (randAlphaNum 10) .Values.auth.jaas.zookeeperPassword (empty .Values.auth.jaas.zookeeperPassword) | b64enc | quote }} + {{- $zookeeperUser := .Values.auth.sasl.jaas.zookeeperUser }} + {{- if and .Values.zookeeper.auth.client.enabled $zookeeperUser }} + {{- $zookeeperPassword := .Values.auth.sasl.jaas.zookeeperPassword }} + zookeeper-password: {{ default (randAlphaNum 10) $zookeeperPassword | b64enc | quote }} {{- end }} {{- if (include "kafka.interBroker.saslAuthentication" .) }} - inter-broker-password: {{ ternary (randAlphaNum 10) .Values.auth.jaas.interBrokerPassword (empty .Values.auth.jaas.interBrokerPassword) | b64enc | quote }} + {{- $interBrokerPassword := .Values.auth.sasl.jaas.interBrokerPassword }} + inter-broker-password: {{ default (randAlphaNum 10) $interBrokerPassword | b64enc | quote }} {{- end }} {{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/jks-secret.yaml b/scripts/helmcharts/databases/charts/kafka/templates/jks-secret.yaml deleted file mode 100755 index 5b878285e..000000000 --- a/scripts/helmcharts/databases/charts/kafka/templates/jks-secret.yaml +++ /dev/null @@ -1,19 +0,0 @@ -{{- if (include "kafka.createJksSecret" .) }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ template "kafka.fullname" . }}-jks - labels: {{- include "kafka.labels" . | nindent 4 }} - {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} - {{- end }} - {{- if .Values.commonAnnotations }} - annotations: {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} - {{- end }} -type: Opaque -data: - {{- $root := . }} - {{- range $path, $bytes := .Files.Glob "files/jks/*.jks" }} - {{ base $path }}: {{ $root.Files.Get $path | b64enc | quote }} - {{- end }} -{{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/jmx-configmap.yaml b/scripts/helmcharts/databases/charts/kafka/templates/jmx-configmap.yaml old mode 100755 new mode 100644 index 2cb2eeb3b..9207f6d1d --- a/scripts/helmcharts/databases/charts/kafka/templates/jmx-configmap.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/jmx-configmap.yaml @@ -2,17 +2,19 @@ apiVersion: v1 kind: ConfigMap metadata: - name: {{ template "kafka.fullname" . }}-jmx-configuration - labels: {{- include "kafka.labels" . | nindent 4 }} + name: {{ printf "%s-jmx-configuration" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: metrics {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} data: jmx-kafka-prometheus.yml: |- - {{- include "kafka.tplValue" ( dict "value" .Values.metrics.jmx.config "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.jmx.config "context" $ ) | nindent 4 }} rules: - pattern: kafka.controller<type=(ControllerChannelManager), name=(QueueSize), broker-id=(\d+)><>(Value) name: kafka_controller_$1_$2_$4 @@ -56,4 +58,7 @@ data: labels: topic: $3 partition: $4 + {{- if .Values.metrics.jmx.extraRules }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.jmx.extraRules "context" $ ) | nindent 6 }} + {{- end }} {{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/jmx-metrics-svc.yaml b/scripts/helmcharts/databases/charts/kafka/templates/jmx-metrics-svc.yaml old mode 100755 new mode 100644 index 83edd8422..35c79f41f --- a/scripts/helmcharts/databases/charts/kafka/templates/jmx-metrics-svc.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/jmx-metrics-svc.yaml @@ -2,44 +2,33 @@ apiVersion: v1 kind: Service metadata: - name: {{ template "kafka.fullname" . }}-jmx-metrics - labels: {{- include "kafka.labels" . | nindent 4 }} - app.kubernetes.io/component: kafka + name: {{ printf "%s-jmx-metrics" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: metrics {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if or .Values.metrics.jmx.service.annotations .Values.commonAnnotations }} annotations: {{- if .Values.metrics.jmx.service.annotations }} - {{ include "kafka.tplValue" ( dict "value" .Values.metrics.jmx.service.annotations "context" $) | nindent 4 }} + {{ include "common.tplvalues.render" ( dict "value" .Values.metrics.jmx.service.annotations "context" $) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} {{- end }} spec: - type: {{ .Values.metrics.jmx.service.type }} - {{- if eq .Values.metrics.jmx.service.type "LoadBalancer" }} - {{- if .Values.metrics.jmx.service.loadBalancerIP }} - loadBalancerIP: {{ .Values.metrics.jmx.service.loadBalancerIP }} - {{- end }} - {{- if .Values.metrics.jmx.service.loadBalancerSourceRanges }} - loadBalancerSourceRanges: {{- toYaml .Values.metrics.jmx.service.loadBalancerSourceRanges | nindent 4 }} - {{- end }} - {{- end }} - {{- if and (eq .Values.metrics.jmx.service.type "ClusterIP") .Values.metrics.jmx.service.clusterIP }} + type: ClusterIP + sessionAffinity: {{ .Values.metrics.jmx.service.sessionAffinity }} + {{- if .Values.metrics.jmx.service.clusterIP }} clusterIP: {{ .Values.metrics.jmx.service.clusterIP }} {{- end }} ports: - name: http-metrics - port: {{ .Values.metrics.jmx.service.port }} + port: {{ .Values.metrics.jmx.service.ports.metrics }} protocol: TCP targetPort: metrics - {{- if and (or (eq .Values.metrics.jmx.service.type "NodePort") (eq .Values.metrics.jmx.service.type "LoadBalancer")) (not (empty .Values.metrics.jmx.service.nodePort)) }} - nodePort: {{ .Values.metrics.jmx.service.nodePort }} - {{- else if eq .Values.metrics.jmx.service.type "ClusterIP" }} - nodePort: null - {{- end }} - selector: {{- include "kafka.matchLabels" . | nindent 4 }} + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} app.kubernetes.io/component: kafka {{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/kafka-metrics-deployment.yaml b/scripts/helmcharts/databases/charts/kafka/templates/kafka-metrics-deployment.yaml old mode 100755 new mode 100644 index c547fbb39..bf731f20b --- a/scripts/helmcharts/databases/charts/kafka/templates/kafka-metrics-deployment.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/kafka-metrics-deployment.yaml @@ -2,86 +2,170 @@ {{- $replicaCount := int .Values.replicaCount -}} {{- $releaseNamespace := .Release.Namespace -}} {{- $clusterDomain := .Values.clusterDomain -}} -{{- $fullname := include "kafka.fullname" . -}} -{{- $servicePort := int .Values.service.port -}} -apiVersion: apps/v1 +{{- $fullname := include "common.names.fullname" . -}} +{{- $servicePort := int .Values.service.ports.client -}} +apiVersion: {{ include "common.capabilities.deployment.apiVersion" . }} kind: Deployment metadata: - name: {{ template "kafka.fullname" . }}-exporter - labels: {{- include "kafka.labels" . | nindent 4 }} - app.kubernetes.io/component: metrics + name: {{ include "kafka.metrics.kafka.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: cluster-metrics {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} spec: replicas: 1 selector: - matchLabels: {{- include "kafka.matchLabels" . | nindent 6 }} - app.kubernetes.io/component: metrics + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: cluster-metrics template: metadata: - labels: {{- include "kafka.labels" . | nindent 8 }} - app.kubernetes.io/component: metrics + labels: {{- include "common.labels.standard" . | nindent 8 }} + app.kubernetes.io/component: cluster-metrics + {{- if .Values.metrics.kafka.podLabels }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.kafka.podLabels "context" $) | nindent 8 }} + {{- end }} + annotations: + {{- if .Values.metrics.kafka.podAnnotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.kafka.podAnnotations "context" $) | nindent 8 }} + {{- end }} spec: -{{- include "kafka.imagePullSecrets" . | indent 6 }} + {{- include "kafka.imagePullSecrets" . | nindent 6 }} + {{- if .Values.metrics.kafka.hostAliases }} + hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.metrics.kafka.hostAliases "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.metrics.kafka.affinity }} + affinity: {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.kafka.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.metrics.kafka.podAffinityPreset "component" "metrics" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.metrics.kafka.podAntiAffinityPreset "component" "metrics" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.metrics.kafka.nodeAffinityPreset.type "key" .Values.metrics.kafka.nodeAffinityPreset.key "values" .Values.metrics.kafka.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.metrics.kafka.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.kafka.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.metrics.kafka.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.metrics.kafka.tolerations "context" .) | nindent 8 }} + {{- end }} + {{- if .Values.metrics.kafka.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "common.tplvalues.render" (dict "value" .Values.metrics.kafka.topologySpreadConstraints "context" .) | nindent 8 }} + {{- end }} + {{- if .Values.metrics.kafka.priorityClassName }} + priorityClassName: {{ .Values.metrics.kafka.priorityClassName }} + {{- end }} + {{- if .Values.metrics.kafka.schedulerName }} + schedulerName: {{ .Values.metrics.kafka.schedulerName }} + {{- end }} + {{- if .Values.metrics.kafka.podSecurityContext.enabled }} + securityContext: {{- omit .Values.metrics.kafka.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + serviceAccountName: {{ template "kafka.metrics.kafka.serviceAccountName" . }} + {{- if .Values.metrics.kafka.initContainers }} + initContainers: {{- include "common.tplvalues.render" (dict "value" .Values.metrics.kafka.initContainers "context" $) | nindent 8 }} + {{- end }} containers: - name: kafka-exporter - image: {{ include "kafka.metrics.kafka.image" . }} + image: {{ include "kafka.metrics.kafka.image" . }} imagePullPolicy: {{ .Values.metrics.kafka.image.pullPolicy | quote }} + {{- if .Values.metrics.kafka.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.metrics.kafka.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else if .Values.metrics.kafka.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.metrics.kafka.command "context" $) | nindent 12 }} + {{- else }} command: - - /bin/bash - - -ec + - bash + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else if .Values.metrics.kafka.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.metrics.kafka.args "context" $) | nindent 12 }} + {{- else }} + args: + - -ce - | - read -r -a sasl_passwords <<< "$(tr ',;' ' ' <<< "${SASL_USER_PASSWORD}")" kafka_exporter \ {{- range $i, $e := until $replicaCount }} --kafka.server={{ $fullname }}-{{ $i }}.{{ $fullname }}-headless.{{ $releaseNamespace }}.svc.{{ $clusterDomain }}:{{ $servicePort }} \ {{- end }} {{- if (include "kafka.client.saslAuthentication" .) }} --sasl.enabled \ - --sasl.username="$SASL_USERNAME" \ - --sasl.password="${sasl_passwords[0]}" \ + --sasl.username=$SASL_USERNAME \ + --sasl.password=$SASL_USER_PASSWORD \ + --sasl.mechanism={{ include "kafka.metrics.kafka.saslMechanism" . }} \ {{- end }} - {{- if (include "kafka.tlsEncryption" .) }} + {{- if (include "kafka.client.tlsEncryption" .) }} --tls.enabled \ {{- if .Values.metrics.kafka.certificatesSecret }} - --tls.ca-file="/opt/bitnami/kafka-exporter/certs/ca-file" \ - --tls.cert-file="/opt/bitnami/kafka-exporter/certs/cert-file" \ - --tls.key-file="/opt/bitnami/kafka-exporter/certs/key-file" \ + --tls.key-file=/opt/bitnami/kafka-exporter/certs/{{ .Values.metrics.kafka.tlsKey }} \ + --tls.cert-file=/opt/bitnami/kafka-exporter/certs/{{ .Values.metrics.kafka.tlsCert }} \ + {{- if .Values.metrics.kafka.tlsCaSecret }} + --tls.ca-file=/opt/bitnami/kafka-exporter/cacert/{{ .Values.metrics.kafka.tlsCaCert }} \ + {{- else }} + --tls.ca-file=/opt/bitnami/kafka-exporter/certs/{{ .Values.metrics.kafka.tlsCaCert }} \ + {{- end }} {{- end }} {{- end }} {{- range $key, $value := .Values.metrics.kafka.extraFlags }} --{{ $key }}{{ if $value }}={{ $value }}{{ end }} \ {{- end }} - --web.listen-address=:9308 + --web.listen-address=:{{ .Values.metrics.kafka.containerPorts.metrics }} + {{- end }} {{- if (include "kafka.client.saslAuthentication" .) }} + {{- $clientUsers := .Values.auth.sasl.jaas.clientUsers }} env: - name: SASL_USERNAME - value: {{ index .Values.auth.jaas.clientUsers 0 | quote }} + value: {{ index $clientUsers 0 | quote }} - name: SASL_USER_PASSWORD valueFrom: secretKeyRef: name: {{ include "kafka.jaasSecretName" . }} - key: client-passwords + key: system-user-password {{- end }} ports: - name: metrics - containerPort: 9308 + containerPort: {{ .Values.metrics.kafka.containerPorts.metrics }} {{- if .Values.metrics.kafka.resources }} resources: {{ toYaml .Values.metrics.kafka.resources | nindent 12 }} {{- end }} - {{- if and (include "kafka.tlsEncryption" .) .Values.metrics.kafka.certificatesSecret }} volumeMounts: + {{- if .Values.metrics.kafka.extraVolumeMounts }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.kafka.extraVolumeMounts "context" $) | nindent 12 }} + {{- end }} + {{- if and (include "kafka.client.tlsEncryption" .) .Values.metrics.kafka.certificatesSecret }} - name: kafka-exporter-certificates mountPath: /opt/bitnami/kafka-exporter/certs/ readOnly: true + {{- if .Values.metrics.kafka.tlsCaSecret }} + - name: kafka-exporter-ca-certificate + mountPath: /opt/bitnami/kafka-exporter/cacert/ + readOnly: true + {{- end }} + {{- end }} + {{- if .Values.metrics.kafka.sidecars }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.kafka.sidecars "context" $) | nindent 8 }} + {{- end }} volumes: + {{- if .Values.metrics.kafka.extraVolumes }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.kafka.extraVolumes "context" $) | nindent 8 }} + {{- end }} + {{- if and (include "kafka.client.tlsEncryption" .) .Values.metrics.kafka.certificatesSecret }} - name: kafka-exporter-certificates secret: secretName: {{ .Values.metrics.kafka.certificatesSecret }} defaultMode: 0440 - {{- end }} + {{- if .Values.metrics.kafka.tlsCaSecret }} + - name: kafka-exporter-ca-certificate + secret: + secretName: {{ .Values.metrics.kafka.tlsCaSecret }} + defaultMode: 0440 + {{- end }} + {{- end }} {{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/kafka-metrics-serviceaccount.yaml b/scripts/helmcharts/databases/charts/kafka/templates/kafka-metrics-serviceaccount.yaml new file mode 100644 index 000000000..f8e3eb305 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/templates/kafka-metrics-serviceaccount.yaml @@ -0,0 +1,16 @@ +{{- if and .Values.metrics.kafka.enabled .Values.metrics.kafka.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "kafka.metrics.kafka.serviceAccountName" . }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: cluster-metrics + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.metrics.kafka.serviceAccount.automountServiceAccountToken }} +{{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/kafka-metrics-svc.yaml b/scripts/helmcharts/databases/charts/kafka/templates/kafka-metrics-svc.yaml old mode 100755 new mode 100644 index 54a4ccb0b..9daae4a1a --- a/scripts/helmcharts/databases/charts/kafka/templates/kafka-metrics-svc.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/kafka-metrics-svc.yaml @@ -2,44 +2,33 @@ apiVersion: v1 kind: Service metadata: - name: {{ template "kafka.fullname" . }}-metrics - labels: {{- include "kafka.labels" . | nindent 4 }} - app.kubernetes.io/component: metrics + name: {{ printf "%s-metrics" (include "common.names.fullname" .) | trunc 63 | trimSuffix "-" }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: cluster-metrics {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if or .Values.metrics.kafka.service.annotations .Values.commonAnnotations }} annotations: {{- if .Values.metrics.kafka.service.annotations }} - {{ include "kafka.tplValue" ( dict "value" .Values.metrics.kafka.service.annotations "context" $) | nindent 4 }} + {{ include "common.tplvalues.render" ( dict "value" .Values.metrics.kafka.service.annotations "context" $) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} {{- end }} spec: - type: {{ .Values.metrics.kafka.service.type }} - {{- if eq .Values.metrics.kafka.service.type "LoadBalancer" }} - {{- if .Values.metrics.kafka.service.loadBalancerIP }} - loadBalancerIP: {{ .Values.metrics.kafka.service.loadBalancerIP }} - {{- end }} - {{- if .Values.metrics.kafka.service.loadBalancerSourceRanges }} - loadBalancerSourceRanges: {{- toYaml .Values.metrics.kafka.service.loadBalancerSourceRanges | nindent 4 }} - {{- end }} - {{- end }} - {{- if and (eq .Values.metrics.kafka.service.type "ClusterIP") .Values.metrics.kafka.service.clusterIP }} + type: ClusterIP + sessionAffinity: {{ .Values.metrics.kafka.service.sessionAffinity }} + {{- if .Values.metrics.kafka.service.clusterIP }} clusterIP: {{ .Values.metrics.kafka.service.clusterIP }} {{- end }} ports: - name: http-metrics - port: {{ .Values.metrics.kafka.service.port }} + port: {{ .Values.metrics.kafka.service.ports.metrics }} protocol: TCP targetPort: metrics - {{- if and (or (eq .Values.metrics.kafka.service.type "NodePort") (eq .Values.metrics.kafka.service.type "LoadBalancer")) (not (empty .Values.metrics.kafka.service.nodePort)) }} - nodePort: {{ .Values.metrics.kafka.service.nodePort }} - {{- else if eq .Values.metrics.kafka.service.type "ClusterIP" }} - nodePort: null - {{- end }} - selector: {{- include "kafka.matchLabels" . | nindent 4 }} - app.kubernetes.io/component: metrics + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} + app.kubernetes.io/component: cluster-metrics {{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/kafka-provisioning-secret.yaml b/scripts/helmcharts/databases/charts/kafka/templates/kafka-provisioning-secret.yaml new file mode 100644 index 000000000..0c0fb1bc1 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/templates/kafka-provisioning-secret.yaml @@ -0,0 +1,19 @@ +{{- if and .Values.provisioning.enabled (include "kafka.client.tlsEncryption" .) (not .Values.provisioning.auth.tls.passwordsSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "kafka.client.passwordsSecretName" . }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: Opaque +data: + truststore-password: {{ default "" .Values.provisioning.auth.tls.keystorePassword | b64enc | quote }} + keystore-password: {{ default "" .Values.provisioning.auth.tls.truststorePassword | b64enc | quote }} + key-password: {{ default "" .Values.provisioning.auth.tls.keyPassword | b64enc | quote }} +{{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/kafka-provisioning-serviceaccount.yaml b/scripts/helmcharts/databases/charts/kafka/templates/kafka-provisioning-serviceaccount.yaml new file mode 100644 index 000000000..47614674c --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/templates/kafka-provisioning-serviceaccount.yaml @@ -0,0 +1,15 @@ +{{- if .Values.provisioning.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "kafka.provisioning.serviceAccountName" . }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.provisioning.serviceAccount.automountServiceAccountToken }} +{{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/kafka-provisioning.yaml b/scripts/helmcharts/databases/charts/kafka/templates/kafka-provisioning.yaml new file mode 100644 index 000000000..765e88315 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/templates/kafka-provisioning.yaml @@ -0,0 +1,260 @@ +{{- if .Values.provisioning.enabled }} +{{- $replicaCount := int .Values.replicaCount }} +kind: Job +apiVersion: batch/v1 +metadata: + name: {{ printf "%s-provisioning" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: kafka-provisioning + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + template: + metadata: + labels: {{- include "common.labels.standard" . | nindent 8 }} + app.kubernetes.io/component: kafka-provisioning + {{- if .Values.provisioning.podLabels }} + {{- include "common.tplvalues.render" (dict "value" .Values.provisioning.podLabels "context" $) | nindent 8 }} + {{- end }} + annotations: + {{- if .Values.provisioning.podAnnotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.provisioning.podAnnotations "context" $) | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ template "kafka.provisioning.serviceAccountName" . }} + {{- include "kafka.imagePullSecrets" . | nindent 6 }} + {{- if .Values.provisioning.schedulerName }} + schedulerName: {{ .Values.provisioning.schedulerName | quote }} + {{- end }} + {{- if .Values.provisioning.podSecurityContext.enabled }} + securityContext: {{- omit .Values.provisioning.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + restartPolicy: OnFailure + terminationGracePeriodSeconds: 0 + {{- if .Values.provisioning.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" ( dict "value" .Values.provisioning.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.provisioning.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.provisioning.tolerations "context" .) | nindent 8 }} + {{- end }} + {{- if or .Values.provisioning.initContainers .Values.provisioning.waitForKafka }} + initContainers: + {{- if .Values.provisioning.waitForKafka }} + - name: wait-for-available-kafka + image: {{ include "kafka.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.provisioning.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.provisioning.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + command: + - /bin/bash + args: + - -ec + - | + wait-for-port \ + --host={{ include "common.names.fullname" . }} \ + --state=inuse \ + --timeout=120 \ + {{ .Values.service.ports.client | int64 }}; + echo "Kafka is available"; + {{- if .Values.provisioning.resources }} + resources: {{- toYaml .Values.provisioning.resources | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.provisioning.initContainers }} + {{- include "common.tplvalues.render" ( dict "value" .Values.provisioning.initContainers "context" $ ) | nindent 8 }} + {{- end }} + {{- end }} + containers: + - name: kafka-provisioning + image: {{ include "kafka.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.provisioning.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.provisioning.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else if .Values.provisioning.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.provisioning.command "context" $) | nindent 12 }} + {{- else }} + command: + - /bin/bash + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else if .Values.provisioning.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.provisioning.args "context" $) | nindent 12 }} + {{- else }} + args: + - -ec + - | + echo "Configuring environment" + . /opt/bitnami/scripts/libkafka.sh + export CLIENT_CONF="${CLIENT_CONF:-/opt/bitnami/kafka/config/client.properties}" + if [ ! -f "$CLIENT_CONF" ]; then + touch $CLIENT_CONF + + kafka_common_conf_set "$CLIENT_CONF" security.protocol {{ include "kafka.listenerType" ( dict "protocol" .Values.auth.clientProtocol ) | quote }} + {{- if (include "kafka.client.tlsEncryption" .) }} + kafka_common_conf_set "$CLIENT_CONF" ssl.keystore.type {{ upper .Values.provisioning.auth.tls.type | quote }} + kafka_common_conf_set "$CLIENT_CONF" ssl.truststore.type {{ upper .Values.provisioning.auth.tls.type | quote }} + ! is_empty_value "$KAFKA_CLIENT_KEY_PASSWORD" && kafka_common_conf_set "$CLIENT_CONF" ssl.key.password "$KAFKA_CLIENT_KEY_PASSWORD" + {{- if eq (upper .Values.provisioning.auth.tls.type) "PEM" }} + file_to_multiline_property() { + awk 'NR > 1{print line" \\"}{line=$0;}END{print $0" "}' <"${1:?missing file}" + } + kafka_common_conf_set "$CLIENT_CONF" ssl.keystore.key "$(file_to_multiline_property "/certs/{{ .Values.provisioning.auth.tls.key }}")" + kafka_common_conf_set "$CLIENT_CONF" ssl.keystore.certificate.chain "$(file_to_multiline_property "/certs/{{ .Values.provisioning.auth.tls.caCert }}")" + kafka_common_conf_set "$CLIENT_CONF" ssl.truststore.certificates "$(file_to_multiline_property "/certs/{{ .Values.provisioning.auth.tls.cert }}")" + {{- else if eq (upper .Values.provisioning.auth.tls.type) "JKS" }} + kafka_common_conf_set "$CLIENT_CONF" ssl.keystore.location "/certs/{{ .Values.provisioning.auth.tls.keystore }}" + kafka_common_conf_set "$CLIENT_CONF" ssl.truststore.location "/certs/{{ .Values.provisioning.auth.tls.truststore }}" + ! is_empty_value "$KAFKA_CLIENT_KEYSTORE_PASSWORD" && kafka_common_conf_set "$CLIENT_CONF" ssl.keystore.password "$KAFKA_CLIENT_KEYSTORE_PASSWORD" + ! is_empty_value "$KAFKA_CLIENT_TRUSTSTORE_PASSWORD" && kafka_common_conf_set "$CLIENT_CONF" ssl.truststore.password "$KAFKA_CLIENT_TRUSTSTORE_PASSWORD" + {{- end }} + {{- end }} + {{- if (include "kafka.client.saslAuthentication" .) }} + {{- if contains "plain" .Values.auth.sasl.mechanisms }} + kafka_common_conf_set "$CLIENT_CONF" sasl.mechanism PLAIN + kafka_common_conf_set "$CLIENT_CONF" sasl.jaas.config "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"$SASL_USERNAME\" password=\"$SASL_USER_PASSWORD\";" + {{- else if contains "scram-sha-256" .Values.auth.sasl.mechanisms }} + kafka_common_conf_set "$CLIENT_CONF" sasl.mechanism SCRAM-SHA-256 + kafka_common_conf_set "$CLIENT_CONF" sasl.jaas.config "org.apache.kafka.common.security.scram.ScramLoginModule required username=\"$SASL_USERNAME\" password=\"$SASL_USER_PASSWORD\";" + {{- else if contains "scram-sha-512" .Values.auth.sasl.mechanisms }} + kafka_common_conf_set "$CLIENT_CONF" sasl.mechanism SCRAM-SHA-512 + kafka_common_conf_set "$CLIENT_CONF" sasl.jaas.config "org.apache.kafka.common.security.scram.ScramLoginModule required username=\"$SASL_USERNAME\" password=\"$SASL_USER_PASSWORD\";" + {{- end }} + {{- end }} + fi + + echo "Running pre-provisioning script if any given" + {{ .Values.provisioning.preScript | nindent 14 }} + + kafka_provisioning_commands=( + {{- range $topic := .Values.provisioning.topics }} + "/opt/bitnami/kafka/bin/kafka-topics.sh \ + --create \ + --if-not-exists \ + --bootstrap-server ${KAFKA_SERVICE} \ + --replication-factor {{ $topic.replicationFactor | default $.Values.provisioning.replicationFactor }} \ + --partitions {{ $topic.partitions | default $.Values.provisioning.numPartitions }} \ + {{- range $name, $value := $topic.config }} + --config {{ $name }}={{ $value }} \ + {{- end }} + --command-config ${CLIENT_CONF} \ + --topic {{ $topic.name }}" + {{- end }} + {{- range $command := .Values.provisioning.extraProvisioningCommands }} + {{- $command | quote | nindent 16 }} + {{- end }} + ) + + echo "Starting provisioning" + for ((index=0; index < ${#kafka_provisioning_commands[@]}; index+={{ .Values.provisioning.parallel }})) + do + for j in $(seq ${index} $((${index}+{{ .Values.provisioning.parallel }}-1))) + do + ${kafka_provisioning_commands[j]} & # Async command + done + wait # Wait the end of the jobs + done + + echo "Running post-provisioning script if any given" + {{ .Values.provisioning.postScript | nindent 14 }} + + echo "Provisioning succeeded" + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" (or .Values.image.debug .Values.diagnosticMode.enabled) | quote }} + {{- if (include "kafka.client.tlsEncryption" .) }} + - name: KAFKA_CLIENT_KEY_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "kafka.client.passwordsSecretName" . }} + key: {{ .Values.provisioning.auth.tls.keyPasswordSecretKey }} + - name: KAFKA_CLIENT_KEYSTORE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "kafka.client.passwordsSecretName" . }} + key: {{ .Values.provisioning.auth.tls.keystorePasswordSecretKey }} + - name: KAFKA_CLIENT_TRUSTSTORE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "kafka.client.passwordsSecretName" . }} + key: {{ .Values.provisioning.auth.tls.truststorePasswordSecretKey }} + {{- end }} + - name: KAFKA_SERVICE + value: {{ printf "%s:%d" (include "common.names.fullname" .) (.Values.service.ports.client | int64) }} + {{- if (include "kafka.client.saslAuthentication" .) }} + {{- $clientUsers := .Values.auth.sasl.jaas.clientUsers }} + - name: SASL_USERNAME + value: {{ index $clientUsers 0 | quote }} + - name: SASL_USER_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "kafka.jaasSecretName" . }} + key: system-user-password + {{- end }} + {{- if .Values.provisioning.extraEnvVars }} + {{- include "common.tplvalues.render" ( dict "value" .Values.provisioning.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + {{- if or .Values.provisioning.extraEnvVarsCM .Values.provisioning.extraEnvVarsSecret }} + envFrom: + {{- if .Values.provisioning.extraEnvVarsCM }} + - configMapRef: + name: {{ include "common.tplvalues.render" (dict "value" .Values.provisioning.extraEnvVarsCM "context" $) }} + {{- end }} + {{- if .Values.provisioning.extraEnvVarsSecret }} + - secretRef: + name: {{ include "common.tplvalues.render" (dict "value" .Values.provisioning.extraEnvVarsSecret "context" $) }} + {{- end }} + {{- end }} + {{- if .Values.provisioning.resources }} + resources: {{- toYaml .Values.provisioning.resources | nindent 12 }} + {{- end }} + volumeMounts: + {{- if or .Values.log4j .Values.existingLog4jConfigMap }} + - name: log4j-config + mountPath: {{ .Values.persistence.mountPath }}/config/log4j.properties + subPath: log4j.properties + {{- end }} + {{- if (include "kafka.client.tlsEncryption" .) }} + {{- if not (empty .Values.provisioning.auth.tls.certificatesSecret) }} + - name: kafka-client-certs + mountPath: /certs + readOnly: true + {{- end }} + {{- end }} + {{- if .Values.provisioning.extraVolumeMounts }} + {{- include "common.tplvalues.render" (dict "value" .Values.provisioning.extraVolumeMounts "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.provisioning.sidecars }} + {{- include "common.tplvalues.render" (dict "value" .Values.provisioning.sidecars "context" $) | nindent 8 }} + {{- end }} + volumes: + {{- if or .Values.log4j .Values.existingLog4jConfigMap }} + - name: log4j-config + configMap: + name: {{ include "kafka.log4j.configMapName" . }} + {{ end }} + {{- if (include "kafka.client.tlsEncryption" .) }} + {{- if not (empty .Values.provisioning.auth.tls.certificatesSecret) }} + - name: kafka-client-certs + secret: + secretName: {{ .Values.provisioning.auth.tls.certificatesSecret }} + defaultMode: 256 + {{- end }} + {{- end }} + {{- if .Values.provisioning.extraVolumes }} + {{- include "common.tplvalues.render" (dict "value" .Values.provisioning.extraVolumes "context" $) | nindent 8 }} + {{- end }} +{{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/log4j-configmap.yaml b/scripts/helmcharts/databases/charts/kafka/templates/log4j-configmap.yaml old mode 100755 new mode 100644 index 0a34d50dd..8f7bc6c14 --- a/scripts/helmcharts/databases/charts/kafka/templates/log4j-configmap.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/log4j-configmap.yaml @@ -3,14 +3,15 @@ apiVersion: v1 kind: ConfigMap metadata: name: {{ include "kafka.log4j.configMapName" . }} - labels: {{- include "kafka.labels" . | nindent 4 }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} data: log4j.properties: |- - {{ .Values.log4j | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.log4j "context" $ ) | nindent 4 }} {{- end -}} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/networkpolicy-egress.yaml b/scripts/helmcharts/databases/charts/kafka/templates/networkpolicy-egress.yaml new file mode 100644 index 000000000..068024a0e --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/templates/networkpolicy-egress.yaml @@ -0,0 +1,22 @@ +{{- if and .Values.networkPolicy.enabled .Values.networkPolicy.egressRules.customRules }} +kind: NetworkPolicy +apiVersion: {{ include "common.capabilities.networkPolicy.apiVersion" . }} +metadata: + name: {{ printf "%s-egress" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + podSelector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 6 }} + policyTypes: + - Egress + egress: + {{- include "common.tplvalues.render" (dict "value" .Values.networkPolicy.egressRules.customRules "context" $) | nindent 4 }} +{{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/networkpolicy-ingress.yaml b/scripts/helmcharts/databases/charts/kafka/templates/networkpolicy-ingress.yaml new file mode 100644 index 000000000..258dcabb6 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/templates/networkpolicy-ingress.yaml @@ -0,0 +1,53 @@ +{{- if .Values.networkPolicy.enabled }} +kind: NetworkPolicy +apiVersion: {{ include "common.capabilities.networkPolicy.apiVersion" . }} +metadata: + name: {{ printf "%s-ingress" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + podSelector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 6 }} + policyTypes: + - Ingress + ingress: + # Allow client connections + - ports: + - port: {{ .Values.containerPorts.client }} + {{- if not .Values.networkPolicy.allowExternal }} + from: + - podSelector: + matchLabels: + {{ template "common.names.fullname" . }}-client: "true" + {{- if .Values.networkPolicy.explicitNamespacesSelector }} + namespaceSelector: {{- toYaml .Values.networkPolicy.explicitNamespacesSelector | nindent 12 }} + {{- end }} + {{- end }} + # Allow communication inter-broker + - ports: + - port: {{ .Values.containerPorts.internal }} + from: + - podSelector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 14 }} + # Allow External connection + {{- if .Values.externalAccess.enabled }} + - ports: + - port: {{ .Values.containerPorts.external }} + {{- if .Values.externalAccess.from }} + from: {{- include "common.tplvalues.render" ( dict "value" .Values.networkPolicy.externalAccess.from "context" $ ) | nindent 8 }} + {{- end }} + {{- end }} + {{- if .Values.metrics.kafka.enabled }} + # Allow prometheus scrapes + - ports: + - port: {{ .Values.metrics.kafka.containerPorts.metrics }} + {{- end }} +{{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/poddisruptionbudget.yaml b/scripts/helmcharts/databases/charts/kafka/templates/poddisruptionbudget.yaml old mode 100755 new mode 100644 index cf515becb..e0a60151d --- a/scripts/helmcharts/databases/charts/kafka/templates/poddisruptionbudget.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/poddisruptionbudget.yaml @@ -1,16 +1,17 @@ {{- $replicaCount := int .Values.replicaCount }} {{- if and .Values.pdb.create (gt $replicaCount 1) }} -apiVersion: policy/v1beta1 +apiVersion: {{ include "common.capabilities.policy.apiVersion" . }} kind: PodDisruptionBudget metadata: - name: {{ template "kafka.fullname" . }} - labels: {{- include "kafka.labels" . | nindent 4 }} + name: {{ include "common.names.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} app.kubernetes.io/component: kafka {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} spec: {{- if .Values.pdb.minAvailable }} @@ -20,6 +21,6 @@ spec: maxUnavailable: {{ .Values.pdb.maxUnavailable }} {{- end }} selector: - matchLabels: {{- include "kafka.matchLabels" . | nindent 6 }} + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} app.kubernetes.io/component: kafka {{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/prometheusrule.yaml b/scripts/helmcharts/databases/charts/kafka/templates/prometheusrule.yaml new file mode 100644 index 000000000..bce728a37 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/templates/prometheusrule.yaml @@ -0,0 +1,20 @@ +{{- if and (or .Values.metrics.kafka.enabled .Values.metrics.jmx.enabled) .Values.metrics.prometheusRule.enabled .Values.metrics.prometheusRule.groups }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ include "common.names.fullname" . }} + namespace: {{ default (include "common.names.namespace" .) .Values.metrics.prometheusRule.namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: metrics + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" (dict "value" .Values.commonLabels "context" .) | nindent 4 }} + {{- end }} + {{- if .Values.metrics.prometheusRule.labels }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.prometheusRule.labels "context" .) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" .) | nindent 4 }} + {{- end }} +spec: + groups: {{- include "common.tplvalues.render" (dict "value" .Values.metrics.prometheusRule.groups "context" .) | nindent 4 }} +{{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/role.yaml b/scripts/helmcharts/databases/charts/kafka/templates/role.yaml old mode 100755 new mode 100644 index 943c5bf3c..63215b3b8 --- a/scripts/helmcharts/databases/charts/kafka/templates/role.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/role.yaml @@ -1,15 +1,16 @@ {{- if .Values.rbac.create -}} -apiVersion: rbac.authorization.k8s.io/v1 +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} kind: Role metadata: - name: {{ template "kafka.fullname" . }} - labels: {{- include "kafka.labels" . | nindent 4 }} + name: {{ include "common.names.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} app.kubernetes.io/component: kafka {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} rules: - apiGroups: diff --git a/scripts/helmcharts/databases/charts/kafka/templates/rolebinding.yaml b/scripts/helmcharts/databases/charts/kafka/templates/rolebinding.yaml old mode 100755 new mode 100644 index 78f940f85..fb5e3a157 --- a/scripts/helmcharts/databases/charts/kafka/templates/rolebinding.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/rolebinding.yaml @@ -1,19 +1,20 @@ -{{- if and .Values.serviceAccount.create .Values.rbac.create }} -apiVersion: rbac.authorization.k8s.io/v1 +{{- if .Values.rbac.create }} +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} kind: RoleBinding metadata: - name: {{ template "kafka.fullname" . }} - labels: {{- include "kafka.labels" . | nindent 4 }} + name: {{ include "common.names.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} app.kubernetes.io/component: kafka {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} roleRef: kind: Role - name: {{ template "kafka.fullname" . }} + name: {{ include "common.names.fullname" . }} apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount diff --git a/scripts/helmcharts/databases/charts/kafka/templates/scripts-configmap.yaml b/scripts/helmcharts/databases/charts/kafka/templates/scripts-configmap.yaml old mode 100755 new mode 100644 index 705545a61..57e125053 --- a/scripts/helmcharts/databases/charts/kafka/templates/scripts-configmap.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/scripts-configmap.yaml @@ -1,20 +1,24 @@ apiVersion: v1 kind: ConfigMap metadata: - name: {{ template "kafka.fullname" . }}-scripts - labels: {{- include "kafka.labels" . | nindent 4 }} + name: {{ printf "%s-scripts" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} data: - {{- $fullname := include "kafka.fullname" . }} + {{- $fullname := include "common.names.fullname" . }} {{- $releaseNamespace := .Release.Namespace }} {{- $clusterDomain := .Values.clusterDomain }} - {{- $interBrokerPort := .Values.service.internalPort }} - {{- $clientPort := .Values.service.port }} + {{- $interBrokerPort := .Values.service.ports.internal }} + {{- $clientPort := .Values.service.ports.client }} + {{- $jksTruststoreSecret := .Values.auth.tls.jksTruststoreSecret -}} + {{- $jksTruststore := .Values.auth.tls.jksTruststore -}} + {{- $jksKeystoreSAN := .Values.auth.tls.jksKeystoreSAN -}} {{- if .Values.externalAccess.autoDiscovery.enabled }} auto-discovery.sh: |- #!/bin/bash @@ -22,7 +26,7 @@ data: SVC_NAME="${MY_POD_NAME}-external" {{- if eq .Values.externalAccess.service.type "LoadBalancer" }} - # Auxiliar functions + # Auxiliary functions retry_while() { local -r cmd="${1:?cmd is missing}" local -r retries="${2:-12}" @@ -72,47 +76,127 @@ data: #!/bin/bash ID="${MY_POD_NAME#"{{ $fullname }}-"}" - export KAFKA_CFG_BROKER_ID="$ID" + if [[ -f "{{ .Values.logsDirs | splitList "," | first }}/meta.properties" ]]; then + export KAFKA_CFG_BROKER_ID="$(grep "broker.id" "{{ .Values.logsDirs | splitList "," | first }}/meta.properties" | awk -F '=' '{print $2}')" + else + export KAFKA_CFG_BROKER_ID="$((ID + {{ .Values.minBrokerId }}))" + fi + + {{- if eq .Values.brokerRackAssignment "aws-az" }} + export KAFKA_CFG_BROKER_RACK=$(curl "http://169.254.169.254/latest/meta-data/placement/availability-zone-id") + {{- end }} {{- if .Values.externalAccess.enabled }} # Configure external ip and port {{- if eq .Values.externalAccess.service.type "LoadBalancer" }} {{- if .Values.externalAccess.autoDiscovery.enabled }} - export EXTERNAL_ACCESS_IP="$(<${SHARED_FILE})" + export EXTERNAL_ACCESS_HOST="$(<${SHARED_FILE})" {{- else }} - export EXTERNAL_ACCESS_IP=$(echo '{{ .Values.externalAccess.service.loadBalancerIPs }}' | tr -d '[]' | cut -d ' ' -f "$(($ID + 1))") + export EXTERNAL_ACCESS_HOST=$(echo '{{ .Values.externalAccess.service.loadBalancerNames | default .Values.externalAccess.service.loadBalancerIPs }}' | tr -d '[]' | cut -d ' ' -f "$(($ID + 1))") {{- end }} - export EXTERNAL_ACCESS_PORT={{ .Values.externalAccess.service.port }} + export EXTERNAL_ACCESS_PORT={{ .Values.externalAccess.service.ports.external }} {{- else if eq .Values.externalAccess.service.type "NodePort" }} - {{- if .Values.externalAccess.service.domain }} - export EXTERNAL_ACCESS_IP={{ .Values.externalAccess.service.domain }} + {{- if and .Values.externalAccess.service.usePodIPs .Values.externalAccess.autoDiscovery.enabled }} + export EXTERNAL_ACCESS_HOST="${MY_POD_IP}" + {{- else if or .Values.externalAccess.service.useHostIPs .Values.externalAccess.autoDiscovery.enabled }} + export EXTERNAL_ACCESS_HOST="${HOST_IP}" + {{- else if .Values.externalAccess.service.domain }} + export EXTERNAL_ACCESS_HOST={{ .Values.externalAccess.service.domain }} {{- else }} - export EXTERNAL_ACCESS_IP=$(curl -s https://ipinfo.io/ip) + export EXTERNAL_ACCESS_HOST=$(curl -s https://ipinfo.io/ip) {{- end }} {{- if .Values.externalAccess.autoDiscovery.enabled }} export EXTERNAL_ACCESS_PORT="$(<${SHARED_FILE})" {{- else }} export EXTERNAL_ACCESS_PORT=$(echo '{{ .Values.externalAccess.service.nodePorts }}' | tr -d '[]' | cut -d ' ' -f "$(($ID + 1))") {{- end }} + {{- else }} + export EXTERNAL_ACCESS_HOST={{ .Values.externalAccess.service.domain }} + export EXTERNAL_ACCESS_PORT="$((ID + {{ .Values.externalAccess.service.ports.external }}))" {{- end }} # Configure Kafka advertised listeners {{- if .Values.advertisedListeners }} - export KAFKA_CFG_ADVERTISED_LISTENERS={{ .Values.advertisedListeners }} + export KAFKA_CFG_ADVERTISED_LISTENERS={{ join "," .Values.advertisedListeners }} {{- else }} - export KAFKA_CFG_ADVERTISED_LISTENERS="INTERNAL://${MY_POD_NAME}.{{ $fullname }}-headless.{{ $releaseNamespace }}.svc.{{ $clusterDomain }}:{{ $interBrokerPort }},CLIENT://${MY_POD_NAME}.{{ $fullname }}-headless.{{ $releaseNamespace }}.svc.{{ $clusterDomain }}:{{ $clientPort }},EXTERNAL://${EXTERNAL_ACCESS_IP}:${EXTERNAL_ACCESS_PORT}" + export KAFKA_CFG_ADVERTISED_LISTENERS="INTERNAL://${MY_POD_NAME}.{{ $fullname }}-headless.{{ $releaseNamespace }}.svc.{{ $clusterDomain }}:{{ $interBrokerPort }},CLIENT://${MY_POD_NAME}.{{ $fullname }}-headless.{{ $releaseNamespace }}.svc.{{ $clusterDomain }}:{{ $clientPort }},EXTERNAL://${EXTERNAL_ACCESS_HOST}:${EXTERNAL_ACCESS_PORT}" {{- end }} {{- end }} {{- if (include "kafka.tlsEncryption" .) }} - if [[ -f "/certs/kafka.truststore.jks" ]] && [[ -f "/certs/kafka-${ID}.keystore.jks" ]]; then - mkdir -p /opt/bitnami/kafka/config/certs - cp "/certs/kafka.truststore.jks" "/opt/bitnami/kafka/config/certs/kafka.truststore.jks" - cp "/certs/kafka-${ID}.keystore.jks" "/opt/bitnami/kafka/config/certs/kafka.keystore.jks" + mkdir -p /opt/bitnami/kafka/config/certs + {{- if eq .Values.auth.tls.type "jks" }} + {{- if not (empty .Values.auth.tls.existingSecrets) }} + JKS_TRUSTSTORE={{ printf "/%s/%s" (ternary "certs-${ID}" "truststore" (empty $jksTruststoreSecret)) (default "kafka.truststore.jks" $jksTruststore) | quote }} + JKS_KEYSTORE={{ printf "/certs-${ID}/%s" (default "kafka.keystore.jks" $jksKeystoreSAN) | quote }} + {{- else }} + JKS_TRUSTSTORE={{ printf "/%s/%s" (ternary "certs" "truststore" (empty $jksTruststoreSecret)) (default "kafka.truststore.jks" $jksTruststore) | quote }} + JKS_KEYSTORE={{ printf "/certs/%s" (default "kafka-${ID}.keystore.jks" $jksKeystoreSAN) | quote }} + {{- end }} + if [[ -f "$JKS_TRUSTSTORE" ]] && [[ -f "$JKS_KEYSTORE" ]]; then + cp "$JKS_TRUSTSTORE" "/opt/bitnami/kafka/config/certs/kafka.truststore.jks" + cp "$JKS_KEYSTORE" "/opt/bitnami/kafka/config/certs/kafka.keystore.jks" else echo "Couldn't find the expected Java Key Stores (JKS) files! They are mandatory when encryption via TLS is enabled." exit 1 fi + export KAFKA_TLS_TRUSTSTORE_FILE="/opt/bitnami/kafka/config/certs/kafka.truststore.jks" + + {{- else if eq .Values.auth.tls.type "pem" }} + + {{- if or (not (empty .Values.auth.tls.existingSecrets)) .Values.auth.tls.autoGenerated }} + PEM_CA="/certs-${ID}/ca.crt" + PEM_CERT="/certs-${ID}/tls.crt" + PEM_KEY="/certs-${ID}/tls.key" + {{- else }} + PEM_CA="/certs/kafka.truststore.pem" + PEM_CERT="/certs/kafka-${ID}.keystore.pem" + PEM_KEY="/certs/kafka-${ID}.keystore.key" + {{- end }} + if [[ -f "$PEM_CERT" ]] && [[ -f "$PEM_KEY" ]]; then + CERT_DIR="/opt/bitnami/kafka/config/certs" + PEM_CA_LOCATION="${CERT_DIR}/kafka.truststore.pem" + PEM_CERT_LOCATION="${CERT_DIR}/kafka.keystore.pem" + {{- if .Values.auth.tls.pemChainIncluded }} + cat $PEM_CERT | csplit - -s -z '/\-*END CERTIFICATE\-*/+1' '{*}' -f ${CERT_DIR}/xx + FIND_CA_RESULT=$(find ${CERT_DIR} -not -name 'xx00' -name 'xx*') + if [[ $(echo $FIND_CA_RESULT | wc -l) < 1 ]]; then + echo "auth.tls.pemChainIncluded was set, but PEM chain only contained 1 cert" + exit 1 + fi + echo $FIND_CA_RESULT | sort | xargs cat >> "$PEM_CA_LOCATION" + cat ${CERT_DIR}/xx00 > "$PEM_CERT_LOCATION" + {{- else }} + if [[ -f "$PEM_CA" ]]; then + cp "$PEM_CA" "$PEM_CA_LOCATION" + cp "$PEM_CERT" "$PEM_CERT_LOCATION" + else + echo "PEM_CA not provided, and auth.tls.pemChainIncluded was not true. One of these values must be set when using PEM type for TLS." + exit 1 + fi + {{- end }} + + # Ensure the key used PEM format with PKCS#8 + openssl pkcs8 -topk8 -nocrypt -in "$PEM_KEY" > "/opt/bitnami/kafka/config/certs/kafka.keystore.key" + else + echo "Couldn't find the expected PEM files! They are mandatory when encryption via TLS is enabled." + exit 1 + fi + export KAFKA_TLS_TRUSTSTORE_FILE="/opt/bitnami/kafka/config/certs/kafka.truststore.pem" + {{- end }} + {{- end }} + + # Configure zookeeper client + {{- if and (not (empty .Values.auth.zookeeper.tls.existingSecret)) .Values.auth.zookeeper.tls.enabled }} + JKS_TRUSTSTORE={{ printf "/kafka-zookeeper-cert/%s" (.Values.auth.zookeeper.tls.existingSecretTruststoreKey) | quote }} + JKS_KEYSTORE={{ printf "/kafka-zookeeper-cert/%s" (.Values.auth.zookeeper.tls.existingSecretKeystoreKey) | quote }} + if [[ -f "$JKS_TRUSTSTORE" ]] && [[ -f "$JKS_KEYSTORE" ]]; then + CERT_DIR="/opt/bitnami/kafka/config/certs" + TRUSTSTORE_LOCATION="${CERT_DIR}/zookeeper.truststore.jks" + cp "$JKS_TRUSTSTORE" "$TRUSTSTORE_LOCATION" + cp "$JKS_KEYSTORE" "${CERT_DIR}/zookeeper.keystore.jks" + export KAFKA_ZOOKEEPER_TLS_TRUSTSTORE_FILE="${TRUSTSTORE_LOCATION}" + fi {{- end }} exec /entrypoint.sh /run.sh diff --git a/scripts/helmcharts/databases/charts/kafka/templates/serviceaccount.yaml b/scripts/helmcharts/databases/charts/kafka/templates/serviceaccount.yaml old mode 100755 new mode 100644 index 790790b3f..73091f5d7 --- a/scripts/helmcharts/databases/charts/kafka/templates/serviceaccount.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/serviceaccount.yaml @@ -3,12 +3,18 @@ apiVersion: v1 kind: ServiceAccount metadata: name: {{ template "kafka.serviceAccountName" . }} - labels: {{- include "kafka.labels" . | nindent 4 }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} app.kubernetes.io/component: kafka {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} - {{- if .Values.commonAnnotations }} - annotations: {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} - {{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.serviceAccount.annotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.serviceAccount.annotations "context" $ ) | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} {{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/servicemonitor-jmx-metrics.yaml b/scripts/helmcharts/databases/charts/kafka/templates/servicemonitor-jmx-metrics.yaml old mode 100755 new mode 100644 index 250bb5306..1919feebb --- a/scripts/helmcharts/databases/charts/kafka/templates/servicemonitor-jmx-metrics.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/servicemonitor-jmx-metrics.yaml @@ -2,25 +2,33 @@ apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: - name: {{ template "kafka.fullname" . }}-jmx-metrics + name: {{ printf "%s-jmx-metrics" (include "common.names.fullname" .) }} {{- if .Values.metrics.serviceMonitor.namespace }} namespace: {{ .Values.metrics.serviceMonitor.namespace }} + {{- else }} + namespace: {{ .Release.Namespace | quote }} {{- end }} - labels: {{- include "kafka.labels" . | nindent 4 }} - app.kubernetes.io/component: kafka - {{- range $key, $value := .Values.metrics.serviceMonitor.selector }} - {{ $key }}: {{ $value | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: metrics + {{- if .Values.metrics.serviceMonitor.labels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.serviceMonitor.labels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} spec: + {{- if .Values.metrics.serviceMonitor.jobLabel }} + jobLabel: {{ .Values.metrics.serviceMonitor.jobLabel }} + {{- end }} selector: - matchLabels: {{- include "kafka.matchLabels" . | nindent 6 }} - app.kubernetes.io/component: kafka + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + {{- if .Values.metrics.serviceMonitor.selector }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.serviceMonitor.selector "context" $) | nindent 6 }} + {{- end }} + app.kubernetes.io/component: metrics endpoints: - port: http-metrics path: "/" @@ -30,6 +38,15 @@ spec: {{- if .Values.metrics.serviceMonitor.scrapeTimeout }} scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }} {{- end }} + {{- if .Values.metrics.serviceMonitor.relabelings }} + relabelings: {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.serviceMonitor.relabelings "context" $) | nindent 6 }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.metricRelabelings }} + metricRelabelings: {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.serviceMonitor.metricRelabelings "context" $) | nindent 6 }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.honorLabels }} + honorLabels: {{ .Values.metrics.serviceMonitor.honorLabels }} + {{- end }} namespaceSelector: matchNames: - {{ .Release.Namespace }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/servicemonitor-metrics.yaml b/scripts/helmcharts/databases/charts/kafka/templates/servicemonitor-metrics.yaml old mode 100755 new mode 100644 index 951bf7c41..343194667 --- a/scripts/helmcharts/databases/charts/kafka/templates/servicemonitor-metrics.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/servicemonitor-metrics.yaml @@ -2,25 +2,33 @@ apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: - name: {{ template "kafka.fullname" . }}-metrics + name: {{ printf "%s-metrics" (include "common.names.fullname" .) }} {{- if .Values.metrics.serviceMonitor.namespace }} namespace: {{ .Values.metrics.serviceMonitor.namespace }} + {{- else }} + namespace: {{ .Release.Namespace | quote }} {{- end }} - labels: {{- include "kafka.labels" . | nindent 4 }} - app.kubernetes.io/component: metrics - {{- range $key, $value := .Values.metrics.serviceMonitor.selector }} - {{ $key }}: {{ $value | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: cluster-metrics + {{- if .Values.metrics.serviceMonitor.labels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.serviceMonitor.labels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} spec: + {{- if .Values.metrics.serviceMonitor.jobLabel }} + jobLabel: {{ .Values.metrics.serviceMonitor.jobLabel }} + {{- end }} selector: - matchLabels: {{- include "kafka.matchLabels" . | nindent 6 }} - app.kubernetes.io/component: metrics + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + {{- if .Values.metrics.serviceMonitor.selector }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.serviceMonitor.selector "context" $) | nindent 6 }} + {{- end }} + app.kubernetes.io/component: cluster-metrics endpoints: - port: http-metrics path: "/metrics" @@ -30,6 +38,15 @@ spec: {{- if .Values.metrics.serviceMonitor.scrapeTimeout }} scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }} {{- end }} + {{- if .Values.metrics.serviceMonitor.relabelings }} + relabelings: {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.serviceMonitor.relabelings "context" $) | nindent 6 }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.metricRelabelings }} + metricRelabelings: {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.serviceMonitor.metricRelabelings "context" $) | nindent 6 }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.honorLabels }} + honorLabels: {{ .Values.metrics.serviceMonitor.honorLabels }} + {{- end }} namespaceSelector: matchNames: - {{ .Release.Namespace }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/statefulset.yaml b/scripts/helmcharts/databases/charts/kafka/templates/statefulset.yaml old mode 100755 new mode 100644 index e9b5ce8f9..9532a5734 --- a/scripts/helmcharts/databases/charts/kafka/templates/statefulset.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/statefulset.yaml @@ -1,54 +1,47 @@ {{- $replicaCount := int .Values.replicaCount }} -{{- $fullname := include "kafka.fullname" . }} +{{- $fullname := include "common.names.fullname" . }} {{- $releaseNamespace := .Release.Namespace }} {{- $clusterDomain := .Values.clusterDomain }} -{{- $interBrokerPort := .Values.service.internalPort }} -{{- $clientPort := .Values.service.port }} -{{- $interBrokerProtocol := include "kafka.listenerType" ( dict "protocol" .Values.auth.interBrokerProtocol ) -}} -{{- $clientProtocol := include "kafka.listenerType" ( dict "protocol" .Values.auth.clientProtocol ) -}} -{{- $loadBalancerIPListLength := len .Values.externalAccess.service.loadBalancerIPs }} -{{- if not (and .Values.externalAccess.enabled (not .Values.externalAccess.autoDiscovery.enabled) (not (eq $replicaCount $loadBalancerIPListLength )) (eq .Values.externalAccess.service.type "LoadBalancer")) }} -apiVersion: apps/v1 +{{- $interBrokerProtocol := include "kafka.listenerType" (dict "protocol" .Values.auth.interBrokerProtocol) -}} +{{- $clientProtocol := include "kafka.listenerType" (dict "protocol" .Values.auth.clientProtocol) -}} +{{- $externalClientProtocol := include "kafka.listenerType" (dict "protocol" (include "kafka.externalClientProtocol" . )) -}} +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} kind: StatefulSet metadata: - name: {{ include "kafka.fullname" . }} - labels: {{- include "kafka.labels" . | nindent 4 }} + name: {{ include "common.names.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} app.kubernetes.io/component: kafka {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - annotations: {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} spec: - podManagementPolicy: Parallel + podManagementPolicy: {{ .Values.podManagementPolicy }} replicas: {{ .Values.replicaCount }} selector: - matchLabels: {{- include "kafka.matchLabels" . | nindent 6 }} + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} app.kubernetes.io/component: kafka - serviceName: {{ template "kafka.fullname" . }}-headless - updateStrategy: - type: {{ .Values.updateStrategy | quote }} - {{- if (eq "OnDelete" .Values.updateStrategy) }} - rollingUpdate: null - {{- else if .Values.rollingUpdatePartition }} - rollingUpdate: - partition: {{ .Values.rollingUpdatePartition }} - {{- end }} + serviceName: {{ printf "%s-headless" (include "common.names.fullname" .) | trunc 63 | trimSuffix "-" }} + updateStrategy: {{- include "common.tplvalues.render" (dict "value" .Values.updateStrategy "context" $ ) | nindent 4 }} template: metadata: - labels: {{- include "kafka.labels" . | nindent 8 }} + labels: {{- include "common.labels.standard" . | nindent 8 }} app.kubernetes.io/component: kafka {{- if .Values.podLabels }} - {{- include "kafka.tplValue" (dict "value" .Values.podLabels "context" $) | nindent 8 }} + {{- include "common.tplvalues.render" (dict "value" .Values.podLabels "context" $) | nindent 8 }} {{- end }} - {{- if or (include "kafka.createConfigmap" .) (include "kafka.createJaasSecret" .) .Values.externalAccess.enabled (include "kafka.metrics.jmx.createConfigmap" .) .Values.podAnnotations }} annotations: {{- if (include "kafka.createConfigmap" .) }} checksum/configuration: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} {{- end }} {{- if (include "kafka.createJaasSecret" .) }} - checksum/secret: {{ include (print $.Template.BasePath "/jaas-secret.yaml") . | sha256sum }} + checksum/jaas-secret: {{ include (print $.Template.BasePath "/jaas-secret.yaml") . | sha256sum }} + {{- end }} + {{- if (include "kafka.createTlsSecret" .) }} + checksum/tls-secret: {{ include (print $.Template.BasePath "/tls-secrets.yaml") . | sha256sum }} {{- end }} {{- if .Values.externalAccess.enabled }} checksum/scripts: {{ include (print $.Template.BasePath "/scripts-configmap.yaml") . | sha256sum }} @@ -57,30 +50,46 @@ spec: checksum/jmx-configuration: {{ include (print $.Template.BasePath "/jmx-configmap.yaml") . | sha256sum }} {{- end }} {{- if .Values.podAnnotations }} - {{- include "kafka.tplValue" (dict "value" .Values.podAnnotations "context" $) | nindent 8 }} + {{- include "common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) | nindent 8 }} {{- end }} - {{- end }} spec: -{{- include "kafka.imagePullSecrets" . | indent 6 }} + {{- include "kafka.imagePullSecrets" . | nindent 6 }} + {{- if .Values.hostAliases }} + hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.hostAliases "context" $) | nindent 8 }} + {{- end }} + hostNetwork: {{ .Values.hostNetwork }} + hostIPC: {{ .Values.hostIPC }} + {{- if .Values.schedulerName }} + schedulerName: {{ .Values.schedulerName | quote }} + {{- end }} {{- if .Values.affinity }} - affinity: {{- include "kafka.tplValue" ( dict "value" .Values.affinity "context" $ ) | nindent 8 }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAffinityPreset "component" "kafka" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAntiAffinityPreset "component" "kafka" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.nodeAffinityPreset.type "key" .Values.nodeAffinityPreset.key "values" .Values.nodeAffinityPreset.values) | nindent 10 }} {{- end }} {{- if .Values.nodeSelector }} - nodeSelector: {{- include "kafka.tplValue" ( dict "value" .Values.nodeSelector "context" $ ) | nindent 8 }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.nodeSelector "context" $) | nindent 8 }} {{- end }} {{- if .Values.tolerations }} - tolerations: {{- include "kafka.tplValue" ( dict "value" .Values.tolerations "context" $ ) | nindent 8 }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "common.tplvalues.render" (dict "value" .Values.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.terminationGracePeriodSeconds }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} {{- end }} {{- if .Values.priorityClassName }} priorityClassName: {{ .Values.priorityClassName }} {{- end }} - {{- if .Values.podSecurityContext }} - securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- if .Values.podSecurityContext.enabled }} + securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }} {{- end }} - {{- if .Values.serviceAccount.create }} - serviceAccountName: {{ template "kafka.serviceAccountName" . }} - {{- end }} - {{- if or (and .Values.volumePermissions.enabled .Values.persistence.enabled) (and .Values.externalAccess.enabled .Values.externalAccess.autoDiscovery.enabled) }} + serviceAccountName: {{ include "kafka.serviceAccountName" . }} + {{- if or (and .Values.volumePermissions.enabled .Values.persistence.enabled) (and .Values.externalAccess.enabled .Values.externalAccess.autoDiscovery.enabled) .Values.initContainers }} initContainers: {{- if and .Values.volumePermissions.enabled .Values.persistence.enabled }} - name: volume-permissions @@ -91,16 +100,23 @@ spec: args: - -ec - | - mkdir -p /bitnami/kafka - chown -R "{{ .Values.podSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.fsGroup }}" "/bitnami/kafka" - securityContext: - runAsUser: 0 + mkdir -p "{{ .Values.persistence.mountPath }}" "{{ .Values.logPersistence.mountPath }}" + chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.fsGroup }} "{{ .Values.persistence.mountPath }}" "{{ .Values.logPersistence.mountPath }}" + find "{{ .Values.persistence.mountPath }}" -mindepth 1 -maxdepth 1 -not -name ".snapshot" -not -name "lost+found" | xargs -r chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.fsGroup }} + find "{{ .Values.logPersistence.mountPath }}" -mindepth 1 -maxdepth 1 -not -name ".snapshot" -not -name "lost+found" | xargs -r chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.fsGroup }} + {{- if eq ( toString ( .Values.volumePermissions.containerSecurityContext.runAsUser )) "auto" }} + securityContext: {{- omit .Values.volumePermissions.containerSecurityContext "runAsUser" | toYaml | nindent 12 }} + {{- else }} + securityContext: {{- .Values.volumePermissions.containerSecurityContext | toYaml | nindent 12 }} + {{- end }} {{- if .Values.volumePermissions.resources }} resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }} {{- end }} volumeMounts: - name: data - mountPath: /bitnami/kafka + mountPath: {{ .Values.persistence.mountPath }} + - name: logs + mountPath: {{ .Values.logPersistence.mountPath }} {{- end }} {{- if and .Values.externalAccess.enabled .Values.externalAccess.autoDiscovery.enabled }} - name: auto-discovery @@ -121,22 +137,36 @@ spec: volumeMounts: - name: shared mountPath: /shared + - name: logs + mountPath: {{ .Values.logPersistence.mountPath }} - name: scripts mountPath: /scripts/auto-discovery.sh subPath: auto-discovery.sh {{- end }} + {{- if .Values.initContainers }} + {{- include "common.tplvalues.render" ( dict "value" .Values.initContainers "context" $ ) | nindent 8 }} + {{- end }} {{- end }} containers: - name: kafka image: {{ include "kafka.image" . }} imagePullPolicy: {{ .Values.image.pullPolicy | quote }} - command: {{- include "kafka.tplValue" (dict "value" .Values.command "context" $) | nindent 12 }} - {{- if .Values.args }} - args: {{- include "kafka.tplValue" (dict "value" .Values.args "context" $) | nindent 12 }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else if .Values.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else if .Values.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.args "context" $) | nindent 12 }} {{- end }} env: - name: BITNAMI_DEBUG - value: {{ ternary "true" "false" .Values.image.debug | quote }} + value: {{ ternary "true" "false" (or .Values.image.debug .Values.diagnosticMode.enabled) | quote }} - name: MY_POD_IP valueFrom: fieldRef: @@ -147,9 +177,9 @@ spec: fieldPath: metadata.name - name: KAFKA_CFG_ZOOKEEPER_CONNECT {{- if .Values.zookeeper.enabled }} - value: {{ include "kafka.zookeeper.fullname" . | quote }} + value: {{ printf "%s%s" (include "kafka.zookeeper.fullname" .) (tpl .Values.zookeeperChrootPath .) | quote }} {{- else }} - value: {{ join "," .Values.externalZookeeper.servers | quote }} + value: {{ include "common.tplvalues.render" (dict "value" (printf "%s%s" (join "," .Values.externalZookeeper.servers) (tpl .Values.zookeeperChrootPath .)) "context" $) }} {{- end }} - name: KAFKA_INTER_BROKER_LISTENER_NAME value: {{ .Values.interBrokerListenerName | quote }} @@ -157,65 +187,60 @@ spec: {{- if .Values.listenerSecurityProtocolMap }} value: {{ .Values.listenerSecurityProtocolMap | quote }} {{- else if .Values.externalAccess.enabled }} - value: "INTERNAL:{{ $interBrokerProtocol }},CLIENT:{{ $clientProtocol }},EXTERNAL:{{ $clientProtocol }}" + value: "INTERNAL:{{ $interBrokerProtocol }},CLIENT:{{ $clientProtocol }},EXTERNAL:{{ $externalClientProtocol }}" {{- else }} value: "INTERNAL:{{ $interBrokerProtocol }},CLIENT:{{ $clientProtocol }}" {{- end }} - {{- if or ($clientProtocol | regexFind "SASL") ($interBrokerProtocol | regexFind "SASL") .Values.auth.jaas.zookeeperUser }} + {{- if or ($clientProtocol | regexFind "SASL") ($externalClientProtocol | regexFind "SASL") ($interBrokerProtocol | regexFind "SASL") .Values.auth.sasl.jaas.zookeeperUser }} - name: KAFKA_CFG_SASL_ENABLED_MECHANISMS - value: {{ include "kafka.auth.saslMechanisms" ( dict "type" .Values.auth.saslMechanisms ) }} + value: {{ upper .Values.auth.sasl.mechanisms | quote }} - name: KAFKA_CFG_SASL_MECHANISM_INTER_BROKER_PROTOCOL - value: {{ upper .Values.auth.saslInterBrokerMechanism | quote }} + value: {{ upper .Values.auth.sasl.interBrokerMechanism | quote }} {{- end }} - name: KAFKA_CFG_LISTENERS {{- if .Values.listeners }} - value: {{ .Values.listeners }} + value: {{ join "," .Values.listeners }} {{- else if .Values.externalAccess.enabled }} - value: "INTERNAL://:{{ $interBrokerPort }},CLIENT://:9092,EXTERNAL://:9094" + value: "INTERNAL://:{{ .Values.containerPorts.internal }},CLIENT://:{{ .Values.containerPorts.client }},EXTERNAL://:{{ .Values.containerPorts.external }}" {{- else }} - value: "INTERNAL://:{{ $interBrokerPort }},CLIENT://:9092" + value: "INTERNAL://:{{ .Values.containerPorts.internal }},CLIENT://:{{ .Values.containerPorts.client }}" {{- end }} {{- if .Values.externalAccess.enabled }} {{- if .Values.externalAccess.autoDiscovery.enabled }} - name: SHARED_FILE value: "/shared/info.txt" {{- end }} + {{- if eq .Values.externalAccess.service.type "NodePort" }} + - name: HOST_IP + valueFrom: + fieldRef: + fieldPath: status.hostIP + {{- end }} {{- else }} - name: KAFKA_CFG_ADVERTISED_LISTENERS {{- if .Values.advertisedListeners }} - value: {{ .Values.advertisedListeners }} + value: {{ join "," .Values.advertisedListeners }} {{- else }} - value: "INTERNAL://$(MY_POD_NAME).{{ $fullname }}-headless.{{ $releaseNamespace }}.svc.{{ $clusterDomain }}:{{ $interBrokerPort }},CLIENT://$(MY_POD_NAME).{{ $fullname }}-headless.{{ $releaseNamespace }}.svc.{{ $clusterDomain }}:{{ $clientPort }}" + value: "INTERNAL://$(MY_POD_NAME).{{ $fullname }}-headless.{{ $releaseNamespace }}.svc.{{ $clusterDomain }}:{{ .Values.service.ports.internal }},CLIENT://$(MY_POD_NAME).{{ $fullname }}-headless.{{ $releaseNamespace }}.svc.{{ $clusterDomain }}:{{ .Values.service.ports.client }}" {{- end }} {{- end }} - name: ALLOW_PLAINTEXT_LISTENER - value: {{ ternary "yes" "no" (or .Values.auth.enabled .Values.allowPlaintextListener) | quote }} + value: {{ ternary "yes" "no" .Values.allowPlaintextListener | quote }} {{- if or (include "kafka.client.saslAuthentication" .) (include "kafka.interBroker.saslAuthentication" .) }} - name: KAFKA_OPTS value: "-Djava.security.auth.login.config=/opt/bitnami/kafka/config/kafka_jaas.conf" {{- if (include "kafka.client.saslAuthentication" .) }} - name: KAFKA_CLIENT_USERS - value: {{ join "," .Values.auth.jaas.clientUsers | quote }} + value: {{ join "," .Values.auth.sasl.jaas.clientUsers | quote }} - name: KAFKA_CLIENT_PASSWORDS valueFrom: secretKeyRef: name: {{ include "kafka.jaasSecretName" . }} key: client-passwords {{- end }} - {{- if .Values.auth.jaas.zookeeperUser }} - - name: KAFKA_ZOOKEEPER_PROTOCOL - value: "SASL" - - name: KAFKA_ZOOKEEPER_USER - value: {{ .Values.auth.jaas.zookeeperUser | quote }} - - name: KAFKA_ZOOKEEPER_PASSWORD - valueFrom: - secretKeyRef: - name: {{ include "kafka.jaasSecretName" . }} - key: zookeeper-password - {{- end }} {{- if (include "kafka.interBroker.saslAuthentication" .) }} - name: KAFKA_INTER_BROKER_USER - value: {{ .Values.auth.jaas.interBrokerUser | quote }} + value: {{ .Values.auth.sasl.jaas.interBrokerUser | quote }} - name: KAFKA_INTER_BROKER_PASSWORD valueFrom: secretKeyRef: @@ -223,18 +248,60 @@ spec: key: inter-broker-password {{- end }} {{- end }} - {{- if (include "kafka.tlsEncryption" .) }} - - name: KAFKA_CFG_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM - value: {{ .Values.auth.tlsEndpointIdentificationAlgorithm | quote }} - {{- if .Values.auth.jksPassword }} - - name: KAFKA_CERTIFICATE_PASSWORD - value: {{ .Values.auth.jksPassword | quote }} + {{- if and .Values.zookeeper.auth.client.enabled .Values.auth.sasl.jaas.zookeeperUser }} + - name: KAFKA_ZOOKEEPER_USER + value: {{ .Values.auth.sasl.jaas.zookeeperUser | quote }} + - name: KAFKA_ZOOKEEPER_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "kafka.jaasSecretName" . }} + key: zookeeper-password {{- end }} + - name: KAFKA_ZOOKEEPER_PROTOCOL + value: {{ include "kafka.zookeeper.protocol" . }} + {{- if .Values.auth.zookeeper.tls.enabled }} + - name: KAFKA_ZOOKEEPER_TLS_TYPE + value: {{ upper .Values.auth.zookeeper.tls.type | quote }} + - name: KAFKA_ZOOKEEPER_TLS_VERIFY_HOSTNAME + value: {{ .Values.auth.zookeeper.tls.verifyHostname | quote }} + {{- if .Values.auth.zookeeper.tls.passwordsSecret }} + - name: KAFKA_ZOOKEEPER_TLS_KEYSTORE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.auth.zookeeper.tls.passwordsSecret }} + key: {{ .Values.auth.zookeeper.tls.passwordsSecretKeystoreKey | quote }} + - name: KAFKA_ZOOKEEPER_TLS_TRUSTSTORE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.auth.zookeeper.tls.passwordsSecret }} + key: {{ .Values.auth.zookeeper.tls.passwordsSecretTruststoreKey | quote }} + {{- end }} + {{- end }} + {{- if (include "kafka.tlsEncryption" .) }} + - name: KAFKA_TLS_TYPE + value: {{ upper .Values.auth.tls.type | quote }} + - name: KAFKA_CFG_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM + value: {{ default "" .Values.auth.tls.endpointIdentificationAlgorithm | quote }} + - name: KAFKA_TLS_CLIENT_AUTH + value: {{ ternary "required" "none" (or (eq (include "kafka.externalClientProtocol" . ) "mtls") (eq .Values.auth.clientProtocol "mtls")) | quote }} + - name: KAFKA_CERTIFICATE_PASSWORD + {{- if .Values.auth.tls.existingSecret }} + valueFrom: + secretKeyRef: + name: {{ .Values.auth.tls.existingSecret }} + key: password + {{- else }} + value: {{ default "" .Values.auth.tls.password | quote }} + {{- end }} {{- end }} {{- if .Values.metrics.jmx.enabled }} - name: JMX_PORT value: "5555" {{- end }} + - name: KAFKA_VOLUME_DIR + value: {{ .Values.persistence.mountPath | quote }} + - name: KAFKA_LOG_DIR + value: {{ .Values.logPersistence.mountPath | quote }} - name: KAFKA_CFG_DELETE_TOPIC_ENABLE value: {{ .Values.deleteTopicEnable | quote }} - name: KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE @@ -242,12 +309,12 @@ spec: - name: KAFKA_HEAP_OPTS value: {{ .Values.heapOpts | quote }} - name: KAFKA_CFG_LOG_FLUSH_INTERVAL_MESSAGES - value: {{ .Values.logFlushIntervalMessages | quote }} + value: {{ .Values.logFlushIntervalMessages | replace "_" "" | quote }} - name: KAFKA_CFG_LOG_FLUSH_INTERVAL_MS value: {{ .Values.logFlushIntervalMs | quote }} - name: KAFKA_CFG_LOG_RETENTION_BYTES value: {{ .Values.logRetentionBytes | replace "_" "" | quote }} - - name: KAFKA_CFG_LOG_RETENTION_CHECK_INTERVALS_MS + - name: KAFKA_CFG_LOG_RETENTION_CHECK_INTERVAL_MS value: {{ .Values.logRetentionCheckIntervalMs | quote }} - name: KAFKA_CFG_LOG_RETENTION_HOURS value: {{ .Values.logRetentionHours | quote }} @@ -281,56 +348,77 @@ spec: value: {{ .Values.socketSendBufferBytes | quote }} - name: KAFKA_CFG_ZOOKEEPER_CONNECTION_TIMEOUT_MS value: {{ .Values.zookeeperConnectionTimeoutMs | quote }} + - name: KAFKA_CFG_AUTHORIZER_CLASS_NAME + value: {{ .Values.authorizerClassName | quote }} + - name: KAFKA_CFG_ALLOW_EVERYONE_IF_NO_ACL_FOUND + value: {{ .Values.allowEveryoneIfNoAclFound | quote }} + - name: KAFKA_CFG_SUPER_USERS + value: {{ .Values.superUsers | quote }} {{- if .Values.extraEnvVars }} - {{ include "kafka.tplValue" ( dict "value" .Values.extraEnvVars "context" $) | nindent 12 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.extraEnvVars "context" $) | nindent 12 }} {{- end }} + {{- if or .Values.extraEnvVarsCM .Values.extraEnvVarsSecret }} + envFrom: + {{- if .Values.extraEnvVarsCM }} + - configMapRef: + name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsCM "context" $) }} + {{- end }} + {{- if .Values.extraEnvVarsSecret }} + - secretRef: + name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsSecret "context" $) }} + {{- end }} + {{- end }} ports: - name: kafka-client - containerPort: 9092 + containerPort: {{ .Values.containerPorts.client }} - name: kafka-internal - containerPort: {{ $interBrokerPort }} + containerPort: {{ .Values.containerPorts.internal }} {{- if .Values.externalAccess.enabled }} - name: kafka-external - containerPort: 9094 + containerPort: {{ .Values.containerPorts.external }} {{- end }} - {{- if .Values.livenessProbe.enabled }} - livenessProbe: + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.customLivenessProbe }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customLivenessProbe "context" $) | nindent 12 }} + {{- else if .Values.livenessProbe.enabled }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" (omit .Values.livenessProbe "enabled") "context" $) | nindent 12 }} tcpSocket: port: kafka-client - initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} - timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} - failureThreshold: {{ .Values.livenessProbe.failureThreshold }} - periodSeconds: {{ .Values.livenessProbe.periodSeconds }} - successThreshold: {{ .Values.livenessProbe.successThreshold }} - {{- else if .Values.customLivenessProbe }} - livenessProbe: {{- include "kafka.tplValue" (dict "value" .Values.customlivenessProbe "context" $) | nindent 12 }} {{- end }} - {{- if .Values.readinessProbe.enabled }} - readinessProbe: + {{- if .Values.customReadinessProbe }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customReadinessProbe "context" $) | nindent 12 }} + {{- else if .Values.readinessProbe.enabled }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" (omit .Values.readinessProbe "enabled") "context" $) | nindent 12 }} tcpSocket: port: kafka-client - initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} - timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} - failureThreshold: {{ .Values.readinessProbe.failureThreshold }} - periodSeconds: {{ .Values.readinessProbe.periodSeconds }} - successThreshold: {{ .Values.readinessProbe.successThreshold }} - {{- else if .Values.customReadinessProbe }} - readinessProbe: {{- include "kafka.tplValue" (dict "value" .Values.customreadinessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.customStartupProbe }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customStartupProbe "context" $) | nindent 12 }} + {{- else if .Values.startupProbe.enabled }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" (omit .Values.startupProbe "enabled") "context" $) | nindent 12 }} + tcpSocket: + port: kafka-client + {{- end }} + {{- end }} + {{- if .Values.lifecycleHooks }} + lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.lifecycleHooks "context" $) | nindent 12 }} {{- end }} {{- if .Values.resources }} resources: {{- toYaml .Values.resources | nindent 12 }} {{- end }} volumeMounts: - name: data - mountPath: /bitnami/kafka + mountPath: {{ .Values.persistence.mountPath }} + - name: logs + mountPath: {{ .Values.logPersistence.mountPath }} {{- if or .Values.config .Values.existingConfigmap }} - name: kafka-config - mountPath: /bitnami/kafka/config/server.properties + mountPath: {{ .Values.persistence.mountPath }}/config/server.properties subPath: server.properties {{- end }} {{- if or .Values.log4j .Values.existingLog4jConfigMap }} - name: log4j-config - mountPath: /bitnami/kafka/config/log4j.properties + mountPath: {{ .Values.persistence.mountPath }}/config/log4j.properties subPath: log4j.properties {{- end }} - name: scripts @@ -341,30 +429,57 @@ spec: mountPath: /shared {{- end }} {{- if (include "kafka.tlsEncryption" .) }} - - name: kafka-certificates - mountPath: /certs + {{- if not (empty .Values.auth.tls.existingSecrets) }} + {{- range $index, $_ := .Values.auth.tls.existingSecrets }} + - name: kafka-certs-{{ $index }} + mountPath: /certs-{{ $index }} readOnly: true {{- end }} + {{- else if .Values.auth.tls.autoGenerated }} + {{- range $index := until $replicaCount }} + - name: kafka-certs-{{ $index }} + mountPath: /certs-{{ $index }} + readOnly: true + {{- end }} + {{- end }} + {{- if and .Values.auth.zookeeper.tls.enabled .Values.auth.zookeeper.tls.existingSecret }} + - name: kafka-zookeeper-cert + mountPath: /kafka-zookeeper-cert + readOnly: true + {{- end }} + {{- if .Values.auth.tls.jksTruststoreSecret }} + - name: kafka-truststore + mountPath: /truststore + readOnly: true + {{- end }} + {{- end }} {{- if .Values.extraVolumeMounts }} - {{- toYaml .Values.extraVolumeMounts | nindent 12 }} + {{- include "common.tplvalues.render" (dict "value" .Values.extraVolumeMounts "context" $) | nindent 12 }} {{- end }} {{- if .Values.metrics.jmx.enabled }} - name: jmx-exporter - image: {{ template "kafka.metrics.jmx.image" . }} + image: {{ include "kafka.metrics.jmx.image" . }} imagePullPolicy: {{ .Values.metrics.jmx.image.pullPolicy | quote }} + {{- if .Values.metrics.jmx.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.metrics.jmx.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else }} command: - java - - -XX:+UnlockExperimentalVMOptions - - -XX:+UseCGroupMemoryLimitForHeap - - -XX:MaxRAMFraction=1 + args: + - -XX:MaxRAMPercentage=100 - -XshowSettings:vm - -jar - jmx_prometheus_httpserver.jar - "5556" - /etc/jmx-kafka/jmx-kafka-prometheus.yml + {{- end }} ports: - name: metrics - containerPort: 5556 + containerPort: {{ .Values.metrics.jmx.containerPorts.metrics }} {{- if .Values.metrics.jmx.resources }} resources: {{- toYaml .Values.metrics.jmx.resources | nindent 12 }} {{- end }} @@ -373,7 +488,7 @@ spec: mountPath: /etc/jmx-kafka {{- end }} {{- if .Values.sidecars }} - {{- include "kafka.tplValue" (dict "value" .Values.sidecars "context" $) | nindent 8 }} + {{- include "common.tplvalues.render" (dict "value" .Values.sidecars "context" $) | nindent 8 }} {{- end }} volumes: {{- if or .Values.config .Values.existingConfigmap }} @@ -388,7 +503,7 @@ spec: {{ end }} - name: scripts configMap: - name: {{ include "kafka.fullname" . }}-scripts + name: {{ include "common.names.fullname" . }}-scripts defaultMode: 0755 {{- if and .Values.externalAccess.enabled .Values.externalAccess.autoDiscovery.enabled }} - name: shared @@ -400,13 +515,36 @@ spec: name: {{ include "kafka.metrics.jmx.configmapName" . }} {{- end }} {{- if (include "kafka.tlsEncryption" .) }} - - name: kafka-certificates + {{- if not (empty .Values.auth.tls.existingSecrets) }} + {{- range $index, $secret := .Values.auth.tls.existingSecrets }} + - name: kafka-certs-{{ $index }} secret: - secretName: {{ include "kafka.jksSecretName" . }} + secretName: {{ tpl $secret $ }} defaultMode: 256 {{- end }} + {{- else if .Values.auth.tls.autoGenerated }} + {{- range $index := until $replicaCount }} + - name: kafka-certs-{{ $index }} + secret: + secretName: {{ printf "%s-%d-tls" (include "common.names.fullname" $) $index }} + defaultMode: 256 + {{- end }} + {{- end }} + {{- if and .Values.auth.zookeeper.tls.enabled .Values.auth.zookeeper.tls.existingSecret }} + - name: kafka-zookeeper-cert + secret: + secretName: {{ .Values.auth.zookeeper.tls.existingSecret }} + defaultMode: 256 + {{- end }} + {{- if .Values.auth.tls.jksTruststoreSecret }} + - name: kafka-truststore + secret: + secretName: {{ .Values.auth.tls.jksTruststoreSecret }} + defaultMode: 256 + {{- end }} + {{- end }} {{- if .Values.extraVolumes }} - {{- toYaml .Values.extraVolumes | nindent 8 }} + {{- include "common.tplvalues.render" (dict "value" .Values.extraVolumes "context" $) | nindent 8 }} {{- end }} {{- if not .Values.persistence.enabled }} - name: data @@ -415,12 +553,26 @@ spec: - name: data persistentVolumeClaim: claimName: {{ printf "%s" (tpl .Values.persistence.existingClaim .) }} -{{- else }} +{{- end }} +{{- if not .Values.logPersistence.enabled }} + - name: logs + emptyDir: {} +{{- else if .Values.logPersistence.existingClaim }} + - name: logs + persistentVolumeClaim: + claimName: {{ printf "%s" (tpl .Values.logPersistence.existingClaim .) }} +{{- end }} + {{- if or (and .Values.persistence.enabled (not .Values.persistence.existingClaim)) (and .Values.logPersistence.enabled (not .Values.logPersistence.existingClaim)) }} volumeClaimTemplates: + {{- end }} +{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} - metadata: name: data {{- if .Values.persistence.annotations }} - annotations: {{- include "kafka.tplValue" (dict "value" .Values.persistence.annotations "context" $) | nindent 10 }} + annotations: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.annotations "context" $) | nindent 10 }} + {{- end }} + {{- if .Values.persistence.labels }} + labels: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.labels "context" $) | nindent 10 }} {{- end }} spec: accessModes: @@ -431,5 +583,26 @@ spec: requests: storage: {{ .Values.persistence.size | quote }} {{ include "kafka.storageClass" . | nindent 8 }} + {{- if .Values.persistence.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.selector "context" $) | nindent 10 }} + {{- end -}} {{- end }} +{{- if and .Values.logPersistence.enabled (not .Values.logPersistence.existingClaim) }} + - metadata: + name: logs + {{- if .Values.logPersistence.annotations }} + annotations: {{- include "common.tplvalues.render" (dict "value" .Values.logPersistence.annotations "context" $) | nindent 10 }} + {{- end }} + spec: + accessModes: + {{- range .Values.logPersistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.logPersistence.size | quote }} + {{ include "kafka.storageClass" . | nindent 8 }} + {{- if .Values.logPersistence.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.logPersistence.selector "context" $) | nindent 10 }} + {{- end -}} {{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/templates/svc-external-access.yaml b/scripts/helmcharts/databases/charts/kafka/templates/svc-external-access.yaml old mode 100755 new mode 100644 index eefe0046d..8d77a4710 --- a/scripts/helmcharts/databases/charts/kafka/templates/svc-external-access.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/svc-external-access.yaml @@ -1,50 +1,61 @@ {{- if .Values.externalAccess.enabled }} -{{- $fullName := include "kafka.fullname" . }} +{{- $fullName := include "common.names.fullname" . }} {{- $replicaCount := .Values.replicaCount | int }} {{- $root := . }} {{- range $i, $e := until $replicaCount }} {{- $targetPod := printf "%s-%d" (printf "%s" $fullName) $i }} ---- +{{- $_ := set $ "targetPod" $targetPod }} apiVersion: v1 kind: Service metadata: - name: {{ template "kafka.fullname" $ }}-{{ $i }}-external - labels: {{- include "kafka.labels" $ | nindent 4 }} + name: {{ printf "%s-%d-external" (include "common.names.fullname" $) $i | trunc 63 | trimSuffix "-" }} + namespace: {{ $root.Release.Namespace | quote }} + labels: {{- include "common.labels.standard" $ | nindent 4 }} app.kubernetes.io/component: kafka pod: {{ $targetPod }} {{- if $root.Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" $root.Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" $root.Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} - {{- if or $root.Values.externalAccess.service.annotations $root.Values.commonAnnotations }} + {{- if $root.Values.externalAccess.service.labels }} + {{- include "common.tplvalues.render" ( dict "value" $root.Values.externalAccess.service.labels "context" $) | nindent 4 }} + {{- end }} + {{- if or $root.Values.externalAccess.service.annotations $root.Values.commonAnnotations $root.Values.externalAccess.service.loadBalancerAnnotations }} annotations: + {{- if and (not (empty $root.Values.externalAccess.service.loadBalancerAnnotations)) (eq (len $root.Values.externalAccess.service.loadBalancerAnnotations) $replicaCount) }} + {{ include "common.tplvalues.render" ( dict "value" (index $root.Values.externalAccess.service.loadBalancerAnnotations $i) "context" $) | nindent 4 }} + {{- end }} {{- if $root.Values.externalAccess.service.annotations }} - {{ include "kafka.tplValue" ( dict "value" $root.Values.externalAccess.service.annotations "context" $) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" $root.Values.externalAccess.service.annotations "context" $) | nindent 4 }} {{- end }} {{- if $root.Values.commonAnnotations }} - {{- include "kafka.tplValue" ( dict "value" $root.Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" $root.Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} {{- end }} spec: type: {{ $root.Values.externalAccess.service.type }} {{- if eq $root.Values.externalAccess.service.type "LoadBalancer" }} - {{- if not (empty $root.Values.externalAccess.service.loadBalancerIPs) }} + {{- if and (not (empty $root.Values.externalAccess.service.loadBalancerIPs)) (eq (len $root.Values.externalAccess.service.loadBalancerIPs) $replicaCount) }} loadBalancerIP: {{ index $root.Values.externalAccess.service.loadBalancerIPs $i }} {{- end }} {{- if $root.Values.externalAccess.service.loadBalancerSourceRanges }} loadBalancerSourceRanges: {{- toYaml $root.Values.externalAccess.service.loadBalancerSourceRanges | nindent 4 }} {{- end }} {{- end }} + publishNotReadyAddresses: {{ $root.Values.externalAccess.service.publishNotReadyAddresses }} ports: - name: tcp-kafka - port: {{ $root.Values.externalAccess.service.port }} + port: {{ $root.Values.externalAccess.service.ports.external }} {{- if not (empty $root.Values.externalAccess.service.nodePorts) }} nodePort: {{ index $root.Values.externalAccess.service.nodePorts $i }} {{- else }} nodePort: null {{- end }} targetPort: kafka-external - selector: {{- include "kafka.matchLabels" $ | nindent 4 }} + {{- if $root.Values.externalAccess.service.extraPorts }} + {{- include "common.tplvalues.render" (dict "value" $root.Values.externalAccess.service.extraPorts "context" $) | nindent 4 }} + {{- end }} + selector: {{- include "common.labels.matchLabels" $ | nindent 4 }} app.kubernetes.io/component: kafka statefulset.kubernetes.io/pod-name: {{ $targetPod }} --- diff --git a/scripts/helmcharts/databases/charts/kafka/templates/svc-headless.yaml b/scripts/helmcharts/databases/charts/kafka/templates/svc-headless.yaml old mode 100755 new mode 100644 index e7c2e5e6e..af462126a --- a/scripts/helmcharts/databases/charts/kafka/templates/svc-headless.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/svc-headless.yaml @@ -1,26 +1,37 @@ apiVersion: v1 kind: Service metadata: - name: {{ template "kafka.fullname" . }}-headless - labels: {{- include "kafka.labels" . | nindent 4 }} + name: {{ printf "%s-headless" (include "common.names.fullname" .) | trunc 63 | trimSuffix "-" }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} app.kubernetes.io/component: kafka - {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- if .Values.service.headless.labels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.service.headless.labels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or .Values.service.headless.annotations .Values.commonAnnotations }} + annotations: + {{- if .Values.service.headless.annotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.service.headless.annotations "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} {{- end }} - {{- if .Values.commonAnnotations }} - annotations: {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} spec: type: ClusterIP clusterIP: None + publishNotReadyAddresses: {{ .Values.service.headless.publishNotReadyAddresses }} ports: - name: tcp-client - port: {{ .Values.service.port }} + port: {{ .Values.service.ports.client }} protocol: TCP targetPort: kafka-client - name: tcp-internal - port: {{ .Values.service.internalPort }} + port: {{ .Values.service.ports.internal }} protocol: TCP targetPort: kafka-internal - selector: {{- include "kafka.matchLabels" . | nindent 4 }} + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} app.kubernetes.io/component: kafka diff --git a/scripts/helmcharts/databases/charts/kafka/templates/svc.yaml b/scripts/helmcharts/databases/charts/kafka/templates/svc.yaml old mode 100755 new mode 100644 index 189cb9ffd..8e0472a1d --- a/scripts/helmcharts/databases/charts/kafka/templates/svc.yaml +++ b/scripts/helmcharts/databases/charts/kafka/templates/svc.yaml @@ -1,34 +1,45 @@ apiVersion: v1 kind: Service metadata: - name: {{ template "kafka.fullname" . }} - labels: {{- include "kafka.labels" . | nindent 4 }} + name: {{ template "common.names.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" . | nindent 4 }} app.kubernetes.io/component: kafka {{- if .Values.commonLabels }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} {{- end }} {{- if or .Values.service.annotations .Values.commonAnnotations }} annotations: {{- if .Values.service.annotations }} - {{ include "kafka.tplValue" ( dict "value" .Values.service.annotations "context" $) | nindent 4 }} + {{ include "common.tplvalues.render" ( dict "value" .Values.service.annotations "context" $) | nindent 4 }} {{- end }} {{- if .Values.commonAnnotations }} - {{- include "kafka.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} {{- end }} {{- end }} spec: type: {{ .Values.service.type }} - {{- if eq .Values.service.type "LoadBalancer" }} - {{- if .Values.service.loadBalancerIP }} + {{- if and .Values.service.clusterIP (eq .Values.service.type "ClusterIP") }} + clusterIP: {{ .Values.service.clusterIP }} + {{- end }} + {{- if or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort") }} + externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy | quote }} + {{- end }} + {{- if and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerSourceRanges)) }} + loadBalancerSourceRanges: {{ .Values.service.loadBalancerSourceRanges }} + {{- end }} + {{- if and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerIP)) }} loadBalancerIP: {{ .Values.service.loadBalancerIP }} {{- end }} - {{- if .Values.service.loadBalancerSourceRanges }} - loadBalancerSourceRanges: {{- toYaml .Values.service.loadBalancerSourceRanges | nindent 4 }} + {{- if .Values.service.sessionAffinity }} + sessionAffinity: {{ .Values.service.sessionAffinity }} {{- end }} + {{- if .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: {{- include "common.tplvalues.render" (dict "value" .Values.service.sessionAffinityConfig "context" $) | nindent 4 }} {{- end }} ports: - name: tcp-client - port: {{ .Values.service.port }} + port: {{ .Values.service.ports.client }} protocol: TCP targetPort: kafka-client {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePorts.client)) }} @@ -36,14 +47,17 @@ spec: {{- else if eq .Values.service.type "ClusterIP" }} nodePort: null {{- end }} - {{- if and .Values.externalAccess.enabled (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) }} + {{- if .Values.externalAccess.enabled }} - name: tcp-external - port: {{ .Values.service.externalPort }} + port: {{ .Values.service.ports.external }} protocol: TCP targetPort: kafka-external {{- if (not (empty .Values.service.nodePorts.external)) }} nodePort: {{ .Values.service.nodePorts.external }} {{- end }} {{- end }} - selector: {{- include "kafka.matchLabels" . | nindent 4 }} + {{- if .Values.service.extraPorts }} + {{- include "common.tplvalues.render" (dict "value" .Values.service.extraPorts "context" $) | nindent 4 }} + {{- end }} + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} app.kubernetes.io/component: kafka diff --git a/scripts/helmcharts/databases/charts/kafka/templates/tls-secrets.yaml b/scripts/helmcharts/databases/charts/kafka/templates/tls-secrets.yaml new file mode 100644 index 000000000..d6b1adc28 --- /dev/null +++ b/scripts/helmcharts/databases/charts/kafka/templates/tls-secrets.yaml @@ -0,0 +1,31 @@ +{{- if (include "kafka.createTlsSecret" .) }} +{{- $replicaCount := int .Values.replicaCount }} +{{- $releaseNamespace := .Release.Namespace }} +{{- $clusterDomain := .Values.clusterDomain }} +{{- $fullname := include "common.names.fullname" . }} +{{- $ca := genCA "kafka-ca" 365 }} +{{- range $i := until $replicaCount }} +{{- $secretName := printf "%s-%d-tls" (include "common.names.fullname" $) $i }} +{{- $replicaHost := printf "%s-%d.%s-headless" $fullname $i $fullname }} +{{- $altNames := list (printf "%s.%s.svc.%s" $replicaHost $releaseNamespace $clusterDomain) (printf "%s.%s.svc.%s" $fullname $releaseNamespace $clusterDomain) (printf "%s.%s" $replicaHost $releaseNamespace) (printf "%s.%s" $fullname $releaseNamespace) $replicaHost $fullname }} +{{- $cert := genSignedCert $replicaHost nil $altNames 365 $ca }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ printf "%s-%d-tls" (include "common.names.fullname" $) $i }} + namespace: {{ $.Release.Namespace | quote }} + labels: {{- include "common.labels.standard" $ | nindent 4 }} + {{- if $.Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" $.Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if $.Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: kubernetes.io/tls +data: + tls.crt: {{ include "common.secrets.lookup" (dict "secret" $secretName "key" "tls.crt" "defaultValue" $cert.Cert "context" $) }} + tls.key: {{ include "common.secrets.lookup" (dict "secret" $secretName "key" "tls.key" "defaultValue" $cert.Key "context" $) }} + ca.crt: {{ include "common.secrets.lookup" (dict "secret" $secretName "key" "ca.crt" "defaultValue" $ca.Cert "context" $) }} +--- +{{- end }} +{{- end }} diff --git a/scripts/helmcharts/databases/charts/kafka/values-production.yaml b/scripts/helmcharts/databases/charts/kafka/values-production.yaml deleted file mode 100755 index af6f43dba..000000000 --- a/scripts/helmcharts/databases/charts/kafka/values-production.yaml +++ /dev/null @@ -1,931 +0,0 @@ -## Global Docker image parameters -## Please, note that this will override the image parameters, including dependencies, configured to use the global value -## Current available global Docker image parameters: imageRegistry and imagePullSecrets -## -# global: -# imageRegistry: myRegistryName -# imagePullSecrets: -# - myRegistryKeySecretName -# storageClass: myStorageClass - -## Bitnami Kafka image version -## ref: https://hub.docker.com/r/bitnami/kafka/tags/ -## -image: - registry: docker.io - repository: bitnami/kafka - tag: 2.6.0-debian-10-r30 - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## Example: - ## pullSecrets: - ## - myRegistryKeySecretName - ## - pullSecrets: [] - - ## Set to true if you would like to see extra information on logs - ## - debug: false - -## String to partially override kafka.fullname template (will maintain the release name) -## -# nameOverride: - -## String to fully override kafka.fullname template -## -# fullnameOverride: - -## Kubernetes Cluster Domain -## -clusterDomain: cluster.local - -## Add labels to all the deployed resources -## -commonLabels: {} - -## Add annotations to all the deployed resources -## -commonAnnotations: {} - -## Kafka Configuration -## Specify content for server.properties -## The server.properties is auto-generated based on other parameters when this paremeter is not specified -## -## Example: -## config: |- -## broker.id=-1 -## listeners=PLAINTEXT://:9092 -## advertised.listeners=PLAINTEXT://KAFKA_IP:9092 -## num.network.threads=3 -## num.io.threads=8 -## socket.send.buffer.bytes=102400 -## socket.receive.buffer.bytes=102400 -## socket.request.max.bytes=104857600 -## log.dirs=/bitnami/kafka/data -## num.partitions=1 -## num.recovery.threads.per.data.dir=1 -## offsets.topic.replication.factor=1 -## transaction.state.log.replication.factor=1 -## transaction.state.log.min.isr=1 -## log.flush.interval.messages=10000 -## log.flush.interval.ms=1000 -## log.retention.hours=168 -## log.retention.bytes=1073741824 -## log.segment.bytes=1073741824 -## log.retention.check.interval.ms=300000 -## zookeeper.connect=ZOOKEEPER_SERVICE_NAME -## zookeeper.connection.timeout.ms=6000 -## group.initial.rebalance.delay.ms=0 -## -# config: - -## ConfigMap with Kafka Configuration -## NOTE: This will override config -## -# existingConfigmap: - -## Kafka Log4J Configuration -## An optional log4j.properties file to overwrite the default of the Kafka brokers. -## See an example log4j.properties at: -## https://github.com/apache/kafka/blob/trunk/config/log4j.properties -## -# log4j: - -## Kafka Log4j ConfigMap -## The name of an existing ConfigMap containing a log4j.properties file. -## NOTE: this will override log4j. -## -# existingLog4jConfigMap: - -## Kafka's Java Heap size -## -heapOpts: -Xmx1024m -Xms1024m - -## Switch to enable topic deletion or not. -## -deleteTopicEnable: false - -## Switch to enable auto creation of topics. -## Enabling auto creation of topics not recommended for production or similar environments. -## -autoCreateTopicsEnable: false - -## The number of messages to accept before forcing a flush of data to disk. -## -logFlushIntervalMessages: 10000 - -## The maximum amount of time a message can sit in a log before we force a flush. -## -logFlushIntervalMs: 1000 - -## A size-based retention policy for logs. -## -logRetentionBytes: _1073741824 - -## The interval at which log segments are checked to see if they can be deleted. -## -logRetentionCheckIntervalMs: 300000 - -## The minimum age of a log file to be eligible for deletion due to age. -## -logRetentionHours: 168 - -## The maximum size of a log segment file. When this size is reached a new log segment will be created. -## -logSegmentBytes: _1073741824 - -## A comma separated list of directories under which to store log files. -## -logsDirs: /bitnami/kafka/data - -## The largest record batch size allowed by Kafka -## -maxMessageBytes: _1000012 - -## Default replication factors for automatically created topics -## -defaultReplicationFactor: 3 - -## The replication factor for the offsets topic -## -offsetsTopicReplicationFactor: 3 - -## The replication factor for the transaction topic -## -transactionStateLogReplicationFactor: 3 - -## Overridden min.insync.replicas config for the transaction topic -## -transactionStateLogMinIsr: 3 - -## The number of threads doing disk I/O. -## -numIoThreads: 8 - -## The number of threads handling network requests. -## -numNetworkThreads: 3 - -## The default number of log partitions per topic. -## -numPartitions: 1 - -## The number of threads per data directory to be used for log recovery at startup and flushing at shutdown. -## -numRecoveryThreadsPerDataDir: 1 - -## The receive buffer (SO_RCVBUF) used by the socket server. -## -socketReceiveBufferBytes: 102400 - -## The maximum size of a request that the socket server will accept (protection against OOM). -## -socketRequestMaxBytes: _104857600 - -## The send buffer (SO_SNDBUF) used by the socket server. -## -socketSendBufferBytes: 102400 - -## Timeout in ms for connecting to zookeeper. -## -zookeeperConnectionTimeoutMs: 6000 - -## Command and args for running the container. Use array form -## -command: - - /scripts/setup.sh -args: - -## All the parameters from the configuration file can be overwritten by using environment variables with this format: KAFKA_CFG_{KEY} -## ref: https://github.com/bitnami/bitnami-docker-kafka#configuration -## Example: -## extraEnvVars: -## - name: KAFKA_CFG_BACKGROUND_THREADS -## value: "10" -## -extraEnvVars: [] - -## extraVolumes and extraVolumeMounts allows you to mount other volumes -## Examples: -# extraVolumes: -# - name: kafka-jaas -# secret: -# secretName: kafka-jaas -# extraVolumeMounts: -# - name: kafka-jaas -# mountPath: /bitnami/kafka/config/kafka_jaas.conf -# subPath: kafka_jaas.conf -extraVolumes: [] -extraVolumeMounts: [] - -## Extra objects to deploy (value evaluated as a template) -## -extraDeploy: [] - -## Authentication parameteres -## https://github.com/bitnami/bitnami-docker-kafka#security -## -auth: - ## Authentication protocol for client and inter-broker communications - ## Supported values: 'plaintext', 'tls', 'mtls', 'sasl' and 'sasl_tls' - ## This table shows the security provided on each protocol: - ## | Method | Authentication | Encryption via TLS | - ## | plaintext | None | No | - ## | tls | None | Yes | - ## | mtls | Yes (two-way authentication) | Yes | - ## | sasl | Yes (via SASL) | No | - ## | sasl_tls | Yes (via SASL) | Yes | - ## - clientProtocol: sasl - interBrokerProtocol: sasl - - ## Allowed SASL mechanisms when clientProtocol or interBrokerProtocol are using either sasl or sasl_tls - ## - saslMechanisms: plain,scram-sha-256,scram-sha-512 - ## SASL mechanism for inter broker communication - ## - saslInterBrokerMechanism: plain - - ## Name of the existing secret containing the truststore and - ## one keystore per Kafka broker you have in the Kafka cluster. - ## MANDATORY when 'tls', 'mtls', or 'sasl_tls' authentication protocols are used. - ## Create this secret following the steps below: - ## 1) Generate your trustore and keystore files. Helpful script: https://raw.githubusercontent.com/confluentinc/confluent-platform-security-tools/master/kafka-generate-ssl.sh - ## 2) Rename your truststore to `kafka.truststore.jks`. - ## 3) Rename your keystores to `kafka-X.keystore.jks` where X is the ID of each Kafka broker. - ## 4) Run the command below where SECRET_NAME is the name of the secret you want to create: - ## kubectl create secret generic SECRET_NAME --from-file=./kafka.truststore.jks --from-file=./kafka-0.keystore.jks --from-file=./kafka-1.keystore.jks ... - ## Alternatively, you can put your JKS files under the files/jks directory - ## - # jksSecret: - - ## Password to access the JKS files when they are password-protected. - ## - # jksPassword: - - ## The endpoint identification algorithm used by clients to validate server host name. - ## Disable server host name verification by setting it to an empty string - ## See: https://docs.confluent.io/current/kafka/authentication_ssl.html#optional-settings - ## - tlsEndpointIdentificationAlgorithm: https - - ## JAAS configuration for SASL authentication - ## MANDATORY when method is 'sasl', or 'sasl_tls' - ## - jaas: - ## Kafka client user list - ## - ## clientUsers: - ## - user1 - ## - user2 - ## - clientUsers: - - user - - ## Kafka client passwords - ## - ## clientPasswords: - ## - password1 - ## - password2 - ## - clientPasswords: [] - - ## Kafka inter broker communication user - ## - interBrokerUser: admin - - ## Kafka inter broker communication password - ## - interBrokerPassword: "" - - ## Kafka Zookeeper user - ## - zookeeperUser: zookeeperUser - - ## Kafka Zookeeper password - ## - zookeeperPassword: zookeeperPassword - - ## Name of the existing secret containing credentials for clientUsers, interBrokerUser and zookeeperUser. - ## Create this secret running the command below where SECRET_NAME is the name of the secret you want to create: - ## kubectl create secret generic SECRET_NAME --from-literal=client-password=CLIENT_PASSWORD1,CLIENT_PASSWORD2 --from-literal=inter-broker-password=INTER_BROKER_PASSWORD --from-literal=zookeeper-password=ZOOKEEPER_PASSWORD - ## - # existingSecret: - -## The address(es) the socket server listens on. -## When it's set to an empty array, the listeners will be configured -## based on the authentication protocols (auth.clientProtocol and auth.interBrokerProtocol parameters) -## -listeners: [] - -## The address(es) (hostname:port) the brokers will advertise to producers and consumers. -## When it's set to an empty array, the advertised listeners will be configured -## based on the authentication protocols (auth.clientProtocol and auth.interBrokerProtocol parameters) -## -advertisedListeners: [] - -## The listener->protocol mapping -## When it's nil, the listeners will be configured -## based on the authentication protocols (auth.clientProtocol and auth.interBrokerProtocol parameters) -## -# listenerSecurityProtocolMap: - -## Allow to use the PLAINTEXT listener. -## -allowPlaintextListener: false - -## Name of listener used for communication between brokers. -## -interBrokerListenerName: INTERNAL - -## Number of Kafka brokers to deploy -## -replicaCount: 3 - -## StrategyType, can be set to RollingUpdate or OnDelete by default. -## ref: https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets -## -updateStrategy: RollingUpdate - -## Partition update strategy -## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions -## -# rollingUpdatePartition: - -## Pod labels. Evaluated as a template -## Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ -podLabels: {} - -## Pod annotations. Evaluated as a template -## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ -## -podAnnotations: {} - -## Name of the priority class to be used by kafka pods, priority class needs to be created beforehand -## Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ -priorityClassName: "" - -## Affinity for pod assignment. Evaluated as a template -## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity -## -affinity: {} - -## Node labels for pod assignment. Evaluated as a template -## ref: https://kubernetes.io/docs/user-guide/node-selection/ -## -nodeSelector: {} - -## Tolerations for pod assignment. Evaluated as a template -## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ -## -tolerations: [] - -## Kafka pods' Security Context -## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod -## -podSecurityContext: - fsGroup: 1001 - runAsUser: 1001 - -## Kafka containers' Security Context -## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container -## Example: -## containerSecurityContext: -## capabilities: -## drop: ["NET_RAW"] -## readOnlyRootFilesystem: true -## -containerSecurityContext: {} - -## Kafka containers' resource requests and limits -## ref: http://kubernetes.io/docs/user-guide/compute-resources/ -## -resources: - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - limits: {} - # cpu: 250m - # memory: 1Gi - requests: {} - # cpu: 250m - # memory: 256Mi - -## Kafka containers' liveness and readiness probes. Evaluated as a template. -## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes -## -livenessProbe: - tcpSocket: - port: kafka-client - initialDelaySeconds: 10 - timeoutSeconds: 5 - # failureThreshold: 3 - # periodSeconds: 10 - # successThreshold: 1 -readinessProbe: - tcpSocket: - port: kafka-client - initialDelaySeconds: 5 - failureThreshold: 6 - timeoutSeconds: 5 - # periodSeconds: 10 - # successThreshold: 1 - -## Pod Disruption Budget configuration -## The PDB will only be created if replicaCount is greater than 1 -## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions -## -pdb: - create: true - ## Min number of pods that must still be available after the eviction - ## - # minAvailable: 1 - ## Max number of pods that can be unavailable after the eviction - ## - maxUnavailable: 1 - -## Add sidecars to the pod. -## Example: -## sidecars: -## - name: your-image-name -## image: your-image -## imagePullPolicy: Always -## ports: -## - name: portname -## containerPort: 1234 -## -sidecars: {} - -## Service parameters -## -service: - ## Service type - ## - type: ClusterIP - ## Kafka port for client connections - ## - port: 9092 - ## Kafka port for inter-broker connections - ## - internalPort: 9093 - ## Kafka port for external connections - ## - externalPort: 9094 - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - nodePorts: - client: "" - external: "" - ## Set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - # loadBalancerIP: - ## Load Balancer sources - ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service - ## Example: - ## loadBalancerSourceRanges: - ## - 10.10.10.0/24 - ## - loadBalancerSourceRanges: [] - ## Provide any additional annotations which may be required. Evaluated as a template - ## - annotations: {} - -## External Access to Kafka brokers configuration -## -externalAccess: - ## Enable Kubernetes external cluster access to Kafka brokers - ## - enabled: false - - ## External IPs auto-discovery configuration - ## An init container is used to auto-detect LB IPs or node ports by querying the K8s API - ## Note: RBAC might be required - ## - autoDiscovery: - ## Enable external IP/ports auto-discovery - ## - enabled: false - ## Bitnami Kubectl image - ## ref: https://hub.docker.com/r/bitnami/kubectl/tags/ - ## - image: - registry: docker.io - repository: bitnami/kubectl - tag: 1.17.12-debian-10-r3 - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## Example: - ## pullSecrets: - ## - myRegistryKeySecretName - ## - pullSecrets: [] - ## Init Container resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - ## - resources: - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - limits: {} - # cpu: 100m - # memory: 128Mi - requests: {} - # cpu: 100m - # memory: 128Mi - - ## Parameters to configure K8s service(s) used to externally access Kafka brokers - ## A new service per broker will be created - ## - service: - ## Service type. Allowed values: LoadBalancer or NodePort - ## - type: LoadBalancer - ## Port used when service type is LoadBalancer - ## - port: 9094 - ## Array of load balancer IPs for each Kafka broker. Length must be the same as replicaCount - ## Example: - ## loadBalancerIPs: - ## - X.X.X.X - ## - Y.Y.Y.Y - ## - loadBalancerIPs: [] - ## Load Balancer sources - ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service - ## Example: - ## loadBalancerSourceRanges: - ## - 10.10.10.0/24 - ## - loadBalancerSourceRanges: [] - ## Array of node ports used for each Kafka broker. Length must be the same as replicaCount - ## Example: - ## nodePorts: - ## - 30001 - ## - 30002 - ## - nodePorts: [] - ## When service type is NodePort, you can specify the domain used for Kafka advertised listeners. - ## If not specified, the container will try to get the kubernetes node external IP - ## - # domain: mydomain.com - ## Provide any additional annotations which may be required. Evaluated as a template - ## - annotations: {} - -## Persistence paramaters -## -persistence: - enabled: true - ## A manually managed Persistent Volume and Claim - ## If defined, PVC must be created manually before volume will be bound - ## The value is evaluated as a template - ## - # existingClaim: - ## PV Storage Class - ## If defined, storageClassName: <storageClass> - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. - ## - # storageClass: "-" - ## PV Access Mode - ## - accessModes: - - ReadWriteOnce - ## PVC size - ## - size: 8Gi - ## PVC annotations - ## - annotations: {} - -## Init Container paramaters -## Change the owner and group of the persistent volume(s) mountpoint(s) to 'runAsUser:fsGroup' on each component -## values from the securityContext section of the component -## -volumePermissions: - enabled: false - ## Bitnami Minideb image - ## ref: https://hub.docker.com/r/bitnami/minideb/tags/ - ## - image: - registry: docker.io - repository: bitnami/minideb - tag: buster - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: Always - ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## Example: - ## pullSecrets: - ## - myRegistryKeySecretName - ## - pullSecrets: [] - ## Init Container resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - ## - resources: - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - limits: {} - # cpu: 100m - # memory: 128Mi - requests: {} - # cpu: 100m - # memory: 128Mi - -## Kafka pods ServiceAccount -## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ -## -serviceAccount: - ## Specifies whether a ServiceAccount should be created - ## - create: true - ## The name of the ServiceAccount to use. - ## If not set and create is true, a name is generated using the fluentd.fullname template - ## - # name: - -## Role Based Access -## ref: https://kubernetes.io/docs/admin/authorization/rbac/ -## -rbac: - ## Specifies whether RBAC rules should be created - ## binding Kafka ServiceAccount to a role - ## that allows Kafka pods querying the K8s API - ## - create: false - -## Prometheus Exporters / Metrics -## -metrics: - ## Prometheus Kafka Exporter: exposes complimentary metrics to JMX Exporter - ## - kafka: - enabled: true - - ## Bitnami Kafka exporter image - ## ref: https://hub.docker.com/r/bitnami/kafka-exporter/tags/ - ## - image: - registry: docker.io - repository: bitnami/kafka-exporter - tag: 1.2.0-debian-10-r220 - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## Example: - ## pullSecrets: - ## - myRegistryKeySecretName - ## - pullSecrets: [] - - ## Extra flags to be passed to Kafka exporter - ## Example: - ## extraFlags: - ## tls.insecure-skip-tls-verify: "" - ## web.telemetry-path: "/metrics" - ## - extraFlags: {} - - ## Name of the existing secret containing the optional certificate and key files - ## for Kafka Exporter client authentication - ## - # certificatesSecret: - - ## Prometheus Kafka Exporter' resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - ## - resources: - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - limits: {} - # cpu: 100m - # memory: 128Mi - requests: {} - # cpu: 100m - # memory: 128Mi - - ## Service configuration - ## - service: - ## Kafka Exporter Service type - ## - type: ClusterIP - ## Kafka Exporter Prometheus port - ## - port: 9308 - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - nodePort: "" - ## Set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - # loadBalancerIP: - ## Load Balancer sources - ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service - ## Example: - ## loadBalancerSourceRanges: - ## - 10.10.10.0/24 - ## - loadBalancerSourceRanges: [] - ## Set the Cluster IP to use - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#choosing-your-own-ip-address - ## - # clusterIP: None - ## Annotations for the Kafka Exporter Prometheus metrics service - ## - annotations: - prometheus.io/scrape: "true" - prometheus.io/port: "{{ .Values.metrics.kafka.service.port }}" - prometheus.io/path: "/metrics" - - ## Prometheus JMX Exporter: exposes the majority of Kafkas metrics - ## - jmx: - enabled: true - - ## Bitnami JMX exporter image - ## ref: https://hub.docker.com/r/bitnami/jmx-exporter/tags/ - ## - image: - registry: docker.io - repository: bitnami/jmx-exporter - tag: 0.14.0-debian-10-r15 - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## Example: - ## pullSecrets: - ## - myRegistryKeySecretName - ## - pullSecrets: [] - - ## Prometheus JMX Exporter' resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - ## - resources: - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - limits: {} - # cpu: 100m - # memory: 128Mi - requests: {} - # cpu: 100m - # memory: 128Mi - - ## Service configuration - ## - service: - ## JMX Exporter Service type - ## - type: ClusterIP - ## JMX Exporter Prometheus port - ## - port: 5556 - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - nodePort: "" - ## Set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - # loadBalancerIP: - ## Load Balancer sources - ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service - ## Example: - ## loadBalancerSourceRanges: - ## - 10.10.10.0/24 - ## - loadBalancerSourceRanges: [] - ## Set the Cluster IP to use - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#choosing-your-own-ip-address - ## - # clusterIP: None - ## Annotations for the JMX Exporter Prometheus metrics service - ## - annotations: - prometheus.io/scrape: "true" - prometheus.io/port: "{{ .Values.metrics.jmx.service.port }}" - prometheus.io/path: "/" - - ## JMX Whitelist Objects, can be set to control which JMX metrics are exposed. Only whitelisted - ## values will be exposed via JMX Exporter. They must also be exposed via Rules. To expose all metrics - ## (warning its crazy excessive and they aren't formatted in a prometheus style) (1) `whitelistObjectNames: []` - ## (2) commented out above `overrideConfig`. - ## - whitelistObjectNames: - - kafka.controller:* - - kafka.server:* - - java.lang:* - - kafka.network:* - - kafka.log:* - - ## Prometheus JMX exporter configuration - ## Specify content for jmx-kafka-prometheus.yml. Evaluated as a template - ## - ## Credits to the incubator/kafka chart for the JMX configuration. - ## https://github.com/helm/charts/tree/master/incubator/kafka - ## - config: |- - jmxUrl: service:jmx:rmi:///jndi/rmi://127.0.0.1:5555/jmxrmi - lowercaseOutputName: true - lowercaseOutputLabelNames: true - ssl: false - {{- if .Values.metrics.jmx.whitelistObjectNames }} - whitelistObjectNames: ["{{ join "\",\"" .Values.metrics.jmx.whitelistObjectNames }}"] - {{- end }} - - ## ConfigMap with Prometheus JMX exporter configuration - ## NOTE: This will override metrics.jmx.config - ## - # existingConfigmap: - - ## Prometheus Operator ServiceMonitor configuration - ## - serviceMonitor: - enabled: false - ## Namespace in which Prometheus is running - ## - # namespace: monitoring - - ## Interval at which metrics should be scraped. - ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#endpoint - ## - # interval: 10s - - ## Timeout after which the scrape is ended - ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#endpoint - ## - # scrapeTimeout: 10s - - ## ServiceMonitor selector labels - ## ref: https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#prometheus-configuration - ## - # selector: - # prometheus: my-prometheus - -## -## Zookeeper chart configuration -## -## https://github.com/bitnami/charts/blob/master/bitnami/zookeeper/values.yaml -## -zookeeper: - enabled: true - auth: - ## Enable Zookeeper auth - ## - enabled: true - ## User that will use Zookeeper clients to auth - ## - clientUser: zookeeperUser - ## Password that will use Zookeeper clients to auth - ## - clientPassword: zookeeperPassword - ## Comma, semicolon or whitespace separated list of user to be created. Specify them as a string, for example: "user1,user2,admin" - ## - serverUsers: zookeeperUser - ## Comma, semicolon or whitespace separated list of passwords to assign to users when created. Specify them as a string, for example: "pass4user1, pass4user2, pass4admin" - ## - serverPasswords: zookeeperPassword - metrics: - enabled: true - -## This value is only used when zookeeper.enabled is set to false -## -externalZookeeper: - ## Server or list of external zookeeper servers to use. - ## - servers: [] diff --git a/scripts/helmcharts/databases/charts/kafka/values.yaml b/scripts/helmcharts/databases/charts/kafka/values.yaml old mode 100755 new mode 100644 index 154d71bd5..1245bdb65 --- a/scripts/helmcharts/databases/charts/kafka/values.yaml +++ b/scripts/helmcharts/databases/charts/kafka/values.yaml @@ -1,62 +1,97 @@ +## @section Global parameters ## Global Docker image parameters ## Please, note that this will override the image parameters, including dependencies, configured to use the global value -## Current available global Docker image parameters: imageRegistry and imagePullSecrets +## Current available global Docker image parameters: imageRegistry, imagePullSecrets and storageClass + +## @param global.imageRegistry Global Docker image registry +## @param global.imagePullSecrets Global Docker registry secret names as an array +## @param global.storageClass Global StorageClass for Persistent Volume(s) ## -# global: -# imageRegistry: myRegistryName -# imagePullSecrets: -# - myRegistryKeySecretName -# storageClass: myStorageClass +global: + imageRegistry: "" + ## E.g. + ## imagePullSecrets: + ## - myRegistryKeySecretName + ## + imagePullSecrets: [] + storageClass: "" + +## @section Common parameters + +## @param kubeVersion Override Kubernetes version +## +kubeVersion: "" +## @param nameOverride String to partially override common.names.fullname +## +nameOverride: "" +## @param fullnameOverride String to fully override common.names.fullname +## +fullnameOverride: "" +## @param clusterDomain Default Kubernetes cluster domain +## +clusterDomain: cluster.local +## @param commonLabels Labels to add to all deployed objects +## +commonLabels: {} +## @param commonAnnotations Annotations to add to all deployed objects +## +commonAnnotations: {} +## @param extraDeploy Array of extra objects to deploy with the release +## +extraDeploy: [] +## Enable diagnostic mode in the statefulset +## +diagnosticMode: + ## @param diagnosticMode.enabled Enable diagnostic mode (all probes will be disabled and the command will be overridden) + ## + enabled: false + ## @param diagnosticMode.command Command to override all containers in the statefulset + ## + command: + - sleep + ## @param diagnosticMode.args Args to override all containers in the statefulset + ## + args: + - infinity + +## @section Kafka parameters ## Bitnami Kafka image version ## ref: https://hub.docker.com/r/bitnami/kafka/tags/ +## @param image.registry Kafka image registry +## @param image.repository Kafka image repository +## @param image.tag Kafka image tag (immutable tags are recommended) +## @param image.digest Kafka image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag +## @param image.pullPolicy Kafka image pull policy +## @param image.pullSecrets Specify docker-registry secret names as an array +## @param image.debug Specify if debug values should be set ## image: registry: docker.io repository: bitnami/kafka - tag: 2.6.0-debian-10-r30 + tag: 3.3.2-debian-11-r0 + digest: "" ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## ref: https://kubernetes.io/docs/user-guide/images/#pre-pulling-images ## pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## Example: + ## e.g: ## pullSecrets: ## - myRegistryKeySecretName ## pullSecrets: [] - ## Set to true if you would like to see extra information on logs ## debug: false - -## String to partially override kafka.fullname template (will maintain the release name) -## -# nameOverride: - -## String to fully override kafka.fullname template -## -# fullnameOverride: - -## Kubernetes Cluster Domain -## -clusterDomain: cluster.local - -## Add labels to all the deployed resources -## -commonLabels: {} - -## Add annotations to all the deployed resources -## -commonAnnotations: {} - -## Kafka Configuration +## @param config Configuration file for Kafka. Auto-generated based on other parameters when not specified ## Specify content for server.properties -## The server.properties is auto-generated based on other parameters when this paremeter is not specified -## -## Example: +## NOTE: This will override any KAFKA_CFG_ environment variables (including those set by the chart) +## The server.properties is auto-generated based on other parameters when this parameter is not specified +## e.g: ## config: |- ## broker.id=-1 ## listeners=PLAINTEXT://:9092 @@ -82,157 +117,108 @@ commonAnnotations: {} ## zookeeper.connection.timeout.ms=6000 ## group.initial.rebalance.delay.ms=0 ## -# config: - -## ConfigMap with Kafka Configuration -## NOTE: This will override config +config: "" +## @param existingConfigmap ConfigMap with Kafka Configuration +## NOTE: This will override `config` AND any KAFKA_CFG_ environment variables ## -# existingConfigmap: - -## Kafka Log4J Configuration -## An optional log4j.properties file to overwrite the default of the Kafka brokers. -## See an example log4j.properties at: -## https://github.com/apache/kafka/blob/trunk/config/log4j.properties +existingConfigmap: "" +## @param log4j An optional log4j.properties file to overwrite the default of the Kafka brokers +## An optional log4j.properties file to overwrite the default of the Kafka brokers +## ref: https://github.com/apache/kafka/blob/trunk/config/log4j.properties ## -# log4j: - -## Kafka Log4j ConfigMap -## The name of an existing ConfigMap containing a log4j.properties file. -## NOTE: this will override log4j. +log4j: "" +## @param existingLog4jConfigMap The name of an existing ConfigMap containing a log4j.properties file +## The name of an existing ConfigMap containing a log4j.properties file +## NOTE: this will override `log4j` ## -# existingLog4jConfigMap: - -## Kafka's Java Heap size +existingLog4jConfigMap: "" +## @param heapOpts Kafka Java Heap size ## heapOpts: -Xmx1024m -Xms1024m - -## Switch to enable topic deletion or not. +## @param deleteTopicEnable Switch to enable topic deletion or not ## deleteTopicEnable: false - -## Switch to enable auto creation of topics. -## Enabling auto creation of topics not recommended for production or similar environments. +## @param autoCreateTopicsEnable Switch to enable auto creation of topics. Enabling auto creation of topics not recommended for production or similar environments ## autoCreateTopicsEnable: true - -## The number of messages to accept before forcing a flush of data to disk. +## @param logFlushIntervalMessages The number of messages to accept before forcing a flush of data to disk ## -logFlushIntervalMessages: 10000 - -## The maximum amount of time a message can sit in a log before we force a flush. +logFlushIntervalMessages: _10000 +## @param logFlushIntervalMs The maximum amount of time a message can sit in a log before we force a flush ## logFlushIntervalMs: 1000 - -## A size-based retention policy for logs. +## @param logRetentionBytes A size-based retention policy for logs ## logRetentionBytes: _1073741824 - -## The interval at which log segments are checked to see if they can be deleted. +## @param logRetentionCheckIntervalMs The interval at which log segments are checked to see if they can be deleted ## logRetentionCheckIntervalMs: 300000 - -## The minimum age of a log file to be eligible for deletion due to age. +## @param logRetentionHours The minimum age of a log file to be eligible for deletion due to age ## logRetentionHours: 168 - -## The maximum size of a log segment file. When this size is reached a new log segment will be created. +## @param logSegmentBytes The maximum size of a log segment file. When this size is reached a new log segment will be created ## logSegmentBytes: _1073741824 - -## A comma separated list of directories under which to store log files. -## +## @param logsDirs A comma separated list of directories in which kafka's log data is kept +## ref: https://kafka.apache.org/documentation/#brokerconfigs_log.dirs logsDirs: /bitnami/kafka/data - -## The largest record batch size allowed by Kafka +## @param maxMessageBytes The largest record batch size allowed by Kafka ## maxMessageBytes: _1000012 - -## Default replication factors for automatically created topics +## @param defaultReplicationFactor Default replication factors for automatically created topics ## defaultReplicationFactor: 1 - -## The replication factor for the offsets topic +## @param offsetsTopicReplicationFactor The replication factor for the offsets topic ## offsetsTopicReplicationFactor: 1 - -## The replication factor for the transaction topic +## @param transactionStateLogReplicationFactor The replication factor for the transaction topic ## transactionStateLogReplicationFactor: 1 - -## Overridden min.insync.replicas config for the transaction topic +## @param transactionStateLogMinIsr Overridden min.insync.replicas config for the transaction topic ## transactionStateLogMinIsr: 1 - -## The number of threads doing disk I/O. +## @param numIoThreads The number of threads doing disk I/O ## numIoThreads: 8 - -## The number of threads handling network requests. +## @param numNetworkThreads The number of threads handling network requests ## numNetworkThreads: 3 - -## The default number of log partitions per topic. +## @param numPartitions The default number of log partitions per topic ## numPartitions: 1 - -## The number of threads per data directory to be used for log recovery at startup and flushing at shutdown. +## @param numRecoveryThreadsPerDataDir The number of threads per data directory to be used for log recovery at startup and flushing at shutdown ## numRecoveryThreadsPerDataDir: 1 - -## The receive buffer (SO_RCVBUF) used by the socket server. +## @param socketReceiveBufferBytes The receive buffer (SO_RCVBUF) used by the socket server ## socketReceiveBufferBytes: 102400 - -## The maximum size of a request that the socket server will accept (protection against OOM). +## @param socketRequestMaxBytes The maximum size of a request that the socket server will accept (protection against OOM) ## socketRequestMaxBytes: _104857600 - -## The send buffer (SO_SNDBUF) used by the socket server. +## @param socketSendBufferBytes The send buffer (SO_SNDBUF) used by the socket server ## socketSendBufferBytes: 102400 - -## Timeout in ms for connecting to zookeeper. +## @param zookeeperConnectionTimeoutMs Timeout in ms for connecting to ZooKeeper ## zookeeperConnectionTimeoutMs: 6000 - -## Command and args for running the container. Use array form +## @param zookeeperChrootPath Path which puts data under some path in the global ZooKeeper namespace +## ref: https://kafka.apache.org/documentation/#brokerconfigs_zookeeper.connect ## -command: - - /scripts/setup.sh -args: - -## All the parameters from the configuration file can be overwritten by using environment variables with this format: KAFKA_CFG_{KEY} -## ref: https://github.com/bitnami/bitnami-docker-kafka#configuration -## Example: -## extraEnvVars: -## - name: KAFKA_CFG_BACKGROUND_THREADS -## value: "10" +zookeeperChrootPath: "" +## @param authorizerClassName The Authorizer is configured by setting authorizer.class.name=kafka.security.authorizer.AclAuthorizer in server.properties ## -extraEnvVars: [] - -## extraVolumes and extraVolumeMounts allows you to mount other volumes -## Examples: -# extraVolumes: -# - name: kafka-jaas -# secret: -# secretName: kafka-jaas -# extraVolumeMounts: -# - name: kafka-jaas -# mountPath: /bitnami/kafka/config/kafka_jaas.conf -# subPath: kafka_jaas.conf -extraVolumes: [] -extraVolumeMounts: [] - -## Extra objects to deploy (value evaluated as a template) +authorizerClassName: "" +## @param allowEveryoneIfNoAclFound By default, if a resource has no associated ACLs, then no one is allowed to access that resource except super users ## -extraDeploy: [] - -## Authentication parameteres -## https://github.com/bitnami/bitnami-docker-kafka#security +allowEveryoneIfNoAclFound: true +## @param superUsers You can add super users in server.properties +## +superUsers: User:admin +## Authentication parameters +## https://github.com/bitnami/containers/tree/main/bitnami/kafka#security ## auth: ## Authentication protocol for client and inter-broker communications - ## Supported values: 'plaintext', 'tls', 'mtls', 'sasl' and 'sasl_tls' ## This table shows the security provided on each protocol: ## | Method | Authentication | Encryption via TLS | ## | plaintext | None | No | @@ -240,223 +226,413 @@ auth: ## | mtls | Yes (two-way authentication) | Yes | ## | sasl | Yes (via SASL) | No | ## | sasl_tls | Yes (via SASL) | Yes | + ## @param auth.clientProtocol Authentication protocol for communications with clients. Allowed protocols: `plaintext`, `tls`, `mtls`, `sasl` and `sasl_tls` + ## @param auth.externalClientProtocol Authentication protocol for communications with external clients. Defaults to value of `auth.clientProtocol`. Allowed protocols: `plaintext`, `tls`, `mtls`, `sasl` and `sasl_tls` + ## @param auth.interBrokerProtocol Authentication protocol for inter-broker communications. Allowed protocols: `plaintext`, `tls`, `mtls`, `sasl` and `sasl_tls` ## clientProtocol: plaintext + # Note: empty by default for backwards compatibility reasons, find more information at + # https://github.com/bitnami/charts/pull/8902/ + externalClientProtocol: "" interBrokerProtocol: plaintext - - ## Allowed SASL mechanisms when clientProtocol or interBrokerProtocol are using either sasl or sasl_tls + ## SASL configuration ## - saslMechanisms: plain,scram-sha-256,scram-sha-512 - ## SASL mechanism for inter broker communication + sasl: + ## @param auth.sasl.mechanisms SASL mechanisms when either `auth.interBrokerProtocol`, `auth.clientProtocol` or `auth.externalClientProtocol` are `sasl`. Allowed types: `plain`, `scram-sha-256`, `scram-sha-512` + ## + mechanisms: plain,scram-sha-256,scram-sha-512 + ## @param auth.sasl.interBrokerMechanism SASL mechanism for inter broker communication. + ## + interBrokerMechanism: plain + ## JAAS configuration for SASL authentication. + ## + jaas: + ## @param auth.sasl.jaas.clientUsers Kafka client user list + ## + ## clientUsers: + ## - user1 + ## - user2 + ## + clientUsers: + - user + ## @param auth.sasl.jaas.clientPasswords Kafka client passwords. This is mandatory if more than one user is specified in clientUsers + ## + ## clientPasswords: + ## - password1 + ## - password2" + ## + clientPasswords: [] + ## @param auth.sasl.jaas.interBrokerUser Kafka inter broker communication user for SASL authentication + ## + interBrokerUser: admin + ## @param auth.sasl.jaas.interBrokerPassword Kafka inter broker communication password for SASL authentication + ## + interBrokerPassword: "" + ## @param auth.sasl.jaas.zookeeperUser Kafka ZooKeeper user for SASL authentication + ## + zookeeperUser: "" + ## @param auth.sasl.jaas.zookeeperPassword Kafka ZooKeeper password for SASL authentication + ## + zookeeperPassword: "" + ## @param auth.sasl.jaas.existingSecret Name of the existing secret containing credentials for clientUsers, interBrokerUser and zookeeperUser + ## Create this secret running the command below where SECRET_NAME is the name of the secret you want to create: + ## kubectl create secret generic SECRET_NAME --from-literal=client-passwords=CLIENT_PASSWORD1,CLIENT_PASSWORD2 --from-literal=inter-broker-password=INTER_BROKER_PASSWORD --from-literal=zookeeper-password=ZOOKEEPER_PASSWORD + ## + existingSecret: "" + ## TLS configuration ## - saslInterBrokerMechanism: plain - - ## Name of the existing secret containing the truststore and - ## one keystore per Kafka broker you have in the Kafka cluster. - ## MANDATORY when 'tls', 'mtls', or 'sasl_tls' authentication protocols are used. - ## Create this secret following the steps below: - ## 1) Generate your trustore and keystore files. Helpful script: https://raw.githubusercontent.com/confluentinc/confluent-platform-security-tools/master/kafka-generate-ssl.sh - ## 2) Rename your truststore to `kafka.truststore.jks`. - ## 3) Rename your keystores to `kafka-X.keystore.jks` where X is the ID of each Kafka broker. - ## 4) Run the command below where SECRET_NAME is the name of the secret you want to create: - ## kubectl create secret generic SECRET_NAME --from-file=./kafka.truststore.jks --from-file=./kafka-0.keystore.jks --from-file=./kafka-1.keystore.jks ... - ## Alternatively, you can put your JKS files under the files/jks directory + tls: + ## @param auth.tls.type Format to use for TLS certificates. Allowed types: `jks` and `pem` + ## + type: jks + ## @param auth.tls.pemChainIncluded Flag to denote that the Certificate Authority (CA) certificates are bundled with the endpoint cert. + ## Certificates must be in proper order, where the top certificate is the leaf and the bottom certificate is the top-most intermediate CA. + ## + pemChainIncluded: false + ## @param auth.tls.existingSecrets Array existing secrets containing the TLS certificates for the Kafka brokers + ## When using 'jks' format for certificates, each secret should contain a truststore and a keystore. + ## Create these secrets following the steps below: + ## 1) Generate your truststore and keystore files. Helpful script: https://raw.githubusercontent.com/confluentinc/confluent-platform-security-tools/master/kafka-generate-ssl.sh + ## 2) Rename your truststore to `kafka.truststore.jks`. + ## 3) Rename your keystores to `kafka-X.keystore.jks` where X is the ID of each Kafka broker. + ## 4) Run the command below one time per broker to create its associated secret (SECRET_NAME_X is the name of the secret you want to create): + ## kubectl create secret generic SECRET_NAME_0 --from-file=kafka.truststore.jks=./kafka.truststore.jks --from-file=kafka.keystore.jks=./kafka-0.keystore.jks + ## kubectl create secret generic SECRET_NAME_1 --from-file=kafka.truststore.jks=./kafka.truststore.jks --from-file=kafka.keystore.jks=./kafka-1.keystore.jks + ## ... + ## + ## When using 'pem' format for certificates, each secret should contain a public CA certificate, a public certificate and one private key. + ## Create these secrets following the steps below: + ## 1) Create a certificate key and signing request per Kafka broker, and sign the signing request with your CA + ## 2) Rename your CA file to `kafka.ca.crt`. + ## 3) Rename your certificates to `kafka-X.tls.crt` where X is the ID of each Kafka broker. + ## 3) Rename your keys to `kafka-X.tls.key` where X is the ID of each Kafka broker. + ## 4) Run the command below one time per broker to create its associated secret (SECRET_NAME_X is the name of the secret you want to create): + ## kubectl create secret generic SECRET_NAME_0 --from-file=ca.crt=./kafka.ca.crt --from-file=tls.crt=./kafka-0.tls.crt --from-file=tls.key=./kafka-0.tls.key + ## kubectl create secret generic SECRET_NAME_1 --from-file=ca.crt=./kafka.ca.crt --from-file=tls.crt=./kafka-1.tls.crt --from-file=tls.key=./kafka-1.tls.key + ## ... + ## + existingSecrets: [] + ## @param auth.tls.autoGenerated Generate automatically self-signed TLS certificates for Kafka brokers. Currently only supported if `auth.tls.type` is `pem` + ## Note: ignored when using 'jks' format or `auth.tls.existingSecrets` is not empty + ## + autoGenerated: false + ## @param auth.tls.password Password to access the JKS files or PEM key when they are password-protected. + ## Note: ignored when using 'existingSecret'. + ## + password: "" + ## @param auth.tls.existingSecret Name of the secret containing the password to access the JKS files or PEM key when they are password-protected. (`key`: `password`) + ## + existingSecret: "" + ## @param auth.tls.jksTruststoreSecret Name of the existing secret containing your truststore if truststore not existing or different from the ones in the `auth.tls.existingSecrets` + ## Note: ignored when using 'pem' format for certificates. + ## + jksTruststoreSecret: "" + ## @param auth.tls.jksKeystoreSAN The secret key from the `auth.tls.existingSecrets` containing the keystore with a SAN certificate + ## The SAN certificate in it should be issued with Subject Alternative Names for all headless services: + ## - kafka-0.kafka-headless.kafka.svc.cluster.local + ## - kafka-1.kafka-headless.kafka.svc.cluster.local + ## - kafka-2.kafka-headless.kafka.svc.cluster.local + ## Note: ignored when using 'pem' format for certificates. + ## + jksKeystoreSAN: "" + ## @param auth.tls.jksTruststore The secret key from the `auth.tls.existingSecrets` or `auth.tls.jksTruststoreSecret` containing the truststore + ## Note: ignored when using 'pem' format for certificates. + ## + jksTruststore: "" + ## @param auth.tls.endpointIdentificationAlgorithm The endpoint identification algorithm to validate server hostname using server certificate + ## Disable server host name verification by setting it to an empty string. + ## ref: https://docs.confluent.io/current/kafka/authentication_ssl.html#optional-settings + ## + endpointIdentificationAlgorithm: https + ## Zookeeper client configuration for kafka brokers ## - # jksSecret: - - ## Password to access the JKS files when they are password-protected. - ## - # jksPassword: - - ## The endpoint identification algorithm used by clients to validate server host name. - ## Disable server host name verification by setting it to an empty string - ## See: https://docs.confluent.io/current/kafka/authentication_ssl.html#optional-settings - ## - tlsEndpointIdentificationAlgorithm: https - - ## JAAS configuration for SASL authentication - ## MANDATORY when method is 'sasl', or 'sasl_tls' - ## - jaas: - ## Kafka client user list + zookeeper: + ## TLS configuration ## - ## clientUsers: - ## - user1 - ## - user2 - ## - clientUsers: - - user - - ## Kafka client passwords. This is mandatory if more than one user is specified in clientUsers. - ## - ## clientPasswords: - ## - password1 - ## - password2" - ## - clientPasswords: [] - - ## Kafka inter broker communication user - ## - interBrokerUser: admin - - ## Kafka inter broker communication password - ## - interBrokerPassword: "" - - ## Kafka Zookeeper user - ## - # zookeeperUser: - - ## Kafka Zookeeper password - ## - # zookeeperPassword: - - ## Name of the existing secret containing credentials for clientUsers, interBrokerUser and zookeeperUser. - ## Create this secret running the command below where SECRET_NAME is the name of the secret you want to create: - ## kubectl create secret generic SECRET_NAME --from-literal=client-passwords=CLIENT_PASSWORD1,CLIENT_PASSWORD2 --from-literal=inter-broker-password=INTER_BROKER_PASSWORD --from-literal=zookeeper-password=ZOOKEEPER_PASSWORD - ## - # existingSecret: - -## The address(es) the socket server listens on. + tls: + ## @param auth.zookeeper.tls.enabled Enable TLS for Zookeeper client connections. + ## + enabled: false + ## @param auth.zookeeper.tls.type Format to use for TLS certificates. Allowed types: `jks` and `pem`. + ## + type: jks + ## @param auth.zookeeper.tls.verifyHostname Hostname validation. + ## + verifyHostname: true + ## @param auth.zookeeper.tls.existingSecret Name of the existing secret containing the TLS certificates for ZooKeeper client communications. + ## + existingSecret: "" + ## @param auth.zookeeper.tls.existingSecretKeystoreKey The secret key from the auth.zookeeper.tls.existingSecret containing the Keystore. + ## + existingSecretKeystoreKey: zookeeper.keystore.jks + ## @param auth.zookeeper.tls.existingSecretTruststoreKey The secret key from the auth.zookeeper.tls.existingSecret containing the Truststore. + ## + existingSecretTruststoreKey: zookeeper.truststore.jks + ## @param auth.zookeeper.tls.passwordsSecret Existing secret containing Keystore and Truststore passwords. + ## + passwordsSecret: "" + ## @param auth.zookeeper.tls.passwordsSecretKeystoreKey The secret key from the auth.zookeeper.tls.passwordsSecret containing the password for the Keystore. + ## + passwordsSecretKeystoreKey: keystore-password + ## @param auth.zookeeper.tls.passwordsSecretTruststoreKey The secret key from the auth.zookeeper.tls.passwordsSecret containing the password for the Truststore. + ## + passwordsSecretTruststoreKey: truststore-password +## @param listeners The address(es) the socket server listens on. Auto-calculated it's set to an empty array ## When it's set to an empty array, the listeners will be configured -## based on the authentication protocols (auth.clientProtocol and auth.interBrokerProtocol parameters) +## based on the authentication protocols (auth.clientProtocol, auth.externalClientProtocol and auth.interBrokerProtocol parameters) ## listeners: [] - -## The address(es) (hostname:port) the brokers will advertise to producers and consumers. +## @param advertisedListeners The address(es) (hostname:port) the broker will advertise to producers and consumers. Auto-calculated it's set to an empty array ## When it's set to an empty array, the advertised listeners will be configured -## based on the authentication protocols (auth.clientProtocol and auth.interBrokerProtocol parameters) +## based on the authentication protocols (auth.clientProtocol, auth.externalClientProtocol and auth.interBrokerProtocol parameters) ## advertisedListeners: [] - -## The listener->protocol mapping -## When it's nil, the listeners will be configured -## based on the authentication protocols (auth.clientProtocol and auth.interBrokerProtocol parameters) +## @param listenerSecurityProtocolMap The protocol->listener mapping. Auto-calculated it's set to nil +## When it's nil, the listeners will be configured based on the authentication protocols (auth.clientProtocol, auth.externalClientProtocol and auth.interBrokerProtocol parameters) ## -# listenerSecurityProtocolMap: - -## Allow to use the PLAINTEXT listener. +listenerSecurityProtocolMap: "" +## @param allowPlaintextListener Allow to use the PLAINTEXT listener ## allowPlaintextListener: true - -## Name of listener used for communication between brokers. +## @param interBrokerListenerName The listener that the brokers should communicate on ## interBrokerListenerName: INTERNAL - -## Number of Kafka brokers to deploy +## @param command Override Kafka container command ## -replicaCount: 2 - -## StrategyType, can be set to RollingUpdate or OnDelete by default. -## ref: https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets +command: + - /scripts/setup.sh +## @param args Override Kafka container arguments ## -updateStrategy: RollingUpdate - -## Partition update strategy -## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions +args: [] +## @param extraEnvVars Extra environment variables to add to Kafka pods +## ref: https://github.com/bitnami/containers/tree/main/bitnami/kafka#configuration +## e.g: +## extraEnvVars: +## - name: KAFKA_CFG_BACKGROUND_THREADS +## value: "10" ## -# rollingUpdatePartition: - -## Pod labels. Evaluated as a template -## Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +extraEnvVars: [] +## @param extraEnvVarsCM ConfigMap with extra environment variables ## -podLabels: {} - -## Pod annotations. Evaluated as a template -## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +extraEnvVarsCM: "" +## @param extraEnvVarsSecret Secret with extra environment variables ## -podAnnotations: {} +extraEnvVarsSecret: "" -## Name of the priority class to be used by kafka pods, priority class needs to be created beforehand -## Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ +## @section Statefulset parameters + +## @param replicaCount Number of Kafka nodes ## -priorityClassName: "" - -## Affinity for pod assignment. Evaluated as a template -## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +replicaCount: 1 +## @param minBrokerId Minimal broker.id value, nodes increment their `broker.id` respectively +## Brokers increment their ID starting at this minimal value. +## E.g., with `minBrokerId=100` and 3 nodes, IDs will be 100, 101, 102 for brokers 0, 1, and 2, respectively. ## -affinity: {} - -## Node labels for pod assignment. Evaluated as a template -## ref: https://kubernetes.io/docs/user-guide/node-selection/ +minBrokerId: 0 +## @param brokerRackAssignment Set Broker Assignment for multi tenant environment Allowed values: `aws-az` +## ref: https://cwiki.apache.org/confluence/display/KAFKA/KIP-392%3A+Allow+consumers+to+fetch+from+closest+replica ## -nodeSelector: {} - -## Tolerations for pod assignment. Evaluated as a template -## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +brokerRackAssignment: "" +## @param containerPorts.client Kafka client container port +## @param containerPorts.internal Kafka inter-broker container port +## @param containerPorts.external Kafka external container port ## -tolerations: [] - -## Kafka pods' Security Context -## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod -## -podSecurityContext: - fsGroup: 1001 - runAsUser: 1001 - -## Kafka containers' Security Context -## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container -## Example: -## containerSecurityContext: -## capabilities: -## drop: ["NET_RAW"] -## readOnlyRootFilesystem: true -## -containerSecurityContext: {} - -## Kafka containers' resource requests and limits -## ref: http://kubernetes.io/docs/user-guide/compute-resources/ -## -resources: - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - limits: {} - # cpu: 250m - # memory: 1Gi - requests: {} - # cpu: 250m - # memory: 256Mi - -## Kafka containers' liveness and readiness probes. Evaluated as a template. -## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes +containerPorts: + client: 9092 + internal: 9093 + external: 9094 +## Configure extra options for Kafka containers' liveness, readiness and startup probes +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes +## @param livenessProbe.enabled Enable livenessProbe on Kafka containers +## @param livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe +## @param livenessProbe.periodSeconds Period seconds for livenessProbe +## @param livenessProbe.timeoutSeconds Timeout seconds for livenessProbe +## @param livenessProbe.failureThreshold Failure threshold for livenessProbe +## @param livenessProbe.successThreshold Success threshold for livenessProbe ## livenessProbe: enabled: true initialDelaySeconds: 10 timeoutSeconds: 5 - # failureThreshold: 3 - # periodSeconds: 10 - # successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 + successThreshold: 1 +## @param readinessProbe.enabled Enable readinessProbe on Kafka containers +## @param readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe +## @param readinessProbe.periodSeconds Period seconds for readinessProbe +## @param readinessProbe.timeoutSeconds Timeout seconds for readinessProbe +## @param readinessProbe.failureThreshold Failure threshold for readinessProbe +## @param readinessProbe.successThreshold Success threshold for readinessProbe +## readinessProbe: enabled: true initialDelaySeconds: 5 failureThreshold: 6 timeoutSeconds: 5 - # periodSeconds: 10 - # successThreshold: 1 - -## Custom liveness/readiness probes that will override the default ones + periodSeconds: 10 + successThreshold: 1 +## @param startupProbe.enabled Enable startupProbe on Kafka containers +## @param startupProbe.initialDelaySeconds Initial delay seconds for startupProbe +## @param startupProbe.periodSeconds Period seconds for startupProbe +## @param startupProbe.timeoutSeconds Timeout seconds for startupProbe +## @param startupProbe.failureThreshold Failure threshold for startupProbe +## @param startupProbe.successThreshold Success threshold for startupProbe +## +startupProbe: + enabled: false + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 15 + successThreshold: 1 +## @param customLivenessProbe Custom livenessProbe that overrides the default one ## customLivenessProbe: {} -customReadinessProbe: {} - -## Pod Disruption Budget configuration -## The PDB will only be created if replicaCount is greater than 1 -## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions +## @param customReadinessProbe Custom readinessProbe that overrides the default one ## -pdb: - create: true - ## Min number of pods that must still be available after the eviction +customReadinessProbe: {} +## @param customStartupProbe Custom startupProbe that overrides the default one +## +customStartupProbe: {} +## @param lifecycleHooks lifecycleHooks for the Kafka container to automate configuration before or after startup +## +lifecycleHooks: {} +## Kafka resource requests and limits +## ref: https://kubernetes.io/docs/user-guide/compute-resources/ +## @param resources.limits The resources limits for the container +## @param resources.requests The requested resources for the container +## +resources: + limits: {} + requests: {} +## Kafka pods' Security Context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod +## @param podSecurityContext.enabled Enable security context for the pods +## @param podSecurityContext.fsGroup Set Kafka pod's Security Context fsGroup +## +podSecurityContext: + enabled: true + fsGroup: 1001 +## Kafka containers' Security Context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container +## @param containerSecurityContext.enabled Enable Kafka containers' Security Context +## @param containerSecurityContext.runAsUser Set Kafka containers' Security Context runAsUser +## @param containerSecurityContext.runAsNonRoot Set Kafka containers' Security Context runAsNonRoot +## @param containerSecurityContext.allowPrivilegeEscalation Force the child process to be run as nonprivilege +## e.g: +## containerSecurityContext: +## enabled: true +## capabilities: +## drop: ["NET_RAW"] +## readOnlyRootFilesystem: true +## +containerSecurityContext: + enabled: true + runAsUser: 1001 + runAsNonRoot: true + allowPrivilegeEscalation: false +## @param hostAliases Kafka pods host aliases +## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ +## +hostAliases: [] +## @param hostNetwork Specify if host network should be enabled for Kafka pods +## +hostNetwork: false +## @param hostIPC Specify if host IPC should be enabled for Kafka pods +## +hostIPC: false +## @param podLabels Extra labels for Kafka pods +## Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +## +podLabels: {} +## @param podAnnotations Extra annotations for Kafka pods +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +## +podAnnotations: {} +## @param podAffinityPreset Pod affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` +## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity +## +podAffinityPreset: "" +## @param podAntiAffinityPreset Pod anti-affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` +## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity +## +podAntiAffinityPreset: soft +## Node affinity preset +## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity +## +nodeAffinityPreset: + ## @param nodeAffinityPreset.type Node affinity preset type. Ignored if `affinity` is set. Allowed values: `soft` or `hard` ## - # minAvailable: 1 - ## Max number of pods that can be unavailable after the eviction + type: "" + ## @param nodeAffinityPreset.key Node label key to match Ignored if `affinity` is set. + ## E.g. + ## key: "kubernetes.io/e2e-az-name" ## - maxUnavailable: 1 - -## Add sidecars to the pod. -## Example: + key: "" + ## @param nodeAffinityPreset.values Node label values to match. Ignored if `affinity` is set. + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] +## @param affinity Affinity for pod assignment +## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +## Note: podAffinityPreset, podAntiAffinityPreset, and nodeAffinityPreset will be ignored when it's set +## +affinity: {} +## @param nodeSelector Node labels for pod assignment +## Ref: https://kubernetes.io/docs/user-guide/node-selection/ +## +nodeSelector: {} +## @param tolerations Tolerations for pod assignment +## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +## +tolerations: [] +## @param topologySpreadConstraints Topology Spread Constraints for pod assignment spread across your cluster among failure-domains. Evaluated as a template +## Ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/#spread-constraints-for-pods +## +topologySpreadConstraints: [] +## @param terminationGracePeriodSeconds Seconds the pod needs to gracefully terminate +## ref: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#hook-handler-execution +## +terminationGracePeriodSeconds: "" +## @param podManagementPolicy StatefulSet controller supports relax its ordering guarantees while preserving its uniqueness and identity guarantees. There are two valid pod management policies: OrderedReady and Parallel +## ref: https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#pod-management-policy +## +podManagementPolicy: Parallel +## @param priorityClassName Name of the existing priority class to be used by kafka pods +## Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ +## +priorityClassName: "" +## @param schedulerName Name of the k8s scheduler (other than default) +## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ +## +schedulerName: "" +## @param updateStrategy.type Kafka statefulset strategy type +## @param updateStrategy.rollingUpdate Kafka statefulset rolling update configuration parameters +## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies +## +updateStrategy: + type: RollingUpdate + rollingUpdate: {} +## @param extraVolumes Optionally specify extra list of additional volumes for the Kafka pod(s) +## e.g: +## extraVolumes: +## - name: kafka-jaas +## secret: +## secretName: kafka-jaas +## +extraVolumes: [] +## @param extraVolumeMounts Optionally specify extra list of additional volumeMounts for the Kafka container(s) +## extraVolumeMounts: +## - name: kafka-jaas +## mountPath: /bitnami/kafka/config/kafka_jaas.conf +## subPath: kafka_jaas.conf +## +extraVolumeMounts: [] +## @param sidecars Add additional sidecar containers to the Kafka pod(s) +## e.g: ## sidecars: ## - name: your-image-name ## image: your-image @@ -465,389 +641,748 @@ pdb: ## - name: portname ## containerPort: 1234 ## -sidecars: {} +sidecars: [] +## @param initContainers Add additional Add init containers to the Kafka pod(s) +## e.g: +## initContainers: +## - name: your-image-name +## image: your-image +## imagePullPolicy: Always +## ports: +## - name: portname +## containerPort: 1234 +## +initContainers: [] +## Kafka Pod Disruption Budget +## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ +## @param pdb.create Deploy a pdb object for the Kafka pod +## @param pdb.minAvailable Maximum number/percentage of unavailable Kafka replicas +## @param pdb.maxUnavailable Maximum number/percentage of unavailable Kafka replicas +## +pdb: + create: false + minAvailable: "" + maxUnavailable: 1 + +## @section Traffic Exposure parameters ## Service parameters ## service: - ## Service type + ## @param service.type Kubernetes Service type ## type: ClusterIP - ## Kafka port for client connections + ## @param service.ports.client Kafka svc port for client connections + ## @param service.ports.internal Kafka svc port for inter-broker connections + ## @param service.ports.external Kafka svc port for external connections ## - port: 9092 - ## Kafka port for inter-broker connections - ## - internalPort: 9093 - ## Kafka port for external connections - ## - externalPort: 9094 - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ports: + client: 9092 + internal: 9093 + external: 9094 + ## @param service.nodePorts.client Node port for the Kafka client connections + ## @param service.nodePorts.external Node port for the Kafka external connections + ## NOTE: choose port between <30000-32767> ## nodePorts: client: "" external: "" - ## Set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## @param service.sessionAffinity Control where client requests go, to the same pod or round-robin + ## Values: ClientIP or None + ## ref: https://kubernetes.io/docs/user-guide/services/ ## - # loadBalancerIP: - ## Load Balancer sources + sessionAffinity: None + ## @param service.sessionAffinityConfig Additional settings for the sessionAffinity + ## sessionAffinityConfig: + ## clientIP: + ## timeoutSeconds: 300 + ## + sessionAffinityConfig: {} + ## @param service.clusterIP Kafka service Cluster IP + ## e.g.: + ## clusterIP: None + ## + clusterIP: "" + ## @param service.loadBalancerIP Kafka service Load Balancer IP + ## ref: https://kubernetes.io/docs/user-guide/services/#type-loadbalancer + ## + loadBalancerIP: "" + ## @param service.loadBalancerSourceRanges Kafka service Load Balancer sources ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service - ## Example: + ## e.g: ## loadBalancerSourceRanges: - ## - 10.10.10.0/24 + ## - 10.10.10.0/24 ## loadBalancerSourceRanges: [] - ## Provide any additional annotations which may be required. Evaluated as a template + ## @param service.externalTrafficPolicy Kafka service external traffic policy + ## ref https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip + ## + externalTrafficPolicy: Cluster + ## @param service.annotations Additional custom annotations for Kafka service ## annotations: {} - + ## Headless service properties + ## + headless: + ## @param service.headless.publishNotReadyAddresses Indicates that any agent which deals with endpoints for this Service should disregard any indications of ready/not-ready + ## ref: https://kubernetes.io/docs/reference/kubernetes-api/service-resources/service-v1/ + publishNotReadyAddresses: false + ## @param service.headless.annotations Annotations for the headless service. + ## + annotations: {} + ## @param service.headless.labels Labels for the headless service. + ## + labels: {} + ## @param service.extraPorts Extra ports to expose in the Kafka service (normally used with the `sidecar` value) + ## + extraPorts: [] ## External Access to Kafka brokers configuration ## externalAccess: - ## Enable Kubernetes external cluster access to Kafka brokers + ## @param externalAccess.enabled Enable Kubernetes external cluster access to Kafka brokers ## enabled: false - ## External IPs auto-discovery configuration ## An init container is used to auto-detect LB IPs or node ports by querying the K8s API ## Note: RBAC might be required ## autoDiscovery: - ## Enable external IP/ports auto-discovery + ## @param externalAccess.autoDiscovery.enabled Enable using an init container to auto-detect external IPs/ports by querying the K8s API ## enabled: false ## Bitnami Kubectl image ## ref: https://hub.docker.com/r/bitnami/kubectl/tags/ + ## @param externalAccess.autoDiscovery.image.registry Init container auto-discovery image registry + ## @param externalAccess.autoDiscovery.image.repository Init container auto-discovery image repository + ## @param externalAccess.autoDiscovery.image.tag Init container auto-discovery image tag (immutable tags are recommended) + ## @param externalAccess.autoDiscovery.image.digest Petete image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag + ## @param externalAccess.autoDiscovery.image.pullPolicy Init container auto-discovery image pull policy + ## @param externalAccess.autoDiscovery.image.pullSecrets Init container auto-discovery image pull secrets ## image: registry: docker.io repository: bitnami/kubectl - tag: 1.17.12-debian-10-r3 + tag: 1.25.6-debian-11-r1 + digest: "" ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## ref: https://kubernetes.io/docs/user-guide/images/#pre-pulling-images ## pullPolicy: IfNotPresent ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## Example: + ## e.g: ## pullSecrets: ## - myRegistryKeySecretName ## pullSecrets: [] ## Init Container resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param externalAccess.autoDiscovery.resources.limits The resources limits for the auto-discovery init container + ## @param externalAccess.autoDiscovery.resources.requests The requested resources for the auto-discovery init container ## resources: - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. limits: {} - # cpu: 100m - # memory: 128Mi requests: {} - # cpu: 100m - # memory: 128Mi - ## Parameters to configure K8s service(s) used to externally access Kafka brokers - ## A new service per broker will be created + ## Note: A new service per broker will be created ## service: - ## Service type. Allowed values: LoadBalancer or NodePort + ## @param externalAccess.service.type Kubernetes Service type for external access. It can be NodePort, LoadBalancer or ClusterIP ## type: LoadBalancer - ## Port used when service type is LoadBalancer + ## @param externalAccess.service.ports.external Kafka port used for external access when service type is LoadBalancer ## - port: 9094 - ## Array of load balancer IPs for each Kafka broker. Length must be the same as replicaCount - ## Example: + ports: + external: 9094 + ## @param externalAccess.service.loadBalancerIPs Array of load balancer IPs for each Kafka broker. Length must be the same as replicaCount + ## e.g: ## loadBalancerIPs: ## - X.X.X.X ## - Y.Y.Y.Y ## loadBalancerIPs: [] - ## Load Balancer sources + ## @param externalAccess.service.loadBalancerNames Array of load balancer Names for each Kafka broker. Length must be the same as replicaCount + ## e.g: + ## loadBalancerNames: + ## - broker1.external.example.com + ## - broker2.external.example.com + ## + loadBalancerNames: [] + ## @param externalAccess.service.loadBalancerAnnotations Array of load balancer annotations for each Kafka broker. Length must be the same as replicaCount + ## e.g: + ## loadBalancerAnnotations: + ## - external-dns.alpha.kubernetes.io/hostname: broker1.external.example.com. + ## - external-dns.alpha.kubernetes.io/hostname: broker2.external.example.com. + ## + loadBalancerAnnotations: [] + ## @param externalAccess.service.loadBalancerSourceRanges Address(es) that are allowed when service is LoadBalancer ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service - ## Example: + ## e.g: ## loadBalancerSourceRanges: ## - 10.10.10.0/24 ## loadBalancerSourceRanges: [] - ## Array of node ports used for each Kafka broker. Length must be the same as replicaCount - ## Example: + ## @param externalAccess.service.nodePorts Array of node ports used for each Kafka broker. Length must be the same as replicaCount + ## e.g: ## nodePorts: ## - 30001 ## - 30002 ## nodePorts: [] - ## When service type is NodePort, you can specify the domain used for Kafka advertised listeners. - ## If not specified, the container will try to get the kubernetes node external IP + ## @param externalAccess.service.useHostIPs Use service host IPs to configure Kafka external listener when service type is NodePort ## - # domain: mydomain.com - ## Provide any additional annotations which may be required. Evaluated as a template + useHostIPs: false + ## @param externalAccess.service.usePodIPs using the MY_POD_IP address for external access. + ## + usePodIPs: false + ## @param externalAccess.service.domain Domain or external ip used to configure Kafka external listener when service type is NodePort or ClusterIP + ## NodePort: If not specified, the container will try to get the kubernetes node external IP + ## ClusterIP: Must be specified, ingress IP or domain where tcp for external ports is configured + ## + domain: "" + ## @param externalAccess.service.publishNotReadyAddresses Indicates that any agent which deals with endpoints for this Service should disregard any indications of ready/not-ready + ## ref: https://kubernetes.io/docs/reference/kubernetes-api/service-resources/service-v1/ + publishNotReadyAddresses: false + ## @param externalAccess.service.labels Service labels for external access + ## + labels: {} + ## @param externalAccess.service.annotations Service annotations for external access ## annotations: {} + ## @param externalAccess.service.extraPorts Extra ports to expose in the Kafka external service + ## + extraPorts: [] +## Network policies +## Ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +## +networkPolicy: + ## @param networkPolicy.enabled Specifies whether a NetworkPolicy should be created + ## + enabled: false + ## @param networkPolicy.allowExternal Don't require client label for connections + ## When set to false, only pods with the correct client label will have network access to the port Kafka is + ## listening on. When true, zookeeper accept connections from any source (with the correct destination port). + ## + allowExternal: true + ## @param networkPolicy.explicitNamespacesSelector A Kubernetes LabelSelector to explicitly select namespaces from which traffic could be allowed + ## If explicitNamespacesSelector is missing or set to {}, only client Pods that are in the networkPolicy's namespace + ## and that match other criteria, the ones that have the good label, can reach the kafka. + ## But sometimes, we want the kafka to be accessible to clients from other namespaces, in this case, we can use this + ## LabelSelector to select these namespaces, note that the networkPolicy's namespace should also be explicitly added. + ## + ## e.g: + ## explicitNamespacesSelector: + ## matchLabels: + ## role: frontend + ## matchExpressions: + ## - {key: role, operator: In, values: [frontend]} + ## + explicitNamespacesSelector: {} + ## @param networkPolicy.externalAccess.from customize the from section for External Access on tcp-external port + ## e.g: + ## - ipBlock: + ## cidr: 172.9.0.0/16 + ## except: + ## - 172.9.1.0/24 + ## + externalAccess: + from: [] + ## @param networkPolicy.egressRules.customRules [object] Custom network policy rule + ## + egressRules: + ## Additional custom egress rules + ## e.g: + ## customRules: + ## - to: + ## - namespaceSelector: + ## matchLabels: + ## label: example + customRules: [] -## Persistence paramaters +## @section Persistence parameters + +## Enable persistence using Persistent Volume Claims +## ref: https://kubernetes.io/docs/user-guide/persistent-volumes/ ## persistence: + ## @param persistence.enabled Enable Kafka data persistence using PVC, note that ZooKeeper persistence is unaffected + ## enabled: true - ## A manually managed Persistent Volume and Claim + ## @param persistence.existingClaim A manually managed Persistent Volume and Claim ## If defined, PVC must be created manually before volume will be bound ## The value is evaluated as a template ## - # existingClaim: - ## PV Storage Class + existingClaim: "" + ## @param persistence.storageClass PVC Storage Class for Kafka data volume ## If defined, storageClassName: <storageClass> ## If set to "-", storageClassName: "", which disables dynamic provisioning ## If undefined (the default) or set to null, no storageClassName spec is ## set, choosing the default provisioner. ## - # storageClass: "-" - ## PV Access Mode + storageClass: "" + ## @param persistence.accessModes Persistent Volume Access Modes ## accessModes: - ReadWriteOnce - ## PVC size + ## @param persistence.size PVC Storage Request for Kafka data volume ## size: 8Gi - ## PVC annotations + ## @param persistence.annotations Annotations for the PVC ## annotations: {} + ## @param persistence.labels Labels for the PVC + ## + labels: {} + ## @param persistence.selector Selector to match an existing Persistent Volume for Kafka data PVC. If set, the PVC can't have a PV dynamically provisioned for it + ## selector: + ## matchLabels: + ## app: my-app + ## + selector: {} + ## @param persistence.mountPath Mount path of the Kafka data volume + ## + mountPath: /bitnami/kafka +## Log Persistence parameters +## +logPersistence: + ## @param logPersistence.enabled Enable Kafka logs persistence using PVC, note that ZooKeeper persistence is unaffected + ## + enabled: false + ## @param logPersistence.existingClaim A manually managed Persistent Volume and Claim + ## If defined, PVC must be created manually before volume will be bound + ## The value is evaluated as a template + ## + existingClaim: "" + ## @param logPersistence.storageClass PVC Storage Class for Kafka logs volume + ## If defined, storageClassName: <storageClass> + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. + ## + storageClass: "" + ## @param logPersistence.accessModes Persistent Volume Access Modes + ## + accessModes: + - ReadWriteOnce + ## @param logPersistence.size PVC Storage Request for Kafka logs volume + ## + size: 8Gi + ## @param logPersistence.annotations Annotations for the PVC + ## + annotations: {} + ## @param logPersistence.selector Selector to match an existing Persistent Volume for Kafka log data PVC. If set, the PVC can't have a PV dynamically provisioned for it + ## selector: + ## matchLabels: + ## app: my-app + ## + selector: {} + ## @param logPersistence.mountPath Mount path of the Kafka logs volume + ## + mountPath: /opt/bitnami/kafka/logs -## Init Container paramaters -## Change the owner and group of the persistent volume(s) mountpoint(s) to 'runAsUser:fsGroup' on each component -## values from the securityContext section of the component +## @section Volume Permissions parameters +## + +## Init containers parameters: +## volumePermissions: Change the owner and group of the persistent volume(s) mountpoint(s) to 'runAsUser:fsGroup' on each node ## volumePermissions: + ## @param volumePermissions.enabled Enable init container that changes the owner and group of the persistent volume + ## enabled: false - ## Bitnami Minideb image - ## ref: https://hub.docker.com/r/bitnami/minideb/tags/ + ## @param volumePermissions.image.registry Init container volume-permissions image registry + ## @param volumePermissions.image.repository Init container volume-permissions image repository + ## @param volumePermissions.image.tag Init container volume-permissions image tag (immutable tags are recommended) + ## @param volumePermissions.image.digest Init container volume-permissions image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag + ## @param volumePermissions.image.pullPolicy Init container volume-permissions image pull policy + ## @param volumePermissions.image.pullSecrets Init container volume-permissions image pull secrets ## image: registry: docker.io - repository: bitnami/minideb - tag: buster - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: Always - ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) + repository: bitnami/bitnami-shell + tag: 11-debian-11-r75 + digest: "" + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ ## Example: ## pullSecrets: ## - myRegistryKeySecretName ## pullSecrets: [] - ## Init Container resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## Init container resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param volumePermissions.resources.limits Init container volume-permissions resource limits + ## @param volumePermissions.resources.requests Init container volume-permissions resource requests ## resources: - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. limits: {} - # cpu: 100m - # memory: 128Mi requests: {} - # cpu: 100m - # memory: 128Mi + ## Init container' Security Context + ## Note: the chown of the data folder is done to containerSecurityContext.runAsUser + ## and not the below volumePermissions.containerSecurityContext.runAsUser + ## @param volumePermissions.containerSecurityContext.runAsUser User ID for the init container + ## + containerSecurityContext: + runAsUser: 0 -## Kafka pods ServiceAccount +## @section Other Parameters + +## ServiceAccount for Kafka ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ ## serviceAccount: - ## Specifies whether a ServiceAccount should be created + ## @param serviceAccount.create Enable creation of ServiceAccount for Kafka pods ## create: true - ## The name of the ServiceAccount to use. - ## If not set and create is true, a name is generated using the fluentd.fullname template + ## @param serviceAccount.name The name of the service account to use. If not set and `create` is `true`, a name is generated + ## If not set and create is true, a name is generated using the kafka.serviceAccountName template ## - # name: - -## Role Based Access + name: "" + ## @param serviceAccount.automountServiceAccountToken Allows auto mount of ServiceAccountToken on the serviceAccount created + ## Can be set to false if pods using this serviceAccount do not need to use K8s API + ## + automountServiceAccountToken: true + ## @param serviceAccount.annotations Additional custom annotations for the ServiceAccount + ## + annotations: {} +## Role Based Access Control ## ref: https://kubernetes.io/docs/admin/authorization/rbac/ ## rbac: - ## Specifies whether RBAC rules should be created + ## @param rbac.create Whether to create & use RBAC resources or not ## binding Kafka ServiceAccount to a role ## that allows Kafka pods querying the K8s API ## create: false +## @section Metrics parameters + ## Prometheus Exporters / Metrics ## metrics: - ## Prometheus Kafka Exporter: exposes complimentary metrics to JMX Exporter + ## Prometheus Kafka exporter: exposes complimentary metrics to JMX exporter ## kafka: + ## @param metrics.kafka.enabled Whether or not to create a standalone Kafka exporter to expose Kafka metrics + ## enabled: false - ## Bitnami Kafka exporter image ## ref: https://hub.docker.com/r/bitnami/kafka-exporter/tags/ + ## @param metrics.kafka.image.registry Kafka exporter image registry + ## @param metrics.kafka.image.repository Kafka exporter image repository + ## @param metrics.kafka.image.tag Kafka exporter image tag (immutable tags are recommended) + ## @param metrics.kafka.image.digest Kafka exporter image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag + ## @param metrics.kafka.image.pullPolicy Kafka exporter image pull policy + ## @param metrics.kafka.image.pullSecrets Specify docker-registry secret names as an array ## image: registry: docker.io repository: bitnami/kafka-exporter - tag: 1.2.0-debian-10-r220 + tag: 1.6.0-debian-11-r52 + digest: "" ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## ref: https://kubernetes.io/docs/user-guide/images/#pre-pulling-images ## pullPolicy: IfNotPresent ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## Example: + ## e.g: ## pullSecrets: ## - myRegistryKeySecretName ## pullSecrets: [] - ## Extra flags to be passed to Kafka exporter - ## Example: + ## @param metrics.kafka.certificatesSecret Name of the existing secret containing the optional certificate and key files + ## for Kafka exporter client authentication + ## + certificatesSecret: "" + ## @param metrics.kafka.tlsCert The secret key from the certificatesSecret if 'client-cert' key different from the default (cert-file) + ## + tlsCert: cert-file + ## @param metrics.kafka.tlsKey The secret key from the certificatesSecret if 'client-key' key different from the default (key-file) + ## + tlsKey: key-file + ## @param metrics.kafka.tlsCaSecret Name of the existing secret containing the optional ca certificate for Kafka exporter client authentication + ## + tlsCaSecret: "" + ## @param metrics.kafka.tlsCaCert The secret key from the certificatesSecret or tlsCaSecret if 'ca-cert' key different from the default (ca-file) + ## + tlsCaCert: ca-file + ## @param metrics.kafka.extraFlags Extra flags to be passed to Kafka exporter + ## e.g: ## extraFlags: ## tls.insecure-skip-tls-verify: "" ## web.telemetry-path: "/metrics" ## extraFlags: {} - - ## Name of the existing secret containing the optional certificate and key files - ## for Kafka Exporter client authentication + ## @param metrics.kafka.command Override Kafka exporter container command ## - # certificatesSecret: - - ## Prometheus Kafka Exporter' resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + command: [] + ## @param metrics.kafka.args Override Kafka exporter container arguments + ## + args: [] + ## @param metrics.kafka.containerPorts.metrics Kafka exporter metrics container port + ## + containerPorts: + metrics: 9308 + ## Kafka exporter resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param metrics.kafka.resources.limits The resources limits for the container + ## @param metrics.kafka.resources.requests The requested resources for the container ## resources: - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. limits: {} - # cpu: 100m - # memory: 128Mi requests: {} - # cpu: 100m - # memory: 128Mi - - ## Service configuration + ## Kafka exporter pods' Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod + ## @param metrics.kafka.podSecurityContext.enabled Enable security context for the pods + ## @param metrics.kafka.podSecurityContext.fsGroup Set Kafka exporter pod's Security Context fsGroup + ## + podSecurityContext: + enabled: true + fsGroup: 1001 + ## Kafka exporter containers' Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container + ## @param metrics.kafka.containerSecurityContext.enabled Enable Kafka exporter containers' Security Context + ## @param metrics.kafka.containerSecurityContext.runAsUser Set Kafka exporter containers' Security Context runAsUser + ## @param metrics.kafka.containerSecurityContext.runAsNonRoot Set Kafka exporter containers' Security Context runAsNonRoot + ## e.g: + ## containerSecurityContext: + ## enabled: true + ## capabilities: + ## drop: ["NET_RAW"] + ## readOnlyRootFilesystem: true + ## + containerSecurityContext: + enabled: true + runAsUser: 1001 + runAsNonRoot: true + ## @param metrics.kafka.hostAliases Kafka exporter pods host aliases + ## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ + ## + hostAliases: [] + ## @param metrics.kafka.podLabels Extra labels for Kafka exporter pods + ## Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podLabels: {} + ## @param metrics.kafka.podAnnotations Extra annotations for Kafka exporter pods + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + ## + podAnnotations: {} + ## @param metrics.kafka.podAffinityPreset Pod affinity preset. Ignored if `metrics.kafka.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAffinityPreset: "" + ## @param metrics.kafka.podAntiAffinityPreset Pod anti-affinity preset. Ignored if `metrics.kafka.affinity` is set. Allowed values: `soft` or `hard` + ## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAntiAffinityPreset: soft + ## Node metrics.kafka.affinity preset + ## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + ## + nodeAffinityPreset: + ## @param metrics.kafka.nodeAffinityPreset.type Node affinity preset type. Ignored if `metrics.kafka.affinity` is set. Allowed values: `soft` or `hard` + ## + type: "" + ## @param metrics.kafka.nodeAffinityPreset.key Node label key to match Ignored if `metrics.kafka.affinity` is set. + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## @param metrics.kafka.nodeAffinityPreset.values Node label values to match. Ignored if `metrics.kafka.affinity` is set. + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + ## @param metrics.kafka.affinity Affinity for pod assignment + ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## Note: metrics.kafka.podAffinityPreset, metrics.kafka.podAntiAffinityPreset, and metrics.kafka.nodeAffinityPreset will be ignored when it's set + ## + affinity: {} + ## @param metrics.kafka.nodeSelector Node labels for pod assignment + ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param metrics.kafka.tolerations Tolerations for pod assignment + ## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param metrics.kafka.schedulerName Name of the k8s scheduler (other than default) for Kafka exporter + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + schedulerName: "" + ## @param metrics.kafka.priorityClassName Kafka exporter pods' priorityClassName + ## + priorityClassName: "" + ## @param metrics.kafka.topologySpreadConstraints Topology Spread Constraints for pod assignment + ## https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## The value is evaluated as a template + ## + topologySpreadConstraints: [] + ## @param metrics.kafka.extraVolumes Optionally specify extra list of additional volumes for the Kafka exporter pod(s) + ## e.g: + ## extraVolumes: + ## - name: kafka-jaas + ## secret: + ## secretName: kafka-jaas + ## + extraVolumes: [] + ## @param metrics.kafka.extraVolumeMounts Optionally specify extra list of additional volumeMounts for the Kafka exporter container(s) + ## extraVolumeMounts: + ## - name: kafka-jaas + ## mountPath: /bitnami/kafka/config/kafka_jaas.conf + ## subPath: kafka_jaas.conf + ## + extraVolumeMounts: [] + ## @param metrics.kafka.sidecars Add additional sidecar containers to the Kafka exporter pod(s) + ## e.g: + ## sidecars: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + sidecars: [] + ## @param metrics.kafka.initContainers Add init containers to the Kafka exporter pods + ## e.g: + ## initContainers: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + initContainers: [] + ## Kafka exporter service configuration ## service: - ## Kafka Exporter Service type + ## @param metrics.kafka.service.ports.metrics Kafka exporter metrics service port ## - type: ClusterIP - ## Kafka Exporter Prometheus port - ## - port: 9308 - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - nodePort: "" - ## Set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - # loadBalancerIP: - ## Load Balancer sources - ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service - ## Example: - ## loadBalancerSourceRanges: - ## - 10.10.10.0/24 - ## - loadBalancerSourceRanges: [] - ## Set the Cluster IP to use + ports: + metrics: 9308 + ## @param metrics.kafka.service.clusterIP Static clusterIP or None for headless services ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#choosing-your-own-ip-address ## - # clusterIP: None - ## Annotations for the Kafka Exporter Prometheus metrics service + clusterIP: "" + ## @param metrics.kafka.service.sessionAffinity Control where client requests go, to the same pod or round-robin + ## Values: ClientIP or None + ## ref: https://kubernetes.io/docs/user-guide/services/ + ## + sessionAffinity: None + ## @param metrics.kafka.service.annotations [object] Annotations for the Kafka exporter service ## annotations: prometheus.io/scrape: "true" - prometheus.io/port: "{{ .Values.metrics.kafka.service.port }}" + prometheus.io/port: "{{ .Values.metrics.kafka.service.ports.metrics }}" prometheus.io/path: "/metrics" - - ## Prometheus JMX Exporter: exposes the majority of Kafkas metrics + ## Kafka exporter pods ServiceAccount + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ + ## + serviceAccount: + ## @param metrics.kafka.serviceAccount.create Enable creation of ServiceAccount for Kafka exporter pods + ## + create: true + ## @param metrics.kafka.serviceAccount.name The name of the service account to use. If not set and `create` is `true`, a name is generated + ## If not set and create is true, a name is generated using the kafka.metrics.kafka.serviceAccountName template + ## + name: "" + ## @param metrics.kafka.serviceAccount.automountServiceAccountToken Allows auto mount of ServiceAccountToken on the serviceAccount created + ## Can be set to false if pods using this serviceAccount do not need to use K8s API + ## + automountServiceAccountToken: true + ## Prometheus JMX exporter: exposes the majority of Kafkas metrics ## jmx: + ## @param metrics.jmx.enabled Whether or not to expose JMX metrics to Prometheus + ## enabled: false - ## Bitnami JMX exporter image ## ref: https://hub.docker.com/r/bitnami/jmx-exporter/tags/ + ## @param metrics.jmx.image.registry JMX exporter image registry + ## @param metrics.jmx.image.repository JMX exporter image repository + ## @param metrics.jmx.image.tag JMX exporter image tag (immutable tags are recommended) + ## @param metrics.jmx.image.digest JMX exporter image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag + ## @param metrics.jmx.image.pullPolicy JMX exporter image pull policy + ## @param metrics.jmx.image.pullSecrets Specify docker-registry secret names as an array ## image: registry: docker.io repository: bitnami/jmx-exporter - tag: 0.14.0-debian-10-r15 + tag: 0.17.2-debian-11-r41 + digest: "" ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## ref: https://kubernetes.io/docs/user-guide/images/#pre-pulling-images ## pullPolicy: IfNotPresent ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## Example: + ## e.g: ## pullSecrets: ## - myRegistryKeySecretName ## pullSecrets: [] - - ## Prometheus JMX Exporter' resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## Prometheus JMX exporter containers' Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container + ## @param metrics.jmx.containerSecurityContext.enabled Enable Prometheus JMX exporter containers' Security Context + ## @param metrics.jmx.containerSecurityContext.runAsUser Set Prometheus JMX exporter containers' Security Context runAsUser + ## @param metrics.jmx.containerSecurityContext.runAsNonRoot Set Prometheus JMX exporter containers' Security Context runAsNonRoot + ## e.g: + ## containerSecurityContext: + ## enabled: true + ## capabilities: + ## drop: ["NET_RAW"] + ## readOnlyRootFilesystem: true + ## + containerSecurityContext: + enabled: true + runAsUser: 1001 + runAsNonRoot: true + ## @param metrics.jmx.containerPorts.metrics Prometheus JMX exporter metrics container port + ## + containerPorts: + metrics: 5556 + ## Prometheus JMX exporter resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param metrics.jmx.resources.limits The resources limits for the JMX exporter container + ## @param metrics.jmx.resources.requests The requested resources for the JMX exporter container ## resources: - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. limits: {} - # cpu: 100m - # memory: 128Mi requests: {} - # cpu: 100m - # memory: 128Mi - - ## Service configuration + ## Prometheus JMX exporter service configuration ## service: - ## JMX Exporter Service type + ## @param metrics.jmx.service.ports.metrics Prometheus JMX exporter metrics service port ## - type: ClusterIP - ## JMX Exporter Prometheus port - ## - port: 5556 - ## Specify the nodePort value for the LoadBalancer and NodePort service types. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - ## - nodePort: "" - ## Set the LoadBalancer service type to internal only. - ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer - ## - # loadBalancerIP: - ## Load Balancer sources - ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service - ## Example: - ## loadBalancerSourceRanges: - ## - 10.10.10.0/24 - ## - loadBalancerSourceRanges: [] - ## Set the Cluster IP to use + ports: + metrics: 5556 + ## @param metrics.jmx.service.clusterIP Static clusterIP or None for headless services ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#choosing-your-own-ip-address ## - # clusterIP: None - ## Annotations for the JMX Exporter Prometheus metrics service + clusterIP: "" + ## @param metrics.jmx.service.sessionAffinity Control where client requests go, to the same pod or round-robin + ## Values: ClientIP or None + ## ref: https://kubernetes.io/docs/user-guide/services/ + ## + sessionAffinity: None + ## @param metrics.jmx.service.annotations [object] Annotations for the Prometheus JMX exporter service ## annotations: prometheus.io/scrape: "true" - prometheus.io/port: "{{ .Values.metrics.jmx.service.port }}" + prometheus.io/port: "{{ .Values.metrics.jmx.service.ports.metrics }}" prometheus.io/path: "/" - - ## JMX Whitelist Objects, can be set to control which JMX metrics are exposed. Only whitelisted - ## values will be exposed via JMX Exporter. They must also be exposed via Rules. To expose all metrics + ## @param metrics.jmx.whitelistObjectNames Allows setting which JMX objects you want to expose to via JMX stats to JMX exporter + ## Only whitelisted values will be exposed via JMX exporter. They must also be exposed via Rules. To expose all metrics ## (warning its crazy excessive and they aren't formatted in a prometheus style) (1) `whitelistObjectNames: []` ## (2) commented out above `overrideConfig`. ## @@ -857,8 +1392,7 @@ metrics: - java.lang:* - kafka.network:* - kafka.log:* - - ## Prometheus JMX exporter configuration + ## @param metrics.jmx.config [string] Configuration file for JMX exporter ## Specify content for jmx-kafka-prometheus.yml. Evaluated as a template ## ## Credits to the incubator/kafka chart for the JMX configuration. @@ -872,63 +1406,347 @@ metrics: {{- if .Values.metrics.jmx.whitelistObjectNames }} whitelistObjectNames: ["{{ join "\",\"" .Values.metrics.jmx.whitelistObjectNames }}"] {{- end }} - - ## ConfigMap with Prometheus JMX exporter configuration + ## @param metrics.jmx.existingConfigmap Name of existing ConfigMap with JMX exporter configuration ## NOTE: This will override metrics.jmx.config ## - # existingConfigmap: - + existingConfigmap: "" + ## @param metrics.jmx.extraRules Add extra rules to JMX exporter configuration + ## e.g: + ## extraRules: |- + ## - pattern: kafka.server<type=socket-server-metrics, listener=(.+), networkProcessor=(.+)><>(connection-count) + ## name: kafka_server_socket_server_metrics_$3 + ## labels: + ## listener: $1 + extraRules: "" ## Prometheus Operator ServiceMonitor configuration ## serviceMonitor: + ## @param metrics.serviceMonitor.enabled if `true`, creates a Prometheus Operator ServiceMonitor (requires `metrics.kafka.enabled` or `metrics.jmx.enabled` to be `true`) + ## enabled: false - ## Namespace in which Prometheus is running + ## @param metrics.serviceMonitor.namespace Namespace in which Prometheus is running ## - # namespace: monitoring - - ## Interval at which metrics should be scraped. + namespace: "" + ## @param metrics.serviceMonitor.interval Interval at which metrics should be scraped ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#endpoint ## - # interval: 10s - - ## Timeout after which the scrape is ended + interval: "" + ## @param metrics.serviceMonitor.scrapeTimeout Timeout after which the scrape is ended ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#endpoint ## - # scrapeTimeout: 10s - - ## ServiceMonitor selector labels - ## ref: https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#prometheus-configuration + scrapeTimeout: "" + ## @param metrics.serviceMonitor.labels Additional labels that can be used so ServiceMonitor will be discovered by Prometheus ## - # selector: - # prometheus: my-prometheus + labels: {} + ## @param metrics.serviceMonitor.selector Prometheus instance selector labels + ## ref: https://github.com/bitnami/charts/tree/main/bitnami/prometheus-operator#prometheus-configuration + ## + selector: {} + ## @param metrics.serviceMonitor.relabelings RelabelConfigs to apply to samples before scraping + ## + relabelings: [] + ## @param metrics.serviceMonitor.metricRelabelings MetricRelabelConfigs to apply to samples before ingestion + ## + metricRelabelings: [] + ## @param metrics.serviceMonitor.honorLabels Specify honorLabels parameter to add the scrape endpoint + ## + honorLabels: false + ## @param metrics.serviceMonitor.jobLabel The name of the label on the target service to use as the job name in prometheus. + ## + jobLabel: "" + prometheusRule: + ## @param metrics.prometheusRule.enabled if `true`, creates a Prometheus Operator PrometheusRule (requires `metrics.kafka.enabled` or `metrics.jmx.enabled` to be `true`) + ## + enabled: false + ## @param metrics.prometheusRule.namespace Namespace in which Prometheus is running + ## + namespace: "" + ## @param metrics.prometheusRule.labels Additional labels that can be used so PrometheusRule will be discovered by Prometheus + ## + labels: {} + ## @param metrics.prometheusRule.groups Prometheus Rule Groups for Kafka + ## + groups: [] + +## @section Kafka provisioning parameters + +## Kafka provisioning ## -## Zookeeper chart configuration -## -## https://github.com/bitnami/charts/blob/master/bitnami/zookeeper/values.yaml +provisioning: + ## @param provisioning.enabled Enable kafka provisioning Job + ## + enabled: false + ## @param provisioning.numPartitions Default number of partitions for topics when unspecified + ## + numPartitions: 1 + ## @param provisioning.replicationFactor Default replication factor for topics when unspecified + ## + replicationFactor: 1 + ## @param provisioning.topics Kafka topics to provision + ## - name: topic-name + ## partitions: 1 + ## replicationFactor: 1 + ## ## https://kafka.apache.org/documentation/#topicconfigs + ## config: + ## max.message.bytes: 64000 + ## flush.messages: 1 + ## + topics: [] + ## @param provisioning.nodeSelector Node labels for pod assignment + ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param provisioning.tolerations Tolerations for pod assignment + ## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param provisioning.extraProvisioningCommands Extra commands to run to provision cluster resources + ## - echo "Allow user to consume from any topic" + ## - >- + ## /opt/bitnami/kafka/bin/kafka-acls.sh + ## --bootstrap-server $KAFKA_SERVICE + ## --command-config $CLIENT_CONF + ## --add + ## --allow-principal User:user + ## --consumer --topic '*' + ## - "/opt/bitnami/kafka/bin/kafka-acls.sh + ## --bootstrap-server $KAFKA_SERVICE + ## --command-config $CLIENT_CONF + ## --list" + ## + extraProvisioningCommands: [] + ## @param provisioning.parallel Number of provisioning commands to run at the same time + ## + parallel: 1 + ## @param provisioning.preScript Extra bash script to run before topic provisioning. $CLIENT_CONF is path to properties file with most needed configurations + ## + preScript: "" + ## @param provisioning.postScript Extra bash script to run after topic provisioning. $CLIENT_CONF is path to properties file with most needed configurations + ## + postScript: "" + ## Auth Configuration for kafka provisioning Job + ## + auth: + ## TLS configuration for kafka provisioning Job + ## + tls: + ## @param provisioning.auth.tls.type Format to use for TLS certificates. Allowed types: `jks` and `pem`. + ## Note: ignored if auth.tls.clientProtocol different from one of these values: "tls" "mtls" "sasl_tls". + ## + type: jks + ## @param provisioning.auth.tls.certificatesSecret Existing secret containing the TLS certificates for the Kafka provisioning Job. + ## When using 'jks' format for certificates, the secret should contain a truststore and a keystore. + ## When using 'pem' format for certificates, the secret should contain a public CA certificate, a public certificate and one private key. + ## + certificatesSecret: "" + ## @param provisioning.auth.tls.cert The secret key from the certificatesSecret if 'cert' key different from the default (tls.crt) + ## + cert: tls.crt + ## @param provisioning.auth.tls.key The secret key from the certificatesSecret if 'key' key different from the default (tls.key) + ## + key: tls.key + ## @param provisioning.auth.tls.caCert The secret key from the certificatesSecret if 'caCert' key different from the default (ca.crt) + ## + caCert: ca.crt + ## @param provisioning.auth.tls.keystore The secret key from the certificatesSecret if 'keystore' key different from the default (keystore.jks) + ## + keystore: keystore.jks + ## @param provisioning.auth.tls.truststore The secret key from the certificatesSecret if 'truststore' key different from the default (truststore.jks) + ## + truststore: truststore.jks + ## @param provisioning.auth.tls.passwordsSecret Name of the secret containing passwords to access the JKS files or PEM key when they are password-protected. + ## It should contain two keys called "keystore-password" and "truststore-password", or "key-password" if using a password-protected PEM key. + ## + passwordsSecret: "" + ## @param provisioning.auth.tls.keyPasswordSecretKey The secret key from the passwordsSecret if 'keyPasswordSecretKey' key different from the default (key-password) + ## Note: must not be used if `passwordsSecret` is not defined. + ## + keyPasswordSecretKey: key-password + ## @param provisioning.auth.tls.keystorePasswordSecretKey The secret key from the passwordsSecret if 'keystorePasswordSecretKey' key different from the default (keystore-password) + ## Note: must not be used if `passwordsSecret` is not defined. + ## + keystorePasswordSecretKey: keystore-password + ## @param provisioning.auth.tls.truststorePasswordSecretKey The secret key from the passwordsSecret if 'truststorePasswordSecretKey' key different from the default (truststore-password) + ## Note: must not be used if `passwordsSecret` is not defined. + ## + truststorePasswordSecretKey: truststore-password + ## @param provisioning.auth.tls.keyPassword Password to access the password-protected PEM key if necessary. Ignored if 'passwordsSecret' is provided. + ## + keyPassword: "" + ## @param provisioning.auth.tls.keystorePassword Password to access the JKS keystore. Ignored if 'passwordsSecret' is provided. + ## + keystorePassword: "" + ## @param provisioning.auth.tls.truststorePassword Password to access the JKS truststore. Ignored if 'passwordsSecret' is provided. + ## + truststorePassword: "" + ## @param provisioning.command Override provisioning container command + ## + command: [] + ## @param provisioning.args Override provisioning container arguments + ## + args: [] + ## @param provisioning.extraEnvVars Extra environment variables to add to the provisioning pod + ## e.g: + ## extraEnvVars: + ## - name: KAFKA_CFG_BACKGROUND_THREADS + ## value: "10" + ## + extraEnvVars: [] + ## @param provisioning.extraEnvVarsCM ConfigMap with extra environment variables + ## + extraEnvVarsCM: "" + ## @param provisioning.extraEnvVarsSecret Secret with extra environment variables + ## + extraEnvVarsSecret: "" + ## @param provisioning.podAnnotations Extra annotations for Kafka provisioning pods + ## + podAnnotations: {} + ## @param provisioning.podLabels Extra labels for Kafka provisioning pods + ## Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podLabels: {} + ## Kafka provisioning pods ServiceAccount + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ + ## + serviceAccount: + ## @param provisioning.serviceAccount.create Enable creation of ServiceAccount for Kafka provisioning pods + ## + create: false + ## @param provisioning.serviceAccount.name The name of the service account to use. If not set and `create` is `true`, a name is generated + ## If not set and create is true, a name is generated using the provisioning.serviceAccount.name template + ## + name: "" + ## @param provisioning.serviceAccount.automountServiceAccountToken Allows auto mount of ServiceAccountToken on the serviceAccount created + ## Can be set to false if pods using this serviceAccount do not need to use K8s API + ## + automountServiceAccountToken: true + ## Kafka provisioning resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param provisioning.resources.limits The resources limits for the Kafka provisioning container + ## @param provisioning.resources.requests The requested resources for the Kafka provisioning container + ## + resources: + limits: {} + requests: {} + ## Kafka provisioning pods' Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod + ## @param provisioning.podSecurityContext.enabled Enable security context for the pods + ## @param provisioning.podSecurityContext.fsGroup Set Kafka provisioning pod's Security Context fsGroup + ## + podSecurityContext: + enabled: true + fsGroup: 1001 + ## Kafka provisioning containers' Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container + ## @param provisioning.containerSecurityContext.enabled Enable Kafka provisioning containers' Security Context + ## @param provisioning.containerSecurityContext.runAsUser Set Kafka provisioning containers' Security Context runAsUser + ## @param provisioning.containerSecurityContext.runAsNonRoot Set Kafka provisioning containers' Security Context runAsNonRoot + ## e.g: + ## containerSecurityContext: + ## enabled: true + ## capabilities: + ## drop: ["NET_RAW"] + ## readOnlyRootFilesystem: true + ## + containerSecurityContext: + enabled: true + runAsUser: 1001 + runAsNonRoot: true + ## @param provisioning.schedulerName Name of the k8s scheduler (other than default) for kafka provisioning + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + schedulerName: "" + ## @param provisioning.extraVolumes Optionally specify extra list of additional volumes for the Kafka provisioning pod(s) + ## e.g: + ## extraVolumes: + ## - name: kafka-jaas + ## secret: + ## secretName: kafka-jaas + ## + extraVolumes: [] + ## @param provisioning.extraVolumeMounts Optionally specify extra list of additional volumeMounts for the Kafka provisioning container(s) + ## extraVolumeMounts: + ## - name: kafka-jaas + ## mountPath: /bitnami/kafka/config/kafka_jaas.conf + ## subPath: kafka_jaas.conf + ## + extraVolumeMounts: [] + ## @param provisioning.sidecars Add additional sidecar containers to the Kafka provisioning pod(s) + ## e.g: + ## sidecars: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + sidecars: [] + ## @param provisioning.initContainers Add additional Add init containers to the Kafka provisioning pod(s) + ## e.g: + ## initContainers: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + initContainers: [] + ## @param provisioning.waitForKafka If true use an init container to wait until kafka is ready before starting provisioning + ## + waitForKafka: true + +## @section ZooKeeper chart parameters + +## ZooKeeper chart configuration +## https://github.com/bitnami/charts/blob/main/bitnami/zookeeper/values.yaml ## zookeeper: + ## @param zookeeper.enabled Switch to enable or disable the ZooKeeper helm chart + ## enabled: true + ## @param zookeeper.replicaCount Number of ZooKeeper nodes + ## + replicaCount: 1 + ## ZooKeeper authenticaiton + ## auth: - ## Enable Zookeeper auth - ## - enabled: false - ## User that will use Zookeeper clients to auth - ## - # clientUser: - ## Password that will use Zookeeper clients to auth - ## - # clientPassword: - ## Comma, semicolon or whitespace separated list of user to be created. Specify them as a string, for example: "user1,user2,admin" - ## - # serverUsers: - ## Comma, semicolon or whitespace separated list of passwords to assign to users when created. Specify them as a string, for example: "pass4user1, pass4user2, pass4admin" - ## - # serverPasswords: + client: + ## @param zookeeper.auth.client.enabled Enable ZooKeeper auth + ## + enabled: false + ## @param zookeeper.auth.client.clientUser User that will use ZooKeeper clients to auth + ## + clientUser: "" + ## @param zookeeper.auth.client.clientPassword Password that will use ZooKeeper clients to auth + ## + clientPassword: "" + ## @param zookeeper.auth.client.serverUsers Comma, semicolon or whitespace separated list of user to be created. Specify them as a string, for example: "user1,user2,admin" + ## + serverUsers: "" + ## @param zookeeper.auth.client.serverPasswords Comma, semicolon or whitespace separated list of passwords to assign to users when created. Specify them as a string, for example: "pass4user1, pass4user2, pass4admin" + ## + serverPasswords: "" + ## ZooKeeper Persistence parameters + ## ref: https://kubernetes.io/docs/user-guide/persistent-volumes/ + ## @param zookeeper.persistence.enabled Enable persistence on ZooKeeper using PVC(s) + ## @param zookeeper.persistence.storageClass Persistent Volume storage class + ## @param zookeeper.persistence.accessModes Persistent Volume access modes + ## @param zookeeper.persistence.size Persistent Volume size + ## + persistence: + enabled: true + storageClass: "" + accessModes: + - ReadWriteOnce + size: 8Gi -## This value is only used when zookeeper.enabled is set to false +## External Zookeeper Configuration +## All of these values are only used if `zookeeper.enabled=false` ## externalZookeeper: - ## Server or list of external zookeeper servers to use. + ## @param externalZookeeper.servers List of external zookeeper servers to use. Typically used in combination with 'zookeeperChrootPath'. ## servers: [] diff --git a/scripts/helmcharts/databases/charts/minio/templates/deployment-standalone.yaml b/scripts/helmcharts/databases/charts/minio/templates/deployment-standalone.yaml index 23a7232a8..de4af3d90 100755 --- a/scripts/helmcharts/databases/charts/minio/templates/deployment-standalone.yaml +++ b/scripts/helmcharts/databases/charts/minio/templates/deployment-standalone.yaml @@ -75,20 +75,20 @@ spec: - name: MINIO_FORCE_NEW_KEYS value: {{ ternary "yes" "no" .Values.forceNewKeys | quote }} {{- if .Values.useCredentialsFile }} - - name: MINIO_ACCESS_KEY_FILE + - name: MINIO_ROOT_USER_FILE value: "/opt/bitnami/minio/secrets/access-key" {{- else }} - - name: MINIO_ACCESS_KEY + - name: MINIO_ROOT_USER valueFrom: secretKeyRef: name: {{ include "minio.secretName" . }} key: access-key {{- end }} {{- if .Values.useCredentialsFile }} - - name: MINIO_SECRET_KEY_FILE + - name: MINIO_ROOT_PASSWORD_FILE value: "/opt/bitnami/minio/secrets/secret-key" {{- else }} - - name: MINIO_SECRET_KEY + - name: MINIO_ROOT_PASSWORD valueFrom: secretKeyRef: name: {{ include "minio.secretName" . }} diff --git a/scripts/helmcharts/databases/charts/minio/values.yaml b/scripts/helmcharts/databases/charts/minio/values.yaml index 8aee06beb..7062254a7 100755 --- a/scripts/helmcharts/databases/charts/minio/values.yaml +++ b/scripts/helmcharts/databases/charts/minio/values.yaml @@ -21,7 +21,7 @@ global: image: registry: docker.io repository: bitnami/minio - tag: 2020.10.9-debian-10-r6 + tag: 2023.2.10-debian-11-r1 ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images diff --git a/scripts/helmcharts/databases/values.yaml b/scripts/helmcharts/databases/values.yaml index bd2731942..1ed77adde 100644 --- a/scripts/helmcharts/databases/values.yaml +++ b/scripts/helmcharts/databases/values.yaml @@ -132,12 +132,13 @@ kafka: tag: 2.8.1 fullnameOverride: kafka enabled: false + replicaCount: 2 # Enterprise dbs clickhouse: image: - tag: "22.2.2.1" + tag: "22.12-alpine" enabled: false postgreql: diff --git a/scripts/helmcharts/init.sh b/scripts/helmcharts/init.sh index 90afd8918..69a6944b2 100644 --- a/scripts/helmcharts/init.sh +++ b/scripts/helmcharts/init.sh @@ -1,27 +1,32 @@ -#/bin/bash +#!/bin/bash set -e +# Ref: https://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BWHITE='\033[1;37m' +NC='\033[0m' # No Color # --- helper functions for logs --- info() { - echo '[INFO] ' "$@" + echo -e "${GREEN}[INFO] " "$@" "$NC" } warn() { - echo '[WARN] ' "$@" >&2 + echo -e "${YELLOW}[INFO] " "$@" "$NC" } fatal() { - echo '[ERROR] ' "$@" >&2 + echo -e "${RED}[INFO] " "$@" "$NC" exit 1 } -version="v1.9.0" -usr=`whoami` +usr=$(whoami) # Installing k3s function install_k8s() { - curl -sL https://get.k3s.io | sudo K3S_KUBECONFIG_MODE="644" INSTALL_K3S_VERSION='v1.22.8+k3s1' INSTALL_K3S_EXEC="--no-deploy=traefik" sh - + curl -sL https://get.k3s.io | sudo K3S_KUBECONFIG_MODE="644" INSTALL_K3S_VERSION='v1.25.6+k3s1' INSTALL_K3S_EXEC="--disable=traefik" sh - [[ -d ~/.kube ]] || mkdir ~/.kube sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config sudo chmod 0644 ~/.kube/config @@ -51,7 +56,7 @@ function install_tools() { ## $install_status GH package manager exists eget || { info "$install_status eget" - download_url=`curl https://api.github.com/repos/zyedidia/eget/releases/latest -s | grep linux_amd64 | grep browser_download_url | cut -d '"' -f4` + download_url=$(curl https://api.github.com/repos/zyedidia/eget/releases/latest -s | grep linux_amd64 | grep browser_download_url | cut -d '"' -f4) curl -SsL ${download_url} -o /tmp/eget.tar.gz tar -xf /tmp/eget.tar.gz --strip-components=1 -C /tmp/ sudo mv /tmp/eget /usr/local/bin/eget @@ -93,8 +98,8 @@ randomPass() { ## Prepping the infra # Mac os doesn't have gnu sed, which will cause compatibility issues. -# This wrapper will help to check the sed, and use the correct version="v1.9.0" -# Ref: https://stackoverflow.com/questions/37639496/how-can-i-check-the-version="v1.9.0" +# This wrapper will help to check the sed, and use the correct version="v1.10.0" +# Ref: https://stackoverflow.com/questions/37639496/how-can-i-check-the-version="v1.10.0" function is_gnu_sed(){ sed --version >/dev/null 2>&1 } @@ -113,7 +118,7 @@ function sed_i_wrapper(){ function create_passwords() { # Error out only if the domain name is empty in vars.yaml - existing_domain_name=`grep domainName vars.yaml | awk '{print $2}' | xargs` + existing_domain_name=$(awk '/domainName/ {print $2}' vars.yaml | xargs) [[ -z $existing_domain_name ]] && { [[ -z $DOMAIN_NAME ]] && { fatal 'DOMAIN_NAME variable is empty. Rerun the script `DOMAIN_NAME=openreplay.mycomp.org bash init.sh `' @@ -139,6 +144,8 @@ function set_permissions() { ## Installing OpenReplay function install_openreplay() { + info "installing toolings" + helm upgrade --install toolings ./toolings -n app --create-namespace --wait -f ./vars.yaml --atomic info "installing databases" helm upgrade --install databases ./databases -n db --create-namespace --wait -f ./vars.yaml --atomic info "installing application" @@ -166,7 +173,15 @@ function main() { } || { set_permissions install_openreplay + sudo mkdir -p /var/lib/openreplay + sudo cp -f openreplay-cli /bin/openreplay + sudo cp -rf ../../../openreplay /var/lib/openreplay + sudo cp -f vars.yaml /var/lib/openreplay } } main + +info "Configuration file is saved in /var/lib/openreplay/vars.yaml" +info "You can delete the directory $(echo $(cd ../.. && pwd)). Backup stored in /var/lib/openreplay" +info "Run ${BWHITE}openreplay -h${GREEN} to see the cli information to manage OpenReplay." diff --git a/scripts/helmcharts/openreplay-cli b/scripts/helmcharts/openreplay-cli index d4b36a663..a470051b8 100755 --- a/scripts/helmcharts/openreplay-cli +++ b/scripts/helmcharts/openreplay-cli @@ -1,42 +1,106 @@ #!/bin/bash +# vim: set ft=sh: -## This script is a helper for managing your OpenReplay instance +OR_DIR="/var/lib/openreplay" +APP_NS="${APP_NS:-app}" +DB_NS="${DB_NS:-db}" +OR_REPO="https://github.com/openreplay/openreplay" +tmp_dir=$(mktemp -d) +# For example HELM_OPTIONS="--set dbMigrationUpstreamBranch=dev" +#HELM_OPTIONS="" +# If you want to install the dev version. It can be any branch or tag. +#OR_VERSION="dev" -set -eE -o pipefail # same as: `set -o errexit -o errtrace` -# Trapping the error -trap err EXIT +[[ -d $OR_DIR ]] || { + sudo mkdir $OR_DIR +} +export PATH=$PATH:/var/lib/openreplay -err() { - case "$?" in - 0) - ;; - *) - ;; - esac +tools=( + zyedidia/eget + stern/stern + derailed/k9s + hidetatz/kubecolor + ) + +# Ref: https://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BWHITE='\033[1;37m' +NC='\033[0m' # No Color + +# Checking whether the app exists or we do have to upgade. +function exists() { + which "${1}" &> /dev/null + return $? } -# make all stderr red -color()(set -o pipefail;"$@" 2>&1>&3|sed $'s,.*,\e[31m&\e[m,'>&2)3>&1 +function err_cd() { + if ! cd "$1" &> /dev/null ; then + log err not able to cd to "$1" + exit 100 + fi +} -# color schemes -# Ansi color code variables -red="\e[0;91m" -blue="\e[0;94m" -expand_bg="\e[K" -blue_bg="\e[0;104m${expand_bg}" -red_bg="\e[0;101m${expand_bg}" -green_bg="\e[0;102m${expand_bg}" -green="\e[0;92m" -white="\e[0;97m" -bold="\e[1m" -uline="\e[4m" -reset="\e[0m" +function log () { + case "$1" in + info) + shift + echo -e "${GREEN}[INFO]" "$@" "${NC}" + return + ;; + debug) + shift + echo -e "${YELLOW}[DEBUG]" "$@" "${NC}" + return + ;; + title) + shift + echo -e "\n${BWHITE}-" "$@" "${NC}" + return + ;; + err) + shift + echo -e "${RED}[ERROR]" "$@" "${NC}" + exit 100 + ;; + *) + echo "Not supported log format" + ;; + esac + echo "[Error]" "$@" + exit 100 +} -CWD=$pwd +function install_packages() { -usage() -{ -clear + [[ -e "$OR_DIR/eget" ]] || { + cd "$tmp_dir" || log err "Not able to cd to tmp dir $tmp_dir" + curl --version &> /dev/null || log err "curl not found. Please install" + curl https://zyedidia.github.io/eget.sh | sh + sudo mv eget $OR_DIR + err_cd - + } + + for package in "${tools[@]}"; do + log info Installing "$(awk -F/ '{print $2}' <<< $package)" + sudo /var/lib/openreplay/eget -q --upgrade-only --to "${OR_DIR}" "$package" + done + log info Installing yq + sudo /var/lib/openreplay/eget -q --upgrade-only --to "$OR_DIR" mikefarah/yq --asset=^tar.gz + log info Installing helm + sudo /var/lib/openreplay/eget -q --upgrade-only --to "$OR_DIR" https://get.helm.sh/helm-v3.10.2-linux-amd64.tar.gz -f helm + log info Installing kubectl + sudo /var/lib/openreplay/eget -q --upgrade-only --to "$OR_DIR" https://dl.k8s.io/release/v1.20.0/bin/linux/amd64/kubectl + log info Installing Busybox + sudo /var/lib/openreplay/eget -q --upgrade-only --to "$OR_DIR" https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox + date | sudo tee $OR_DIR/packages.lock &> /dev/null +} + +function help() { + +echo -e ${BWHITE} cat <<"EOF" ___ ____ _ / _ \ _ __ ___ _ __ | _ \ ___ _ __ | | __ _ _ _ @@ -44,105 +108,200 @@ cat <<"EOF" | |_| | |_) | __/ | | | _ < __/ |_) | | (_| | |_| | \___/| .__/ \___|_| |_|_| \_\___| .__/|_|\__,_|\__, | |_| |_| |___/ - EOF +echo -e ${NC} - echo -e "${green}Usage: openreplay-cli [ -h | --help ] - [ -d | --status ] - [ -v | --verbose ] - [ -l | --logs SERVICE ] - [ -I | --helm-install SERVICE ] - [ -s | --stop SERVICE|all ] - [ -S | --start SERVICE|all ] - [ -r | --restart SERVICE|all ]" - echo -e "${reset}${blue}services: ${services[*]}${reset}" - exit 0 +log info ' + Usage: openreplay [ -h | --help ] + [ -s | --status ] + [ -u | --upgrade ] + [ -U | --deprecated-upgrade /path/to/old_vars.yaml] + [ -r | --restart ] + [ -R | --Reload ] + [ -p | --install-packages ] + [ -l | --logs SERVICE ] + Services: alerts assets assist chalice + db ender frontend heuristics + http integrations nginx-controller + peers sink sourcemapreader storage + ' + return } -services=( alerts assets chalice clickhouse ender sink storage http integrations ios-proxy db pg redis postgresql ) -check() { - if ! command -v kubectl &> /dev/null - then - >&2 echo "Kubectl not found. Please refer https://kubernetes.io/docs/tasks/tools/install-kubectl/ " - exit 2 - fi - kubectl cluster-info &> /dev/null - if [[ $? -ne 0 ]]; then - echo -e "${red}Kubernetes cluster is not accessible.\nPlease check ${bold}KUBECONFIG${reset}${red} env variable is set or ${bold}~/.kube/config exists.${reset}" - exit 1 +function status() { + log info OpenReplay Version + # awk '(NR<2)' < "$OR_DIR/vars.yaml" + awk '/fromVersion/{print $2}' < "${OR_DIR}/vars.yaml" + log info Disk + df -h /var + log info Memory + free -mh + log info CPU + uname -a + # Print only the fist line. + awk '(NR<2)' < /etc/os-release + echo "CPU Count: $(nproc)" + log info Kubernetes + kubecolor version --short + log info Openreplay Component + kubecolor get po -n "${APP_NS}" + kubecolor get po -n "${DB_NS}" + return +} + +# Function to upgrade helm openreplay app. +function or_helm_upgrade() { + set -o pipefail + log_file="${tmp_dir}/helm.log" + chart_names=( + toolings + openreplay + ) + for chart in "${chart_names[@]}"; do + [[ -z $OR_VERSION ]] || HELM_OPTIONS="--set dbMigrationUpstreamBranch=${OR_VERSION}" + if ! helm upgrade --install "$chart" ./"$chart" -n "$APP_NS" --wait -f ./vars.yaml --atomic --debug $HELM_OPTIONS 2>&1 | tee -a "${log_file}"; then + log err " + Installation failed, run ${BWHITE}cat ${log_file}${RED} for more info + + If logs aren't verbose, run ${BWHITE}openreplay --status${RED} + + If pods are in failed state, run ${BWHITE}openreplay --logs <pod-name>${RED} + " fi + done + set +o pipefail + return } -stop() { - if [[ $1 == "all" ]]; then - kubectl scale deployment -n app --replicas=0 --all - return - fi - kubectl scale -n app deployment --replicas=0 $1-openreplay +function upgrade_old() { + old_vars_path="$1" + or_version=$(busybox awk '/fromVersion/{print $2}' < "${old_vars_path}") + sudo cp "${old_vars_path}" ${OR_DIR}/vars.yaml.backup."${or_version//\"}"_"$(date +%Y%m%d-%H%M%S)" || log err "Not able to copy old vars.yaml" + sudo cp "${old_vars_path}" ${OR_DIR}/vars.yaml || log err "Not able to copy old vars.yaml" + upgrade } -start() { - helm upgrade --install openreplay -n app openreplay -f vars.yaml +function upgrade() { + # TODO: + # 1. store vars.yaml in central place. + # 3. In upgrade you'll have to clone the repo + # 3. How to update package. Because openreplay -u will be done from old update script + # 4. Update from Version + exists git || log err "Git not found. Please install" + log info "Working directory $tmp_dir" + err_cd "$tmp_dir" + or_version=$(busybox awk '/fromVersion/{print $2}' < "${OR_DIR}/vars.yaml") + + # Creating backup dir of current installation + [[ -d "$OR_DIR/openreplay" ]] && sudo cp -rfb "$OR_DIR/openreplay" "$OR_DIR/openreplay_${or_version//\"}" && sudo rm -rf ${OR_DIR}/openreplay + + git_options="-b ${OR_VERSION:-main}" + eval git clone "${OR_REPO}" --depth 1 $git_options + err_cd openreplay/scripts/helmcharts + install_packages + [[ -d /openreplay ]] && sudo chown -R 1001:1001 /openreplay + + # Merge prefrerences + cp $OR_DIR/vars.yaml old_vars.yaml + or_new_version=$(awk '/fromVersion/{print $2}' < "vars.yaml") + yq '(load("old_vars.yaml") | .. | select(tag != "!!map" and tag != "!!seq")) as $i ireduce(.; setpath($i | path; $i))' vars.yaml > new_vars.yaml + mv new_vars.yaml vars.yaml + or_helm_upgrade + + # Update the version + busybox sed -i "s/fromVersion.*/fromVersion: ${or_new_version}/" vars.yaml + sudo mv ./openreplay-cli /bin/ + sudo mv ./vars.yaml "$OR_DIR" + sudo cp -rf ../../../openreplay $OR_DIR/ + log info "Configuration file is saved in /var/lib/openreplay/vars.yaml" + log info "Run ${BWHITE}openreplay -h${GREEN} to see the cli information to manage OpenReplay." + + err_cd - + return } - -restart() { - if [[ $1 == "all" ]]; then - kubectl rollout restart deployment -n app - return - fi - kubectl rollout restart -n app deployment $1-openreplay +function reload() { + err_cd $OR_DIR/openreplay/scripts/helmcharts + sudo cp -f $OR_DIR/vars.yaml . + or_helm_upgrade + return } - -helmInstall() { - [[ FORCE_UPGRADE_FRONTENT -eq 1 ]] && { - helm upgrade --install openreplay -n app openreplay -f vars.yaml --set forceUpgradeFrontend=true - } || { - helm upgrade --install openreplay -n app openreplay -f vars.yaml - } +function clean_tmp_dir() { + [[ -z $SKIP_DELETE_TMP_DIR ]] && rm -rf "${tmp_dir}" } -upgrade() { - sed -i "s/tag:.*/ tag: 'latest'/g" ./app/$1.yaml +[[ -f $OR_DIR/packages.lock ]] || { + log title Installing packages "${NC}" + install_packages } -logs() { - check - kubectl logs --timestamps -n app -l app.kubernetes.io/name=$1 -f -} - -status() { - kubectl get deployment.apps -n app -} - -[[ $# -eq 0 ]] && usage && exit 1 - -PARSED_ARGUMENTS=$(color getopt -a -n openreplay-cli -o vhds:S:l:r:I --long verbose,help,status,start:,stop:,logs:,restart:,helm-install -- "$@") +PARSED_ARGUMENTS=$(busybox getopt -a -n openreplay -o Rrvpiuhsl:U: --long reload,restart,verbose,install-packages,install,upgrade,help,status,logs,deprecated-upgrade: -- "$@") VALID_ARGUMENTS=$? if [[ "$VALID_ARGUMENTS" != "0" ]]; then - usage + help + exit 100 fi eval set -- "$PARSED_ARGUMENTS" while : do case "$1" in - -v | --verbose) VERBOSE=1 ; shift ;; - -h | --help) usage ; shift ;; - -d | --status) status ; shift ;; - -I | --helm-install) helmInstall; shift ;; - -s | --stop) stop $2 ; shift 2 ;; - -S | --start) start $2 ; shift 2 ;; - -l | --logs) logs "$2" ; shift 2 ;; - -r | --restart) restart "$2" ; shift 2 ;; + -v | --verbose) VERBOSE=1; echo $VERBOSE; clean_tmp_dir ; shift ;; + -h | --help) + help + clean_tmp_dir + exit 0 + ;; + -u | --upgrade) + log title "Upgrading OpenReplay" + upgrade + clean_tmp_dir + exit 0 + ;; + -U | --deprecated-upgrade) + log title "[Deprected] Upgrading OpenReplay" + upgrade_old "$2" + clean_tmp_dir + exit 0 + ;; + -r | --restart) + log title "Restarting OpenReplay Components" + kubectl rollout restart deployment -n "${APP_NS}" + kubectl rollout status deployment -n "${APP_NS}" + clean_tmp_dir + exit 0 + ;; + -R | --reload) + log title "Reloading OpenReplay Components" + reload + clean_tmp_dir + exit 0 + ;; + -s | --status) + log title "Checking OpenReplay Components Status" + status + clean_tmp_dir + exit 0 + ;; + -l | --logs) + # Skipping double quotes because we want globbing. For example + # ./openreplay -l "chalice --tail 10" + stern -A --container-state=running,terminated $2 + clean_tmp_dir + exit 0 + ;; # -- means the end of the arguments; drop this, and break out of the while loop --) shift; break ;; # If invalid options were passed, then getopt should have reported an error, # which we checked as VALID_ARGUMENTS when getopt was called... - *) echo "Unexpected option: $1 - this should not happen." - usage ;; + *) + echo "Unexpected option: $1 - this should not happen." + help + clean_tmp_dir + ;; esac done -[[ $VERBOSE -eq 1 ]] && set -x +[ $# -eq 0 ] && help +clean_tmp_dir diff --git a/scripts/helmcharts/openreplay-container-sign.pub b/scripts/helmcharts/openreplay-container-sign.pub new file mode 100644 index 000000000..520d21063 --- /dev/null +++ b/scripts/helmcharts/openreplay-container-sign.pub @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoLidzRiNIO3l/sWCYw2f +Ct71YSj7UVerhbR81TNEKYtW0fUqg4GagS+esprcXteHPoBI+ZcfL2xJIs0ZNHZs +A+2VXYrsFRgREtABFCwJ2G51ybusoS3jpBsAmSNjG0uzseDxQMTh0arNOlNbhbmI +Tj1ty2JfyLejDKlxavXheKmJGb+7IdDCMmP3f5mXSsJpsOM8SJo49BkvKhTwzjc0 +01dsSLo5mk9jeG2C6UvPCQeMIUKaf5GlYWyFx7vLZ+z5be9TPuWDH4GO0RtxJVXt +tqmk32aKe+0KDLH0ak9WRVz3ugYEjs+tqdO3y3ALLoGAAI+yGxGSfWFDnDj5AXpA +2/XYSJAWRzPu35/H3laSrxaApYWN5an69jI30JY7SoEy/k+10oIGe2FGIihXTdq+ +As3IKPEtvuN9s3RTm2ujV/7rEnVVKWiHvQCwH8rxhsbDTeJCoNs8hSBUq1Muttct +EWML8s/TCIK01PyvH6VNQSnc+lRKAJOd5NpZ/SVMXBbrykCQSZPE8RcaQum3nMxE +Tri24VcWfRHj1WwUYzxpmoVE5F1lw0lqQIXlwz+AFhCLGsePSkjFShFtNFQuX22r +Q73JTt3FX4JEzaaKC5BZwXmkEs3MVpQj43HuEqDyejlsPWwRBYwZIzXpoBhOCFHD +t4PI8n+1dSE+uavu/ijgXl8CAwEAAQ== +-----END PUBLIC KEY----- diff --git a/scripts/helmcharts/openreplay/Chart.yaml b/scripts/helmcharts/openreplay/Chart.yaml index c409972d7..5153f5f1e 100644 --- a/scripts/helmcharts/openreplay/Chart.yaml +++ b/scripts/helmcharts/openreplay/Chart.yaml @@ -22,7 +22,7 @@ version: 0.1.0 # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. # Ref: https://github.com/helm/helm/issues/7858#issuecomment-608114589 -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" dependencies: - name: ingress-nginx diff --git a/scripts/helmcharts/openreplay/charts/alerts/Chart.yaml b/scripts/helmcharts/openreplay/charts/alerts/Chart.yaml index b0e118052..632c71cd2 100644 --- a/scripts/helmcharts/openreplay/charts/alerts/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/alerts/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" diff --git a/scripts/helmcharts/openreplay/charts/alerts/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/alerts/templates/deployment.yaml index ac01dbdac..d4c1d6e49 100644 --- a/scripts/helmcharts/openreplay/charts/alerts/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/alerts/templates/deployment.yaml @@ -55,6 +55,10 @@ spec: value: "{{ .Values.global.clickhouse.chHost }}" - name: ch_port value: "{{ .Values.global.clickhouse.service.webPort }}" + - name: ch_username + value: '{{ .Values.global.clickhouse.username }}' + - name: ch_password + value: '{{ .Values.global.clickhouse.passwordk }}' - name: pg_user value: '{{ .Values.global.postgresql.postgresqlUser }}' - name: pg_password @@ -112,6 +116,10 @@ spec: value: '{{ .Values.global.email.emailSslCert }}' - name: EMAIL_FROM value: '{{ .Values.global.email.emailFrom }}' + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} value: '{{ $val }}' diff --git a/scripts/helmcharts/openreplay/charts/assets/Chart.yaml b/scripts/helmcharts/openreplay/charts/assets/Chart.yaml index c12d6b251..75fa974cb 100644 --- a/scripts/helmcharts/openreplay/charts/assets/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/assets/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" diff --git a/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml index f66479475..f959adc13 100644 --- a/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml @@ -94,6 +94,10 @@ spec: value: '{{ .Values.global.s3.endpoint }}/{{.Values.global.s3.assetsBucket}}' {{- end }} {{- include "openreplay.env.redis_string" .Values.global.redis | nindent 12 }} + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} value: '{{ $val }}' diff --git a/scripts/helmcharts/openreplay/charts/assist/Chart.yaml b/scripts/helmcharts/openreplay/charts/assist/Chart.yaml index 022faeebf..edd315810 100644 --- a/scripts/helmcharts/openreplay/charts/assist/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/assist/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" diff --git a/scripts/helmcharts/openreplay/charts/assist/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/assist/templates/deployment.yaml index e153e50c3..92ae9a93c 100644 --- a/scripts/helmcharts/openreplay/charts/assist/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/assist/templates/deployment.yaml @@ -75,6 +75,10 @@ spec: {{- end }} - name: REDIS_URL value: {{ .Values.global.redis.redisHost }} + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} value: '{{ $val }}' diff --git a/scripts/helmcharts/openreplay/charts/chalice/Chart.yaml b/scripts/helmcharts/openreplay/charts/chalice/Chart.yaml index 01d5d21e1..b64b3b243 100644 --- a/scripts/helmcharts/openreplay/charts/chalice/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/chalice/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" diff --git a/scripts/helmcharts/openreplay/charts/chalice/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/chalice/templates/deployment.yaml index 2635bc58a..a15553a8a 100644 --- a/scripts/helmcharts/openreplay/charts/chalice/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/chalice/templates/deployment.yaml @@ -43,6 +43,10 @@ spec: {{- .Values.healthCheck | toYaml | nindent 10}} {{- end}} env: + - name: ch_username + value: "{{ .Values.global.clickhouse.username }}" + - name: ch_password + value: "{{ .Values.global.clickhouse.password }}" - name: ch_host value: "{{ .Values.global.clickhouse.chHost }}" - name: ch_port @@ -62,7 +66,7 @@ spec: - name: pg_host value: '{{ .Values.global.postgresql.postgresqlHost }}' - name: pg_port - value: "5432" + value: '{{ .Values.global.postgresql.postgresqlPort }}' - name: pg_dbname value: "{{ .Values.global.postgresql.postgresqlDatabase }}" - name: pg_user @@ -130,6 +134,10 @@ spec: value: '{{ .Values.global.email.emailSslCert }}' - name: EMAIL_FROM value: '{{ .Values.global.email.emailFrom }}' + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} value: '{{ $val }}' diff --git a/scripts/helmcharts/openreplay/charts/db/Chart.yaml b/scripts/helmcharts/openreplay/charts/db/Chart.yaml index 1788ab793..037d1d085 100644 --- a/scripts/helmcharts/openreplay/charts/db/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/db/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" diff --git a/scripts/helmcharts/openreplay/charts/db/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/db/templates/deployment.yaml index 039e889a1..90e971c8d 100644 --- a/scripts/helmcharts/openreplay/charts/db/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/db/templates/deployment.yaml @@ -43,6 +43,10 @@ spec: {{- .Values.healthCheck | toYaml | nindent 10}} {{- end}} env: + - name: CH_USERNAME + value: '{{ .Values.global.clickhouse.userame }}' + - name: CH_PASSWORD + value: '{{ .Values.global.clickhouse.password }}' - name: CLICKHOUSE_STRING value: '{{ .Values.global.clickhouse.chHost }}:{{.Values.global.clickhouse.service.webPort}}/{{.Values.env.ch_db}}' - name: LICENSE_KEY @@ -65,6 +69,10 @@ spec: - name: POSTGRES_STRING value: 'postgres://{{ .Values.global.postgresql.postgresqlUser }}:$(pg_password)@{{ .Values.global.postgresql.postgresqlHost }}:{{ .Values.global.postgresql.postgresqlPort }}/{{ .Values.global.postgresql.postgresqlDatabase }}' {{- include "openreplay.env.redis_string" .Values.global.redis | nindent 12 }} + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} value: '{{ $val }}' diff --git a/scripts/helmcharts/openreplay/charts/ender/Chart.yaml b/scripts/helmcharts/openreplay/charts/ender/Chart.yaml index c15e9065f..76bf5e80f 100644 --- a/scripts/helmcharts/openreplay/charts/ender/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/ender/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" diff --git a/scripts/helmcharts/openreplay/charts/ender/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/ender/templates/deployment.yaml index e5b0a946b..fec4a808d 100644 --- a/scripts/helmcharts/openreplay/charts/ender/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/ender/templates/deployment.yaml @@ -61,6 +61,10 @@ spec: - name: POSTGRES_STRING value: 'postgres://{{ .Values.global.postgresql.postgresqlUser }}:$(pg_password)@{{ .Values.global.postgresql.postgresqlHost }}:{{ .Values.global.postgresql.postgresqlPort }}/{{ .Values.global.postgresql.postgresqlDatabase }}' {{- include "openreplay.env.redis_string" .Values.global.redis | nindent 12 }} + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} value: '{{ $val }}' diff --git a/scripts/helmcharts/openreplay/charts/frontend/Chart.yaml b/scripts/helmcharts/openreplay/charts/frontend/Chart.yaml index 1989cd77d..bd57607ba 100644 --- a/scripts/helmcharts/openreplay/charts/frontend/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/frontend/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" diff --git a/scripts/helmcharts/openreplay/charts/frontend/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/frontend/templates/deployment.yaml index e5eb29441..f685b76bc 100644 --- a/scripts/helmcharts/openreplay/charts/frontend/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/frontend/templates/deployment.yaml @@ -101,6 +101,10 @@ spec: value: '{{ .Values.global.s3.endpoint }}/{{.Values.global.s3.assetsBucket}}' {{- end }} {{- include "openreplay.env.redis_string" .Values.global.redis | nindent 12 }} + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} value: '{{ $val }}' diff --git a/scripts/helmcharts/openreplay/charts/heuristics/Chart.yaml b/scripts/helmcharts/openreplay/charts/heuristics/Chart.yaml index 0f964f13c..008e958c1 100644 --- a/scripts/helmcharts/openreplay/charts/heuristics/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/heuristics/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" diff --git a/scripts/helmcharts/openreplay/charts/heuristics/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/heuristics/templates/deployment.yaml index 6d88fec7a..f545ff77f 100644 --- a/scripts/helmcharts/openreplay/charts/heuristics/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/heuristics/templates/deployment.yaml @@ -50,6 +50,10 @@ spec: - name: KAFKA_USE_SSL value: '{{ .Values.global.kafka.kafkaUseSsl }}' {{- include "openreplay.env.redis_string" .Values.global.redis | nindent 12 }} + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} value: '{{ $val }}' diff --git a/scripts/helmcharts/openreplay/charts/http/Chart.yaml b/scripts/helmcharts/openreplay/charts/http/Chart.yaml index a2ed44a38..ffe60777f 100644 --- a/scripts/helmcharts/openreplay/charts/http/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/http/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" diff --git a/scripts/helmcharts/openreplay/charts/http/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/http/templates/deployment.yaml index 9f7d407bb..1add28054 100644 --- a/scripts/helmcharts/openreplay/charts/http/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/http/templates/deployment.yaml @@ -101,6 +101,10 @@ spec: value: '{{ .Values.global.s3.endpoint }}/{{.Values.global.s3.assetsBucket}}' {{- end }} {{- include "openreplay.env.redis_string" .Values.global.redis | nindent 12 }} + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} value: '{{ $val }}' diff --git a/scripts/helmcharts/openreplay/charts/http/templates/ingress.yaml b/scripts/helmcharts/openreplay/charts/http/templates/ingress.yaml index 5188285a3..01e9864c9 100644 --- a/scripts/helmcharts/openreplay/charts/http/templates/ingress.yaml +++ b/scripts/helmcharts/openreplay/charts/http/templates/ingress.yaml @@ -58,7 +58,7 @@ spec: name: minio port: number: 9000 - path: /(minio|mobs|sessions-assets|frontend|static|sourcemaps|ios-images)/ + path: /(minio|mobs|sessions-assets|frontend|static|sourcemaps|ios-images|records)/ tls: - hosts: - {{ .Values.global.domainName }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/CHANGELOG.md b/scripts/helmcharts/openreplay/charts/ingress-nginx/CHANGELOG.md index bedb9f720..7d81ac1bd 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/CHANGELOG.md +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/CHANGELOG.md @@ -2,6 +2,26 @@ This file documents all notable changes to [ingress-nginx](https://github.com/kubernetes/ingress-nginx) Helm Chart. The release numbering uses [semantic versioning](http://semver.org). +### 4.4.0 + +* Adding support for disabling liveness and readiness probes to the Helm chart by @njegosrailic in https://github.com/kubernetes/ingress-nginx/pull/9238 +* add:(admission-webhooks) ability to set securityContext by @ybelMekk in https://github.com/kubernetes/ingress-nginx/pull/9186 +* #7652 - Updated Helm chart to use the fullname for the electionID if not specified. by @FutureMatt in https://github.com/kubernetes/ingress-nginx/pull/9133 +* Rename controller-wehbooks-networkpolicy.yaml. by @Gacko in https://github.com/kubernetes/ingress-nginx/pull/9123 + +### 4.3.0 +- Support for Kubernetes v.1.25.0 was added and support for endpoint slices +- Support for Kubernetes v1.20.0 and v1.21.0 was removed +- [8890](https://github.com/kubernetes/ingress-nginx/pull/8890) migrate to endpointslices +- [9059](https://github.com/kubernetes/ingress-nginx/pull/9059) kubewebhookcertgen sha change after go1191 +- [9046](https://github.com/kubernetes/ingress-nginx/pull/9046) Parameterize metrics port name +- [9104](https://github.com/kubernetes/ingress-nginx/pull/9104) Fix yaml formatting error with multiple annotations + +### 4.2.1 + +- The sha of kube-webhook-certgen image & the opentelemetry image, in values file, was changed to new images built on alpine-v3.16.1 +- "[8896](https://github.com/kubernetes/ingress-nginx/pull/8896) updated to new images built today" + ### 4.2.0 - Support for Kubernetes v1.19.0 was removed @@ -109,7 +129,7 @@ This file documents all notable changes to [ingress-nginx](https://github.com/ku - [8118] https://github.com/kubernetes/ingress-nginx/pull/8118 Remove deprecated libraries, update other libs - [8117] https://github.com/kubernetes/ingress-nginx/pull/8117 Fix codegen errors - [8115] https://github.com/kubernetes/ingress-nginx/pull/8115 chart/ghaction: set the correct permission to have access to push a release -- [8098] https://github.com/kubernetes/ingress-nginx/pull/8098 generating SHA for CA only certs in backend_ssl.go + comparision of P… +- [8098] https://github.com/kubernetes/ingress-nginx/pull/8098 generating SHA for CA only certs in backend_ssl.go + comparison of P… - [8088] https://github.com/kubernetes/ingress-nginx/pull/8088 Fix Edit this page link to use main branch - [8072] https://github.com/kubernetes/ingress-nginx/pull/8072 Expose GeoIP2 Continent code as variable - [8061] https://github.com/kubernetes/ingress-nginx/pull/8061 docs(charts): using helm-docs for chart @@ -119,7 +139,7 @@ This file documents all notable changes to [ingress-nginx](https://github.com/ku - [8046] https://github.com/kubernetes/ingress-nginx/pull/8046 Report expired certificates (#8045) - [8044] https://github.com/kubernetes/ingress-nginx/pull/8044 remove G109 check till gosec resolves issues - [8042] https://github.com/kubernetes/ingress-nginx/pull/8042 docs_multiple_instances_one_cluster_ticket_7543 -- [8041] https://github.com/kubernetes/ingress-nginx/pull/8041 docs: fix typo'd executible name +- [8041] https://github.com/kubernetes/ingress-nginx/pull/8041 docs: fix typo'd executable name - [8035] https://github.com/kubernetes/ingress-nginx/pull/8035 Comment busy owners - [8029] https://github.com/kubernetes/ingress-nginx/pull/8029 Add stream-snippet as a ConfigMap and Annotation option - [8023] https://github.com/kubernetes/ingress-nginx/pull/8023 fix nginx compilation flags @@ -136,7 +156,7 @@ This file documents all notable changes to [ingress-nginx](https://github.com/ku - [7996] https://github.com/kubernetes/ingress-nginx/pull/7996 doc: improvement - [7983] https://github.com/kubernetes/ingress-nginx/pull/7983 Fix a couple of misspellings in the annotations documentation. - [7979] https://github.com/kubernetes/ingress-nginx/pull/7979 allow set annotations for admission Jobs -- [7977] https://github.com/kubernetes/ingress-nginx/pull/7977 Add ssl_reject_handshake to defaul server +- [7977] https://github.com/kubernetes/ingress-nginx/pull/7977 Add ssl_reject_handshake to default server - [7975] https://github.com/kubernetes/ingress-nginx/pull/7975 add legacy version update v0.50.0 to main changelog - [7972] https://github.com/kubernetes/ingress-nginx/pull/7972 updated service upstream definition diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/Chart.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/Chart.yaml index 7040a29cd..8b048ca0f 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/Chart.yaml @@ -1,46 +1,12 @@ annotations: artifacthub.io/changes: | - - "[8810](https://github.com/kubernetes/ingress-nginx/pull/8810) Prepare for v1.3.0" - - "[8808](https://github.com/kubernetes/ingress-nginx/pull/8808) revert arch var name" - - "[8805](https://github.com/kubernetes/ingress-nginx/pull/8805) Bump k8s.io/klog/v2 from 2.60.1 to 2.70.1" - - "[8803](https://github.com/kubernetes/ingress-nginx/pull/8803) Update to nginx base with alpine v3.16" - - "[8802](https://github.com/kubernetes/ingress-nginx/pull/8802) chore: start v1.3.0 release process" - - "[8798](https://github.com/kubernetes/ingress-nginx/pull/8798) Add v1.24.0 to test matrix" - - "[8796](https://github.com/kubernetes/ingress-nginx/pull/8796) fix: add MAC_OS variable for static-check" - - "[8793](https://github.com/kubernetes/ingress-nginx/pull/8793) changed to alpine-v3.16" - - "[8781](https://github.com/kubernetes/ingress-nginx/pull/8781) Bump github.com/stretchr/testify from 1.7.5 to 1.8.0" - - "[8778](https://github.com/kubernetes/ingress-nginx/pull/8778) chore: remove stable.txt from release process" - - "[8775](https://github.com/kubernetes/ingress-nginx/pull/8775) Remove stable" - - "[8773](https://github.com/kubernetes/ingress-nginx/pull/8773) Bump github/codeql-action from 2.1.14 to 2.1.15" - - "[8772](https://github.com/kubernetes/ingress-nginx/pull/8772) Bump ossf/scorecard-action from 1.1.1 to 1.1.2" - - "[8771](https://github.com/kubernetes/ingress-nginx/pull/8771) fix bullet md format" - - "[8770](https://github.com/kubernetes/ingress-nginx/pull/8770) Add condition for monitoring.coreos.com/v1 API" - - "[8769](https://github.com/kubernetes/ingress-nginx/pull/8769) Fix typos and add links to developer guide" - - "[8767](https://github.com/kubernetes/ingress-nginx/pull/8767) change v1.2.0 to v1.2.1 in deploy doc URLs" - - "[8765](https://github.com/kubernetes/ingress-nginx/pull/8765) Bump github/codeql-action from 1.0.26 to 2.1.14" - - "[8752](https://github.com/kubernetes/ingress-nginx/pull/8752) Bump github.com/spf13/cobra from 1.4.0 to 1.5.0" - - "[8751](https://github.com/kubernetes/ingress-nginx/pull/8751) Bump github.com/stretchr/testify from 1.7.2 to 1.7.5" - - "[8750](https://github.com/kubernetes/ingress-nginx/pull/8750) added announcement" - - "[8740](https://github.com/kubernetes/ingress-nginx/pull/8740) change sha e2etestrunner and echoserver" - - "[8738](https://github.com/kubernetes/ingress-nginx/pull/8738) Update docs to make it easier for noobs to follow step by step" - - "[8737](https://github.com/kubernetes/ingress-nginx/pull/8737) updated baseimage sha" - - "[8736](https://github.com/kubernetes/ingress-nginx/pull/8736) set ld-musl-path" - - "[8733](https://github.com/kubernetes/ingress-nginx/pull/8733) feat: migrate leaderelection lock to leases" - - "[8726](https://github.com/kubernetes/ingress-nginx/pull/8726) prometheus metric: upstream_latency_seconds" - - "[8720](https://github.com/kubernetes/ingress-nginx/pull/8720) Ci pin deps" - - "[8719](https://github.com/kubernetes/ingress-nginx/pull/8719) Working OpenTelemetry sidecar (base nginx image)" - - "[8714](https://github.com/kubernetes/ingress-nginx/pull/8714) Create Openssf scorecard" - - "[8708](https://github.com/kubernetes/ingress-nginx/pull/8708) Bump github.com/prometheus/common from 0.34.0 to 0.35.0" - - "[8703](https://github.com/kubernetes/ingress-nginx/pull/8703) Bump actions/dependency-review-action from 1 to 2" - - "[8701](https://github.com/kubernetes/ingress-nginx/pull/8701) Fix several typos" - - "[8699](https://github.com/kubernetes/ingress-nginx/pull/8699) fix the gosec test and a make target for it" - - "[8698](https://github.com/kubernetes/ingress-nginx/pull/8698) Bump actions/upload-artifact from 2.3.1 to 3.1.0" - - "[8697](https://github.com/kubernetes/ingress-nginx/pull/8697) Bump actions/setup-go from 2.2.0 to 3.2.0" - - "[8695](https://github.com/kubernetes/ingress-nginx/pull/8695) Bump actions/download-artifact from 2 to 3" - - "[8694](https://github.com/kubernetes/ingress-nginx/pull/8694) Bump crazy-max/ghaction-docker-buildx from 1.6.2 to 3.3.1" + - Adding support for disabling liveness and readiness probes to the Helm chart + - add:(admission-webhooks) ability to set securityContext + - Updated Helm chart to use the fullname for the electionID if not specified + - Rename controller-wehbooks-networkpolicy.yaml artifacthub.io/prerelease: "false" apiVersion: v2 -appVersion: 1.3.0 +appVersion: 1.5.1 description: Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer home: https://github.com/kubernetes/ingress-nginx @@ -56,5 +22,4 @@ maintainers: name: ingress-nginx sources: - https://github.com/kubernetes/ingress-nginx -type: application -version: 4.2.0 +version: 4.4.2 diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/README.md b/scripts/helmcharts/openreplay/charts/ingress-nginx/README.md index 942cf0467..e4ef9ec1b 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/README.md +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/README.md @@ -2,7 +2,7 @@ [ingress-nginx](https://github.com/kubernetes/ingress-nginx) Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer -![Version: 4.2.0](https://img.shields.io/badge/Version-4.2.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.3.0](https://img.shields.io/badge/AppVersion-1.3.0-informational?style=flat-square) +![Version: 4.4.2](https://img.shields.io/badge/Version-4.4.2-informational?style=flat-square) ![AppVersion: 1.5.1](https://img.shields.io/badge/AppVersion-1.5.1-informational?style=flat-square) To use, add `ingressClassName: nginx` spec field or the `kubernetes.io/ingress.class: nginx` annotation to your Ingress resources. @@ -175,7 +175,7 @@ controller: internal: enabled: true annotations: - # Create internal LB. More informations: https://cloud.google.com/kubernetes-engine/docs/how-to/internal-load-balancing + # Create internal LB. More information: https://cloud.google.com/kubernetes-engine/docs/how-to/internal-load-balancing # For GKE versions 1.17 and later networking.gke.io/load-balancer-type: "Internal" # For earlier versions @@ -217,6 +217,21 @@ With nginx-ingress-controller version 0.25+, the nginx ingress controller pod ex With nginx-ingress-controller in 0.25.* work only with kubernetes 1.14+, 0.26 fix [this issue](https://github.com/kubernetes/ingress-nginx/pull/4521) +#### How the Chart Configures the Hooks +A validating and configuration requires the endpoint to which the request is sent to use TLS. It is possible to set up custom certificates to do this, but in most cases, a self-signed certificate is enough. The setup of this component requires some more complex orchestration when using helm. The steps are created to be idempotent and to allow turning the feature on and off without running into helm quirks. + +1. A pre-install hook provisions a certificate into the same namespace using a format compatible with provisioning using end user certificates. If the certificate already exists, the hook exits. +2. The ingress nginx controller pod is configured to use a TLS proxy container, which will load that certificate. +3. Validating and Mutating webhook configurations are created in the cluster. +4. A post-install hook reads the CA from the secret created by step 1 and patches the Validating and Mutating webhook configurations. This process will allow a custom CA provisioned by some other process to also be patched into the webhook configurations. The chosen failure policy is also patched into the webhook configurations + +#### Alternatives +It should be possible to use [cert-manager/cert-manager](https://github.com/cert-manager/cert-manager) if a more complete solution is required. + +You can enable automatic self-signed TLS certificate provisioning via cert-manager by setting the `controller.admissionWebhooks.certManager.enable` value to true. + +Please ensure that cert-manager is correctly installed and configured. + ### Helm Error When Upgrading: spec.clusterIP: Invalid value: "" If you are upgrading this chart from a version between 0.31.0 and 1.2.2 then you may get an error like this: @@ -240,8 +255,12 @@ Kubernetes: `>=1.20.0-0` | commonLabels | object | `{}` | | | controller.addHeaders | object | `{}` | Will add custom headers before sending response traffic to the client according to: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#add-headers | | controller.admissionWebhooks.annotations | object | `{}` | | +| controller.admissionWebhooks.certManager.admissionCert.duration | string | `""` | | +| controller.admissionWebhooks.certManager.enabled | bool | `false` | | +| controller.admissionWebhooks.certManager.rootCert.duration | string | `""` | | | controller.admissionWebhooks.certificate | string | `"/usr/local/certificates/cert"` | | | controller.admissionWebhooks.createSecretJob.resources | object | `{}` | | +| controller.admissionWebhooks.createSecretJob.securityContext.allowPrivilegeEscalation | bool | `false` | | | controller.admissionWebhooks.enabled | bool | `true` | | | controller.admissionWebhooks.existingPsp | string | `""` | Use an existing PSP instead of creating one | | controller.admissionWebhooks.extraEnvs | list | `[]` | Additional environment variables to set | @@ -249,30 +268,35 @@ Kubernetes: `>=1.20.0-0` | controller.admissionWebhooks.key | string | `"/usr/local/certificates/key"` | | | controller.admissionWebhooks.labels | object | `{}` | Labels to be added to admission webhooks | | controller.admissionWebhooks.namespaceSelector | object | `{}` | | +| controller.admissionWebhooks.networkPolicyEnabled | bool | `false` | | | controller.admissionWebhooks.objectSelector | object | `{}` | | | controller.admissionWebhooks.patch.enabled | bool | `true` | | -| controller.admissionWebhooks.patch.fsGroup | int | `2000` | | -| controller.admissionWebhooks.patch.image.digest | string | `"sha256:64d8c73dca984af206adf9d6d7e46aa550362b1d7a01f3a0a91b20cc67868660"` | | +| controller.admissionWebhooks.patch.image.digest | string | `"sha256:39c5b2e3310dc4264d638ad28d9d1d96c4cbb2b2dcfb52368fe4e3c63f61e10f"` | | | controller.admissionWebhooks.patch.image.image | string | `"ingress-nginx/kube-webhook-certgen"` | | | controller.admissionWebhooks.patch.image.pullPolicy | string | `"IfNotPresent"` | | | controller.admissionWebhooks.patch.image.registry | string | `"registry.k8s.io"` | | -| controller.admissionWebhooks.patch.image.tag | string | `"v1.1.1"` | | +| controller.admissionWebhooks.patch.image.tag | string | `"v20220916-gd32f8c343"` | | | controller.admissionWebhooks.patch.labels | object | `{}` | Labels to be added to patch job resources | | controller.admissionWebhooks.patch.nodeSelector."kubernetes.io/os" | string | `"linux"` | | | controller.admissionWebhooks.patch.podAnnotations | object | `{}` | | -| controller.admissionWebhooks.patch.priorityClassName | string | `""` | Provide a priority class name to the webhook patching job | -| controller.admissionWebhooks.patch.runAsUser | int | `2000` | | +| controller.admissionWebhooks.patch.priorityClassName | string | `""` | Provide a priority class name to the webhook patching job # | +| controller.admissionWebhooks.patch.securityContext.fsGroup | int | `2000` | | +| controller.admissionWebhooks.patch.securityContext.runAsNonRoot | bool | `true` | | +| controller.admissionWebhooks.patch.securityContext.runAsUser | int | `2000` | | | controller.admissionWebhooks.patch.tolerations | list | `[]` | | | controller.admissionWebhooks.patchWebhookJob.resources | object | `{}` | | +| controller.admissionWebhooks.patchWebhookJob.securityContext.allowPrivilegeEscalation | bool | `false` | | | controller.admissionWebhooks.port | int | `8443` | | | controller.admissionWebhooks.service.annotations | object | `{}` | | | controller.admissionWebhooks.service.externalIPs | list | `[]` | | | controller.admissionWebhooks.service.loadBalancerSourceRanges | list | `[]` | | | controller.admissionWebhooks.service.servicePort | int | `443` | | | controller.admissionWebhooks.service.type | string | `"ClusterIP"` | | -| controller.affinity | object | `{}` | Affinity and anti-affinity rules for server scheduling to nodes | +| controller.affinity | object | `{}` | Affinity and anti-affinity rules for server scheduling to nodes # Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity # | | controller.allowSnippetAnnotations | bool | `true` | This configuration defines if Ingress Controller should allow users to set their own *-snippet annotations, otherwise this is forbidden / dropped when users add those annotations. Global snippets in ConfigMap are still respected | -| controller.annotations | object | `{}` | Annotations to be added to the controller Deployment or DaemonSet | +| controller.annotations | object | `{}` | Annotations to be added to the controller Deployment or DaemonSet # | +| controller.autoscaling.annotations | object | `{}` | | +| controller.autoscaling.apiVersion | string | `"autoscaling/v2"` | | | controller.autoscaling.behavior | object | `{}` | | | controller.autoscaling.enabled | bool | `false` | | | controller.autoscaling.maxReplicas | int | `11` | | @@ -289,14 +313,14 @@ Kubernetes: `>=1.20.0-0` | controller.customTemplate.configMapName | string | `""` | | | controller.dnsConfig | object | `{}` | Optionally customize the pod dnsConfig. | | controller.dnsPolicy | string | `"ClusterFirst"` | Optionally change this to ClusterFirstWithHostNet in case you have 'hostNetwork: true'. By default, while using host network, name resolution uses the host's DNS. If you wish nginx-controller to keep resolving names inside the k8s network, use ClusterFirstWithHostNet. | -| controller.electionID | string | `"ingress-controller-leader"` | Election ID to use for status update | -| controller.enableMimalloc | bool | `true` | Enable mimalloc as a drop-in replacement for malloc. | +| controller.electionID | string | `""` | Election ID to use for status update, by default it uses the controller name combined with a suffix of 'leader' | +| controller.enableMimalloc | bool | `true` | Enable mimalloc as a drop-in replacement for malloc. # ref: https://github.com/microsoft/mimalloc # | | controller.existingPsp | string | `""` | Use an existing PSP instead of creating one | | controller.extraArgs | object | `{}` | Additional command line arguments to pass to nginx-ingress-controller E.g. to specify the default SSL certificate you can use | | controller.extraContainers | list | `[]` | Additional containers to be added to the controller pod. See https://github.com/lemonldap-ng-controller/lemonldap-ng-controller as example. | | controller.extraEnvs | list | `[]` | Additional environment variables to set | | controller.extraInitContainers | list | `[]` | Containers, which are run before the app containers are started. | -| controller.extraModules | list | `[]` | | +| controller.extraModules | list | `[]` | Modules, which are mounted into the core nginx image. See values.yaml for a sample to add opentelemetry module | | controller.extraVolumeMounts | list | `[]` | Additional volumeMounts to the controller main container. | | controller.extraVolumes | list | `[]` | Additional volumes to the controller pod. | | controller.healthCheckHost | string | `""` | Address to bind the health check endpoint. It is better to set this option to the internal node address if the ingress nginx controller is running in the `hostNetwork: true` mode. | @@ -308,13 +332,13 @@ Kubernetes: `>=1.20.0-0` | controller.hostname | object | `{}` | Optionally customize the pod hostname. | | controller.image.allowPrivilegeEscalation | bool | `true` | | | controller.image.chroot | bool | `false` | | -| controller.image.digest | string | `"sha256:d1707ca76d3b044ab8a28277a2466a02100ee9f58a86af1535a3edf9323ea1b5"` | | -| controller.image.digestChroot | string | `"sha256:0fcb91216a22aae43b374fc2e6a03b8afe9e8c78cbf07a09d75636dc4ea3c191"` | | +| controller.image.digest | string | `"sha256:4ba73c697770664c1e00e9f968de14e08f606ff961c76e5d7033a4a9c593c629"` | | +| controller.image.digestChroot | string | `"sha256:c1c091b88a6c936a83bd7b098662760a87868d12452529bad0d178fb36147345"` | | | controller.image.image | string | `"ingress-nginx/controller"` | | | controller.image.pullPolicy | string | `"IfNotPresent"` | | | controller.image.registry | string | `"registry.k8s.io"` | | | controller.image.runAsUser | int | `101` | | -| controller.image.tag | string | `"v1.3.0"` | | +| controller.image.tag | string | `"v1.5.1"` | | | controller.ingressClass | string | `"nginx"` | For backwards compatibility with ingress.class annotation, use ingressClass. Algorithm is as follows, first ingressClassName is considered, if not present, controller looks for ingress.class annotation | | controller.ingressClassByName | bool | `false` | Process IngressClass per name (additionally as per spec.controller). | | controller.ingressClassResource.controllerValue | string | `"k8s.io/ingress-nginx"` | Controller-value of the controller that is processing this ingressClass | @@ -333,8 +357,8 @@ Kubernetes: `>=1.20.0-0` | controller.keda.scaledObject.annotations | object | `{}` | | | controller.keda.triggers | list | `[]` | | | controller.kind | string | `"Deployment"` | Use a `DaemonSet` or `Deployment` | -| controller.labels | object | `{}` | Labels to be added to the controller Deployment or DaemonSet and other resources that do not have option to specify labels | -| controller.lifecycle | object | `{"preStop":{"exec":{"command":["/wait-shutdown"]}}}` | Improve connection draining when ingress controller pod is deleted using a lifecycle hook: With this new hook, we increased the default terminationGracePeriodSeconds from 30 seconds to 300, allowing the draining of connections up to five minutes. If the active connections end before that, the pod will terminate gracefully at that time. To effectively take advantage of this feature, the Configmap feature worker-shutdown-timeout new value is 240s instead of 10s. | +| controller.labels | object | `{}` | Labels to be added to the controller Deployment or DaemonSet and other resources that do not have option to specify labels # | +| controller.lifecycle | object | `{"preStop":{"exec":{"command":["/wait-shutdown"]}}}` | Improve connection draining when ingress controller pod is deleted using a lifecycle hook: With this new hook, we increased the default terminationGracePeriodSeconds from 30 seconds to 300, allowing the draining of connections up to five minutes. If the active connections end before that, the pod will terminate gracefully at that time. To effectively take advantage of this feature, the Configmap feature worker-shutdown-timeout new value is 240s instead of 10s. # | | controller.livenessProbe.failureThreshold | int | `5` | | | controller.livenessProbe.httpGet.path | string | `"/healthz"` | | | controller.livenessProbe.httpGet.port | int | `10254` | | @@ -343,14 +367,15 @@ Kubernetes: `>=1.20.0-0` | controller.livenessProbe.periodSeconds | int | `10` | | | controller.livenessProbe.successThreshold | int | `1` | | | controller.livenessProbe.timeoutSeconds | int | `1` | | -| controller.maxmindLicenseKey | string | `""` | Maxmind license key to download GeoLite2 Databases. | +| controller.maxmindLicenseKey | string | `""` | Maxmind license key to download GeoLite2 Databases. # https://blog.maxmind.com/2019/12/18/significant-changes-to-accessing-and-using-geolite2-databases | | controller.metrics.enabled | bool | `false` | | | controller.metrics.port | int | `10254` | | +| controller.metrics.portName | string | `"metrics"` | | | controller.metrics.prometheusRule.additionalLabels | object | `{}` | | | controller.metrics.prometheusRule.enabled | bool | `false` | | | controller.metrics.prometheusRule.rules | list | `[]` | | | controller.metrics.service.annotations | object | `{}` | | -| controller.metrics.service.externalIPs | list | `[]` | List of IP addresses at which the stats-exporter service is available | +| controller.metrics.service.externalIPs | list | `[]` | List of IP addresses at which the stats-exporter service is available # Ref: https://kubernetes.io/docs/user-guide/services/#external-ips # | | controller.metrics.service.loadBalancerSourceRanges | list | `[]` | | | controller.metrics.service.servicePort | int | `10254` | | | controller.metrics.service.type | string | `"ClusterIP"` | | @@ -362,11 +387,14 @@ Kubernetes: `>=1.20.0-0` | controller.metrics.serviceMonitor.relabelings | list | `[]` | | | controller.metrics.serviceMonitor.scrapeInterval | string | `"30s"` | | | controller.metrics.serviceMonitor.targetLabels | list | `[]` | | -| controller.minAvailable | int | `1` | | -| controller.minReadySeconds | int | `0` | `minReadySeconds` to avoid killing pods before we are ready | +| controller.minAvailable | int | `1` | Define either 'minAvailable' or 'maxUnavailable', never both. | +| controller.minReadySeconds | int | `0` | `minReadySeconds` to avoid killing pods before we are ready # | | controller.name | string | `"controller"` | | -| controller.nodeSelector | object | `{"kubernetes.io/os":"linux"}` | Node labels for controller pod assignment | -| controller.podAnnotations | object | `{}` | Annotations to be added to controller pods | +| controller.nodeSelector | object | `{"kubernetes.io/os":"linux"}` | Node labels for controller pod assignment # Ref: https://kubernetes.io/docs/user-guide/node-selection/ # | +| controller.opentelemetry.containerSecurityContext.allowPrivilegeEscalation | bool | `false` | | +| controller.opentelemetry.enabled | bool | `false` | | +| controller.opentelemetry.image | string | `"registry.k8s.io/ingress-nginx/opentelemetry:v20221114-controller-v1.5.1-6-ga66ee73c5@sha256:41076fd9fb4255677c1a3da1ac3fc41477f06eba3c7ebf37ffc8f734dad51d7c"` | | +| controller.podAnnotations | object | `{}` | Annotations to be added to controller pods # | | controller.podLabels | object | `{}` | Labels to add to the pod container metadata | | controller.podSecurityContext | object | `{}` | Security Context policies for controller pods | | controller.priorityClassName | string | `""` | | @@ -390,17 +418,17 @@ Kubernetes: `>=1.20.0-0` | controller.scope.namespace | string | `""` | Namespace to limit the controller to; defaults to $(POD_NAMESPACE) | | controller.scope.namespaceSelector | string | `""` | When scope.enabled == false, instead of watching all namespaces, we watching namespaces whose labels only match with namespaceSelector. Format like foo=bar. Defaults to empty, means watching all namespaces. | | controller.service.annotations | object | `{}` | | -| controller.service.appProtocol | bool | `true` | If enabled is adding an appProtocol option for Kubernetes service. An appProtocol field replacing annotations that were using for setting a backend protocol. Here is an example for AWS: service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http It allows choosing the protocol for each backend specified in the Kubernetes service. See the following GitHub issue for more details about the purpose: https://github.com/kubernetes/kubernetes/issues/40244 Will be ignored for Kubernetes versions older than 1.20 | +| controller.service.appProtocol | bool | `true` | If enabled is adding an appProtocol option for Kubernetes service. An appProtocol field replacing annotations that were using for setting a backend protocol. Here is an example for AWS: service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http It allows choosing the protocol for each backend specified in the Kubernetes service. See the following GitHub issue for more details about the purpose: https://github.com/kubernetes/kubernetes/issues/40244 Will be ignored for Kubernetes versions older than 1.20 # | | controller.service.enableHttp | bool | `true` | | | controller.service.enableHttps | bool | `true` | | | controller.service.enabled | bool | `true` | | | controller.service.external.enabled | bool | `true` | | -| controller.service.externalIPs | list | `[]` | List of IP addresses at which the controller services are available | +| controller.service.externalIPs | list | `[]` | List of IP addresses at which the controller services are available # Ref: https://kubernetes.io/docs/user-guide/services/#external-ips # | | controller.service.internal.annotations | object | `{}` | Annotations are mandatory for the load balancer to come up. Varies with the cloud service. | | controller.service.internal.enabled | bool | `false` | Enables an additional internal load balancer (besides the external one). | | controller.service.internal.loadBalancerSourceRanges | list | `[]` | Restrict access For LoadBalancer service. Defaults to 0.0.0.0/0. | -| controller.service.ipFamilies | list | `["IPv4"]` | List of IP families (e.g. IPv4, IPv6) assigned to the service. This field is usually assigned automatically based on cluster configuration and the ipFamilyPolicy field. | -| controller.service.ipFamilyPolicy | string | `"SingleStack"` | Represents the dual-stack-ness requested or required by this Service. Possible values are SingleStack, PreferDualStack or RequireDualStack. The ipFamilies and clusterIPs fields depend on the value of this field. | +| controller.service.ipFamilies | list | `["IPv4"]` | List of IP families (e.g. IPv4, IPv6) assigned to the service. This field is usually assigned automatically based on cluster configuration and the ipFamilyPolicy field. # Ref: https://kubernetes.io/docs/concepts/services-networking/dual-stack/ | +| controller.service.ipFamilyPolicy | string | `"SingleStack"` | Represents the dual-stack-ness requested or required by this Service. Possible values are SingleStack, PreferDualStack or RequireDualStack. The ipFamilies and clusterIPs fields depend on the value of this field. # Ref: https://kubernetes.io/docs/concepts/services-networking/dual-stack/ | | controller.service.labels | object | `{}` | | | controller.service.loadBalancerIP | string | `""` | Used by cloud providers to connect the resulting `LoadBalancer` to a pre-existing static IP according to https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer | | controller.service.loadBalancerSourceRanges | list | `[]` | | @@ -417,12 +445,12 @@ Kubernetes: `>=1.20.0-0` | controller.sysctls | object | `{}` | See https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/ for notes on enabling and using sysctls | | controller.tcp.annotations | object | `{}` | Annotations to be added to the tcp config configmap | | controller.tcp.configMapNamespace | string | `""` | Allows customization of the tcp-services-configmap; defaults to $(POD_NAMESPACE) | -| controller.terminationGracePeriodSeconds | int | `300` | `terminationGracePeriodSeconds` to avoid killing pods before we are ready | -| controller.tolerations | list | `[]` | Node tolerations for server scheduling to nodes with taints | -| controller.topologySpreadConstraints | list | `[]` | Topology spread constraints rely on node labels to identify the topology domain(s) that each Node is in. | +| controller.terminationGracePeriodSeconds | int | `300` | `terminationGracePeriodSeconds` to avoid killing pods before we are ready # wait up to five minutes for the drain of connections # | +| controller.tolerations | list | `[]` | Node tolerations for server scheduling to nodes with taints # Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ # | +| controller.topologySpreadConstraints | list | `[]` | Topology spread constraints rely on node labels to identify the topology domain(s) that each Node is in. # Ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ # | | controller.udp.annotations | object | `{}` | Annotations to be added to the udp config configmap | | controller.udp.configMapNamespace | string | `""` | Allows customization of the udp-services-configmap; defaults to $(POD_NAMESPACE) | -| controller.updateStrategy | object | `{}` | The update strategy to apply to the Deployment or DaemonSet | +| controller.updateStrategy | object | `{}` | The update strategy to apply to the Deployment or DaemonSet # | | controller.watchIngressWithoutClass | bool | `false` | Process Ingress objects without ingressClass annotation/ingressClassName field Overrides value for --watch-ingress-without-class flag of the controller binary Defaults to false | | defaultBackend.affinity | object | `{}` | | | defaultBackend.autoscaling.annotations | object | `{}` | | @@ -431,7 +459,7 @@ Kubernetes: `>=1.20.0-0` | defaultBackend.autoscaling.minReplicas | int | `1` | | | defaultBackend.autoscaling.targetCPUUtilizationPercentage | int | `50` | | | defaultBackend.autoscaling.targetMemoryUtilizationPercentage | int | `50` | | -| defaultBackend.containerSecurityContext | object | `{}` | Security Context policies for controller main container. See https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/ for notes on enabling and using sysctls | +| defaultBackend.containerSecurityContext | object | `{}` | Security Context policies for controller main container. See https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/ for notes on enabling and using sysctls # | | defaultBackend.enabled | bool | `false` | | | defaultBackend.existingPsp | string | `""` | Use an existing PSP instead of creating one | | defaultBackend.extraArgs | object | `{}` | | @@ -454,10 +482,10 @@ Kubernetes: `>=1.20.0-0` | defaultBackend.livenessProbe.timeoutSeconds | int | `5` | | | defaultBackend.minAvailable | int | `1` | | | defaultBackend.name | string | `"defaultbackend"` | | -| defaultBackend.nodeSelector | object | `{"kubernetes.io/os":"linux"}` | Node labels for default backend pod assignment | -| defaultBackend.podAnnotations | object | `{}` | Annotations to be added to default backend pods | +| defaultBackend.nodeSelector | object | `{"kubernetes.io/os":"linux"}` | Node labels for default backend pod assignment # Ref: https://kubernetes.io/docs/user-guide/node-selection/ # | +| defaultBackend.podAnnotations | object | `{}` | Annotations to be added to default backend pods # | | defaultBackend.podLabels | object | `{}` | Labels to add to the pod container metadata | -| defaultBackend.podSecurityContext | object | `{}` | Security Context policies for controller pods See https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/ for notes on enabling and using sysctls | +| defaultBackend.podSecurityContext | object | `{}` | Security Context policies for controller pods See https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/ for notes on enabling and using sysctls # | | defaultBackend.port | int | `8080` | | | defaultBackend.priorityClassName | string | `""` | | | defaultBackend.readinessProbe.failureThreshold | int | `6` | | @@ -468,25 +496,25 @@ Kubernetes: `>=1.20.0-0` | defaultBackend.replicaCount | int | `1` | | | defaultBackend.resources | object | `{}` | | | defaultBackend.service.annotations | object | `{}` | | -| defaultBackend.service.externalIPs | list | `[]` | List of IP addresses at which the default backend service is available | +| defaultBackend.service.externalIPs | list | `[]` | List of IP addresses at which the default backend service is available # Ref: https://kubernetes.io/docs/user-guide/services/#external-ips # | | defaultBackend.service.loadBalancerSourceRanges | list | `[]` | | | defaultBackend.service.servicePort | int | `80` | | | defaultBackend.service.type | string | `"ClusterIP"` | | | defaultBackend.serviceAccount.automountServiceAccountToken | bool | `true` | | | defaultBackend.serviceAccount.create | bool | `true` | | | defaultBackend.serviceAccount.name | string | `""` | | -| defaultBackend.tolerations | list | `[]` | Node tolerations for server scheduling to nodes with taints | -| dhParam | string | `nil` | A base64-encoded Diffie-Hellman parameter. This can be generated with: `openssl dhparam 4096 2> /dev/null | base64` | -| imagePullSecrets | list | `[]` | Optional array of imagePullSecrets containing private registry credentials | +| defaultBackend.tolerations | list | `[]` | Node tolerations for server scheduling to nodes with taints # Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ # | +| dhParam | string | `nil` | A base64-encoded Diffie-Hellman parameter. This can be generated with: `openssl dhparam 4096 2> /dev/null | base64` # Ref: https://github.com/kubernetes/ingress-nginx/tree/main/docs/examples/customization/ssl-dh-param | +| imagePullSecrets | list | `[]` | Optional array of imagePullSecrets containing private registry credentials # Ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ | | podSecurityPolicy.enabled | bool | `false` | | -| portNamePrefix | string | `""` | Prefix for TCP and UDP ports names in ingress controller service | +| portNamePrefix | string | `""` | Prefix for TCP and UDP ports names in ingress controller service # Some cloud providers, like Yandex Cloud may have a requirements for a port name regex to support cloud load balancer integration | | rbac.create | bool | `true` | | | rbac.scope | bool | `false` | | -| revisionHistoryLimit | int | `10` | Rollback limit | +| revisionHistoryLimit | int | `10` | Rollback limit # | | serviceAccount.annotations | object | `{}` | Annotations for the controller service account | | serviceAccount.automountServiceAccountToken | bool | `true` | | | serviceAccount.create | bool | `true` | | | serviceAccount.name | string | `""` | | -| tcp | object | `{}` | TCP service key-value pairs | -| udp | object | `{}` | UDP service key-value pairs | +| tcp | object | `{}` | TCP service key-value pairs # Ref: https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/exposing-tcp-udp-services.md # | +| udp | object | `{}` | UDP service key-value pairs # Ref: https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/exposing-tcp-udp-services.md # | diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/README.md.gotmpl b/scripts/helmcharts/openreplay/charts/ingress-nginx/README.md.gotmpl index 895996111..78e4f38d4 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/README.md.gotmpl +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/README.md.gotmpl @@ -174,7 +174,7 @@ controller: internal: enabled: true annotations: - # Create internal LB. More informations: https://cloud.google.com/kubernetes-engine/docs/how-to/internal-load-balancing + # Create internal LB. More information: https://cloud.google.com/kubernetes-engine/docs/how-to/internal-load-balancing # For GKE versions 1.17 and later networking.gke.io/load-balancer-type: "Internal" # For earlier versions @@ -216,6 +216,21 @@ With nginx-ingress-controller version 0.25+, the nginx ingress controller pod ex With nginx-ingress-controller in 0.25.* work only with kubernetes 1.14+, 0.26 fix [this issue](https://github.com/kubernetes/ingress-nginx/pull/4521) +#### How the Chart Configures the Hooks +A validating and configuration requires the endpoint to which the request is sent to use TLS. It is possible to set up custom certificates to do this, but in most cases, a self-signed certificate is enough. The setup of this component requires some more complex orchestration when using helm. The steps are created to be idempotent and to allow turning the feature on and off without running into helm quirks. + +1. A pre-install hook provisions a certificate into the same namespace using a format compatible with provisioning using end user certificates. If the certificate already exists, the hook exits. +2. The ingress nginx controller pod is configured to use a TLS proxy container, which will load that certificate. +3. Validating and Mutating webhook configurations are created in the cluster. +4. A post-install hook reads the CA from the secret created by step 1 and patches the Validating and Mutating webhook configurations. This process will allow a custom CA provisioned by some other process to also be patched into the webhook configurations. The chosen failure policy is also patched into the webhook configurations + +#### Alternatives +It should be possible to use [cert-manager/cert-manager](https://github.com/cert-manager/cert-manager) if a more complete solution is required. + +You can enable automatic self-signed TLS certificate provisioning via cert-manager by setting the `controller.admissionWebhooks.certManager.enable` value to true. + +Please ensure that cert-manager is correctly installed and configured. + ### Helm Error When Upgrading: spec.clusterIP: Invalid value: "" If you are upgrading this chart from a version between 0.31.0 and 1.2.2 then you may get an error like this: diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/changelog.md.gotmpl b/scripts/helmcharts/openreplay/charts/ingress-nginx/changelog.md.gotmpl new file mode 100644 index 000000000..de9885670 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/changelog.md.gotmpl @@ -0,0 +1,9 @@ +# Changelog + +This file documents all notable changes to [ingress-nginx](https://github.com/kubernetes/ingress-nginx) Helm Chart. The release numbering uses [semantic versioning](http://semver.org). + +### {{ .NewHelmChartVersion }} +{{ with .HelmUpdates }} +{{ range . }}* {{ . }} +{{ end }}{{ end }} +**Full Changelog**: https://github.com/kubernetes/ingress-nginx/compare/helm-chart-{{ .PreviousHelmChartVersion }}...helm-chart-{{ .NewHelmChartVersion }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/controller-admission-tls-cert-manager-values.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/controller-admission-tls-cert-manager-values.yaml new file mode 100644 index 000000000..a13241cd4 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/controller-admission-tls-cert-manager-values.yaml @@ -0,0 +1,6 @@ +controller: + admissionWebhooks: + certManager: + enabled: true + service: + type: ClusterIP diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/deployment-extra-modules-default-container-sec-context.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/deployment-extra-modules-default-container-sec-context.yaml new file mode 100644 index 000000000..2310c344e --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/deployment-extra-modules-default-container-sec-context.yaml @@ -0,0 +1,12 @@ +controller: + image: + repository: ingress-controller/controller + tag: 1.0.0-dev + digest: null + service: + type: ClusterIP + containerSecurityContext: + allowPrivilegeEscalation: false + extraModules: + - name: opentelemetry + image: busybox diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/deployment-extra-modules-specific-container-sec-context.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/deployment-extra-modules-specific-container-sec-context.yaml new file mode 100644 index 000000000..bd2f011cc --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/deployment-extra-modules-specific-container-sec-context.yaml @@ -0,0 +1,12 @@ +controller: + image: + repository: ingress-controller/controller + tag: 1.0.0-dev + digest: null + service: + type: ClusterIP + extraModules: + - name: opentelemetry + image: busybox + containerSecurityContext: + allowPrivilegeEscalation: false diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/_helpers.tpl b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/_helpers.tpl index e69de0c41..7db5b2ca8 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/_helpers.tpl +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/_helpers.tpl @@ -85,6 +85,16 @@ We truncate at 63 chars because some Kubernetes name fields are limited to this {{- printf "%s-%s" (include "ingress-nginx.fullname" .) .Values.controller.name | trunc 63 | trimSuffix "-" -}} {{- end -}} +{{/* +Construct a unique electionID. +Users can provide an override for an explicit electionID if they want via `.Values.controller.electionID` +*/}} +{{- define "ingress-nginx.controller.electionID" -}} +{{- $defElectionID := printf "%s-leader" (include "ingress-nginx.fullname" .) -}} +{{- $electionID := default $defElectionID .Values.controller.electionID -}} +{{- print $electionID -}} +{{- end -}} + {{/* Construct the path for the publish-service. @@ -183,3 +193,20 @@ IngressClass parameters. {{ toYaml .Values.controller.ingressClassResource.parameters | indent 4}} {{ end }} {{- end -}} + +{{/* +Extra modules. +*/}} +{{- define "extraModules" -}} + +- name: {{ .name }} + image: {{ .image }} + command: ['sh', '-c', '/usr/local/bin/init_module.sh'] + {{- if (.containerSecurityContext) }} + securityContext: {{ .containerSecurityContext | toYaml | nindent 4 }} + {{- end }} + volumeMounts: + - name: {{ toYaml "modules"}} + mountPath: {{ toYaml "/modules_mount"}} + +{{- end -}} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/_params.tpl b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/_params.tpl index 305ce0dd2..66c581fa6 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/_params.tpl +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/_params.tpl @@ -10,7 +10,7 @@ - --publish-service={{ template "ingress-nginx.controller.publishServicePath" . }}-internal {{- end }} {{- end }} -- --election-id={{ .Values.controller.electionID }} +- --election-id={{ include "ingress-nginx.controller.electionID" . }} - --controller-class={{ .Values.controller.ingressClassResource.controllerValue }} {{- if .Values.controller.ingressClass }} - --ingress-class={{ .Values.controller.ingressClass }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/cert-manager.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/cert-manager.yaml new file mode 100644 index 000000000..55fab471c --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/cert-manager.yaml @@ -0,0 +1,63 @@ +{{- if and .Values.controller.admissionWebhooks.enabled .Values.controller.admissionWebhooks.certManager.enabled -}} +{{- if not .Values.controller.admissionWebhooks.certManager.issuerRef -}} +# Create a selfsigned Issuer, in order to create a root CA certificate for +# signing webhook serving certificates +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ include "ingress-nginx.fullname" . }}-self-signed-issuer + namespace: {{ .Release.Namespace }} +spec: + selfSigned: {} +--- +# Generate a CA Certificate used to sign certificates for the webhook +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "ingress-nginx.fullname" . }}-root-cert + namespace: {{ .Release.Namespace }} +spec: + secretName: {{ include "ingress-nginx.fullname" . }}-root-cert + duration: {{ .Values.controller.admissionWebhooks.certManager.rootCert.duration | default "43800h0m0s" | quote }} + issuerRef: + name: {{ include "ingress-nginx.fullname" . }}-self-signed-issuer + commonName: "ca.webhook.ingress-nginx" + isCA: true + subject: + organizations: + - ingress-nginx +--- +# Create an Issuer that uses the above generated CA certificate to issue certs +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ include "ingress-nginx.fullname" . }}-root-issuer + namespace: {{ .Release.Namespace }} +spec: + ca: + secretName: {{ include "ingress-nginx.fullname" . }}-root-cert +{{- end }} +--- +# generate a server certificate for the apiservices to use +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "ingress-nginx.fullname" . }}-admission + namespace: {{ .Release.Namespace }} +spec: + secretName: {{ include "ingress-nginx.fullname" . }}-admission + duration: {{ .Values.controller.admissionWebhooks.certManager.admissionCert.duration | default "8760h0m0s" | quote }} + issuerRef: + {{- if .Values.controller.admissionWebhooks.certManager.issuerRef }} + {{- toYaml .Values.controller.admissionWebhooks.certManager.issuerRef | nindent 4 }} + {{- else }} + name: {{ include "ingress-nginx.fullname" . }}-root-issuer + {{- end }} + dnsNames: + - {{ include "ingress-nginx.controller.fullname" . }}-admission + - {{ include "ingress-nginx.controller.fullname" . }}-admission.{{ .Release.Namespace }} + - {{ include "ingress-nginx.controller.fullname" . }}-admission.{{ .Release.Namespace }}.svc + subject: + organizations: + - ingress-nginx-admission +{{- end -}} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/clusterrole.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/clusterrole.yaml index 5659a1f10..f9ec70974 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/clusterrole.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/clusterrole.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.controller.admissionWebhooks.enabled .Values.controller.admissionWebhooks.patch.enabled -}} +{{- if and .Values.controller.admissionWebhooks.enabled .Values.controller.admissionWebhooks.patch.enabled (not .Values.controller.admissionWebhooks.certManager.enabled) -}} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/clusterrolebinding.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/clusterrolebinding.yaml index abf17fb9f..002abd43b 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/clusterrolebinding.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/clusterrolebinding.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.controller.admissionWebhooks.enabled .Values.controller.admissionWebhooks.patch.enabled -}} +{{- if and .Values.controller.admissionWebhooks.enabled .Values.controller.admissionWebhooks.patch.enabled (not .Values.controller.admissionWebhooks.certManager.enabled) -}} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml index 72c17eae4..d93433ecd 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.controller.admissionWebhooks.enabled .Values.controller.admissionWebhooks.patch.enabled -}} +{{- if and .Values.controller.admissionWebhooks.enabled .Values.controller.admissionWebhooks.patch.enabled (not .Values.controller.admissionWebhooks.certManager.enabled) -}} apiVersion: batch/v1 kind: Job metadata: @@ -59,8 +59,9 @@ spec: {{- if .Values.controller.admissionWebhooks.extraEnvs }} {{- toYaml .Values.controller.admissionWebhooks.extraEnvs | nindent 12 }} {{- end }} - securityContext: - allowPrivilegeEscalation: false + {{- if .Values.controller.admissionWebhooks.createSecretJob.securityContext }} + securityContext: {{ toYaml .Values.controller.admissionWebhooks.createSecretJob.securityContext | nindent 12 }} + {{- end }} {{- if .Values.controller.admissionWebhooks.createSecretJob.resources }} resources: {{ toYaml .Values.controller.admissionWebhooks.createSecretJob.resources | nindent 12 }} {{- end }} @@ -72,8 +73,8 @@ spec: {{- if .Values.controller.admissionWebhooks.patch.tolerations }} tolerations: {{ toYaml .Values.controller.admissionWebhooks.patch.tolerations | nindent 8 }} {{- end }} + {{- if .Values.controller.admissionWebhooks.patch.securityContext }} securityContext: - runAsNonRoot: true - runAsUser: {{ .Values.controller.admissionWebhooks.patch.runAsUser }} - fsGroup: {{ .Values.controller.admissionWebhooks.patch.fsGroup }} + {{- toYaml .Values.controller.admissionWebhooks.patch.securityContext | nindent 8 }} + {{- end }} {{- end }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml index 3a1637a64..0fa3ff9a2 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.controller.admissionWebhooks.enabled .Values.controller.admissionWebhooks.patch.enabled -}} +{{- if and .Values.controller.admissionWebhooks.enabled .Values.controller.admissionWebhooks.patch.enabled (not .Values.controller.admissionWebhooks.certManager.enabled) -}} apiVersion: batch/v1 kind: Job metadata: @@ -61,8 +61,9 @@ spec: {{- if .Values.controller.admissionWebhooks.extraEnvs }} {{- toYaml .Values.controller.admissionWebhooks.extraEnvs | nindent 12 }} {{- end }} - securityContext: - allowPrivilegeEscalation: false + {{- if .Values.controller.admissionWebhooks.patchWebhookJob.securityContext }} + securityContext: {{ toYaml .Values.controller.admissionWebhooks.patchWebhookJob.securityContext | nindent 12 }} + {{- end }} {{- if .Values.controller.admissionWebhooks.patchWebhookJob.resources }} resources: {{ toYaml .Values.controller.admissionWebhooks.patchWebhookJob.resources | nindent 12 }} {{- end }} @@ -74,8 +75,8 @@ spec: {{- if .Values.controller.admissionWebhooks.patch.tolerations }} tolerations: {{ toYaml .Values.controller.admissionWebhooks.patch.tolerations | nindent 8 }} {{- end }} + {{- if .Values.controller.admissionWebhooks.patch.securityContext }} securityContext: - runAsNonRoot: true - runAsUser: {{ .Values.controller.admissionWebhooks.patch.runAsUser }} - fsGroup: {{ .Values.controller.admissionWebhooks.patch.fsGroup }} + {{- toYaml .Values.controller.admissionWebhooks.patch.securityContext | nindent 8 }} + {{- end }} {{- end }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/psp.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/psp.yaml index 70edde334..e19c95572 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/psp.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/psp.yaml @@ -1,3 +1,4 @@ +{{- if (semverCompare "<1.25.0-0" .Capabilities.KubeVersion.Version) }} {{- if and .Values.controller.admissionWebhooks.enabled .Values.controller.admissionWebhooks.patch.enabled .Values.podSecurityPolicy.enabled (empty .Values.controller.admissionWebhooks.existingPsp) -}} apiVersion: policy/v1beta1 kind: PodSecurityPolicy @@ -37,3 +38,4 @@ spec: - secret - downwardAPI {{- end }} +{{- end }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/role.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/role.yaml index 795bac6b9..2aab6f4b1 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/role.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/role.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.controller.admissionWebhooks.enabled .Values.controller.admissionWebhooks.patch.enabled -}} +{{- if and .Values.controller.admissionWebhooks.enabled .Values.controller.admissionWebhooks.patch.enabled (not .Values.controller.admissionWebhooks.certManager.enabled) -}} apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/rolebinding.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/rolebinding.yaml index 698c5c864..60c3f4ff0 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/rolebinding.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/rolebinding.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.controller.admissionWebhooks.enabled .Values.controller.admissionWebhooks.patch.enabled -}} +{{- if and .Values.controller.admissionWebhooks.enabled .Values.controller.admissionWebhooks.patch.enabled (not .Values.controller.admissionWebhooks.certManager.enabled) -}} apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/serviceaccount.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/serviceaccount.yaml index eae475118..00be54ec5 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/serviceaccount.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/serviceaccount.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.controller.admissionWebhooks.enabled .Values.controller.admissionWebhooks.patch.enabled -}} +{{- if and .Values.controller.admissionWebhooks.enabled .Values.controller.admissionWebhooks.patch.enabled (not .Values.controller.admissionWebhooks.certManager.enabled) -}} apiVersion: v1 kind: ServiceAccount metadata: diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/validating-webhook.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/validating-webhook.yaml index 8caffcb03..f27244dc9 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/validating-webhook.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/validating-webhook.yaml @@ -4,8 +4,13 @@ apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: + annotations: + {{- if .Values.controller.admissionWebhooks.certManager.enabled }} + certmanager.k8s.io/inject-ca-from: {{ printf "%s/%s-admission" .Release.Namespace (include "ingress-nginx.fullname" .) | quote }} + cert-manager.io/inject-ca-from: {{ printf "%s/%s-admission" .Release.Namespace (include "ingress-nginx.fullname" .) | quote }} + {{- end }} {{- if .Values.controller.admissionWebhooks.annotations }} - annotations: {{ toYaml .Values.controller.admissionWebhooks.annotations | nindent 4 }} + {{- toYaml .Values.controller.admissionWebhooks.annotations | nindent 4 }} {{- end }} labels: {{- include "ingress-nginx.labels" . | nindent 4 }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/clusterrole.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/clusterrole.yaml index 0e725ec06..51bc5002c 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/clusterrole.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/clusterrole.yaml @@ -89,6 +89,14 @@ rules: - get - list - watch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get {{- end }} {{- end }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-daemonset.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-daemonset.yaml index 2dca8e5c1..e6721566b 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-daemonset.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-daemonset.yaml @@ -102,8 +102,12 @@ spec: {{- if .Values.controller.startupProbe }} startupProbe: {{ toYaml .Values.controller.startupProbe | nindent 12 }} {{- end }} + {{- if .Values.controller.livenessProbe }} livenessProbe: {{ toYaml .Values.controller.livenessProbe | nindent 12 }} + {{- end }} + {{- if .Values.controller.readinessProbe }} readinessProbe: {{ toYaml .Values.controller.readinessProbe | nindent 12 }} + {{- end }} ports: {{- range $key, $value := .Values.controller.containerPort }} - name: {{ $key }} @@ -114,7 +118,7 @@ spec: {{- end }} {{- end }} {{- if .Values.controller.metrics.enabled }} - - name: metrics + - name: {{ .Values.controller.metrics.portName }} containerPort: {{ .Values.controller.metrics.port }} protocol: TCP {{- end }} @@ -177,6 +181,12 @@ spec: - name: {{ .Name }} image: {{ .Image }} command: ['sh', '-c', '/usr/local/bin/init_module.sh'] + {{- if (or $.Values.controller.containerSecurityContext .containerSecurityContext) }} + securityContext: {{ .containerSecurityContext | default $.Values.controller.containerSecurityContext | toYaml | nindent 14 }} + {{- end }} + volumeMounts: + - name: modules + mountPath: /modules_mount {{- end }} {{- end }} {{- end }} @@ -215,6 +225,13 @@ spec: - name: webhook-cert secret: secretName: {{ include "ingress-nginx.fullname" . }}-admission + {{- if .Values.controller.admissionWebhooks.certManager.enabled }} + items: + - key: tls.crt + path: cert + - key: tls.key + path: key + {{- end }} {{- end }} {{- if .Values.controller.extraVolumes }} {{ toYaml .Values.controller.extraVolumes | nindent 8 }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-deployment.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-deployment.yaml index 5b781f8de..9f1cf70db 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-deployment.yaml @@ -106,8 +106,12 @@ spec: {{- if .Values.controller.startupProbe }} startupProbe: {{ toYaml .Values.controller.startupProbe | nindent 12 }} {{- end }} + {{- if .Values.controller.livenessProbe }} livenessProbe: {{ toYaml .Values.controller.livenessProbe | nindent 12 }} + {{- end }} + {{- if .Values.controller.readinessProbe }} readinessProbe: {{ toYaml .Values.controller.readinessProbe | nindent 12 }} + {{- end }} ports: {{- range $key, $value := .Values.controller.containerPort }} - name: {{ $key }} @@ -118,7 +122,7 @@ spec: {{- end }} {{- end }} {{- if .Values.controller.metrics.enabled }} - - name: metrics + - name: {{ .Values.controller.metrics.portName }} containerPort: {{ .Values.controller.metrics.port }} protocol: TCP {{- end }} @@ -143,11 +147,15 @@ spec: hostPort: {{ $key }} {{- end }} {{- end }} - {{- if (or .Values.controller.customTemplate.configMapName .Values.controller.extraVolumeMounts .Values.controller.admissionWebhooks.enabled .Values.controller.extraModules) }} + {{- if (or .Values.controller.customTemplate.configMapName .Values.controller.extraVolumeMounts .Values.controller.admissionWebhooks.enabled .Values.controller.extraModules .Values.controller.opentelemetry.enabled) }} volumeMounts: - {{- if .Values.controller.extraModules }} + {{- if (or .Values.controller.extraModules .Values.controller.opentelemetry.enabled) }} - name: modules + {{ if .Values.controller.image.chroot }} + mountPath: /chroot/modules_mount + {{ else }} mountPath: /modules_mount + {{ end }} {{- end }} {{- if .Values.controller.customTemplate.configMapName }} - mountPath: /etc/nginx/template @@ -169,21 +177,21 @@ spec: {{- if .Values.controller.extraContainers }} {{ toYaml .Values.controller.extraContainers | nindent 8 }} {{- end }} - {{- if (or .Values.controller.extraInitContainers .Values.controller.extraModules) }} + {{- if (or .Values.controller.extraInitContainers .Values.controller.extraModules .Values.controller.opentelemetry.enabled) }} initContainers: {{- if .Values.controller.extraInitContainers }} {{ toYaml .Values.controller.extraInitContainers | nindent 8 }} {{- end }} {{- if .Values.controller.extraModules }} {{- range .Values.controller.extraModules }} - - name: {{ .name }} - image: {{ .image }} - command: ['sh', '-c', '/usr/local/bin/init_module.sh'] - volumeMounts: - - name: modules - mountPath: /modules_mount + {{ $containerSecurityContext := .containerSecurityContext | default $.Values.controller.containerSecurityContext }} + {{- include "extraModules" (dict "name" .name "image" .image "containerSecurityContext" $containerSecurityContext | nindent 8) }} {{- end }} {{- end }} + {{- if .Values.controller.opentelemetry.enabled}} + {{ $otelContainerSecurityContext := $.Values.controller.opentelemetry.containerSecurityContext | default $.Values.controller.containerSecurityContext }} + {{- include "extraModules" (dict "name" "opentelemetry" "image" .Values.controller.opentelemetry.image "containerSecurityContext" $otelContainerSecurityContext) | nindent 8}} + {{- end}} {{- end }} {{- if .Values.controller.hostNetwork }} hostNetwork: {{ .Values.controller.hostNetwork }} @@ -202,9 +210,9 @@ spec: {{- end }} serviceAccountName: {{ template "ingress-nginx.serviceAccountName" . }} terminationGracePeriodSeconds: {{ .Values.controller.terminationGracePeriodSeconds }} - {{- if (or .Values.controller.customTemplate.configMapName .Values.controller.extraVolumeMounts .Values.controller.admissionWebhooks.enabled .Values.controller.extraVolumes .Values.controller.extraModules) }} + {{- if (or .Values.controller.customTemplate.configMapName .Values.controller.extraVolumeMounts .Values.controller.admissionWebhooks.enabled .Values.controller.extraVolumes .Values.controller.extraModules .Values.controller.opentelemetry.enabled) }} volumes: - {{- if .Values.controller.extraModules }} + {{- if (or .Values.controller.extraModules .Values.controller.opentelemetry.enabled)}} - name: modules emptyDir: {} {{- end }} @@ -220,6 +228,13 @@ spec: - name: webhook-cert secret: secretName: {{ include "ingress-nginx.fullname" . }}-admission + {{- if .Values.controller.admissionWebhooks.certManager.enabled }} + items: + - key: tls.crt + path: cert + - key: tls.key + path: key + {{- end }} {{- end }} {{- if .Values.controller.extraVolumes }} {{ toYaml .Values.controller.extraVolumes | nindent 8 }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-hpa.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-hpa.yaml index e0979f14b..d1e78bdfc 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-hpa.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-hpa.yaml @@ -1,7 +1,7 @@ {{- if and .Values.controller.autoscaling.enabled (or (eq .Values.controller.kind "Deployment") (eq .Values.controller.kind "Both")) -}} {{- if not .Values.controller.keda.enabled }} -apiVersion: autoscaling/v2beta2 +apiVersion: {{ .Values.controller.autoscaling.apiVersion }} kind: HorizontalPodAutoscaler metadata: annotations: diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-poddisruptionbudget.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-poddisruptionbudget.yaml index 8dfbe9891..899d3cc5d 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-poddisruptionbudget.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-poddisruptionbudget.yaml @@ -15,5 +15,9 @@ spec: matchLabels: {{- include "ingress-nginx.selectorLabels" . | nindent 6 }} app.kubernetes.io/component: controller + {{- if .Values.controller.minAvailable }} minAvailable: {{ .Values.controller.minAvailable }} + {{- else if .Values.controller.maxUnavailable }} + maxUnavailable: {{ .Values.controller.maxUnavailable }} + {{- end }} {{- end }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-psp.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-psp.yaml index fe34408c8..2e0499ce9 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-psp.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-psp.yaml @@ -1,3 +1,4 @@ +{{- if (semverCompare "<1.25.0-0" .Capabilities.KubeVersion.Version) }} {{- if and .Values.podSecurityPolicy.enabled (empty .Values.controller.existingPsp) -}} apiVersion: policy/v1beta1 kind: PodSecurityPolicy @@ -90,3 +91,4 @@ spec: seLinux: rule: 'RunAsAny' {{- end }} +{{- end }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-role.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-role.yaml index 8e5f8a0d7..d1aa9aac7 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-role.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-role.yaml @@ -58,27 +58,12 @@ rules: - get - list - watch - - apiGroups: - - "" - resources: - - configmaps - resourceNames: - - {{ .Values.controller.electionID }} - verbs: - - get - - update - - apiGroups: - - "" - resources: - - configmaps - verbs: - - create - apiGroups: - coordination.k8s.io resources: - leases resourceNames: - - {{ .Values.controller.electionID }} + - {{ include "ingress-nginx.controller.electionID" . }} verbs: - get - update @@ -95,6 +80,14 @@ rules: verbs: - create - patch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get {{- if .Values.podSecurityPolicy.enabled }} - apiGroups: [{{ template "podSecurityPolicy.apiGroup" . }}] resources: ['podsecuritypolicies'] diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-service-metrics.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-service-metrics.yaml index 0aaf41473..b178401c9 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-service-metrics.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-service-metrics.yaml @@ -31,10 +31,10 @@ spec: externalTrafficPolicy: {{ .Values.controller.metrics.service.externalTrafficPolicy }} {{- end }} ports: - - name: metrics + - name: {{ .Values.controller.metrics.portName }} port: {{ .Values.controller.metrics.service.servicePort }} protocol: TCP - targetPort: metrics + targetPort: {{ .Values.controller.metrics.portName }} {{- $setNodePorts := (or (eq .Values.controller.metrics.service.type "NodePort") (eq .Values.controller.metrics.service.type "LoadBalancer")) }} {{- if (and $setNodePorts (not (empty .Values.controller.metrics.service.nodePort))) }} nodePort: {{ .Values.controller.metrics.service.nodePort }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-serviceaccount.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-serviceaccount.yaml index 824b2a124..e6e776d09 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-serviceaccount.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-serviceaccount.yaml @@ -12,7 +12,7 @@ metadata: namespace: {{ .Release.Namespace }} {{- if .Values.serviceAccount.annotations }} annotations: - {{ toYaml .Values.serviceAccount.annotations | indent 4 }} + {{- toYaml .Values.serviceAccount.annotations | nindent 4 }} {{- end }} automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} {{- end }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-servicemonitor.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-servicemonitor.yaml index 4dbc6da9f..8ab16f0b2 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-servicemonitor.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-servicemonitor.yaml @@ -14,7 +14,7 @@ metadata: {{- end }} spec: endpoints: - - port: metrics + - port: {{ .Values.controller.metrics.portName }} interval: {{ .Values.controller.metrics.serviceMonitor.scrapeInterval }} {{- if .Values.controller.metrics.serviceMonitor.honorLabels }} honorLabels: true diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-webhooks-networkpolicy.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-webhooks-networkpolicy.yaml new file mode 100644 index 000000000..f74c2fbf3 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-webhooks-networkpolicy.yaml @@ -0,0 +1,19 @@ +{{- if .Values.controller.admissionWebhooks.enabled }} +{{- if .Values.controller.admissionWebhooks.networkPolicyEnabled }} + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "ingress-nginx.fullname" . }}-webhooks-allow + namespace: {{ .Release.Namespace }} +spec: + ingress: + - {} + podSelector: + matchLabels: + app.kubernetes.io/name: {{ include "ingress-nginx.name" . }} + policyTypes: + - Ingress + +{{- end }} +{{- end }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/default-backend-psp.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/default-backend-psp.yaml index 42061c5d3..c144c8fbf 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/default-backend-psp.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/default-backend-psp.yaml @@ -1,3 +1,4 @@ +{{- if (semverCompare "<1.25.0-0" .Capabilities.KubeVersion.Version) }} {{- if and .Values.podSecurityPolicy.enabled .Values.defaultBackend.enabled (empty .Values.defaultBackend.existingPsp) -}} apiVersion: policy/v1beta1 kind: PodSecurityPolicy @@ -34,3 +35,4 @@ spec: - secret - downwardAPI {{- end }} +{{- end }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/values.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/values.yaml index cddc2c742..b7089addb 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/values.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/values.yaml @@ -23,9 +23,9 @@ controller: ## for backwards compatibility consider setting the full image url via the repository value below ## use *either* current default registry/image or repository format or installing chart by providing the values.yaml will fail ## repository: - tag: "v1.3.0" - digest: sha256:d1707ca76d3b044ab8a28277a2466a02100ee9f58a86af1535a3edf9323ea1b5 - digestChroot: sha256:0fcb91216a22aae43b374fc2e6a03b8afe9e8c78cbf07a09d75636dc4ea3c191 + tag: "v1.5.1" + digest: sha256:4ba73c697770664c1e00e9f968de14e08f606ff961c76e5d7033a4a9c593c629 + digestChroot: sha256:c1c091b88a6c936a83bd7b098662760a87868d12452529bad0d178fb36147345 pullPolicy: IfNotPresent # www-data -> uid 101 runAsUser: 101 @@ -99,8 +99,8 @@ controller: # -- 'hostPort' https port https: 443 - # -- Election ID to use for status update - electionID: ingress-controller-leader + # -- Election ID to use for status update, by default it uses the controller name combined with a suffix of 'leader' + electionID: "" ## This section refers to the creation of the IngressClass resource ## IngressClass resources are supported since k8s >= 1.18 and required since k8s >= 1.19 @@ -345,7 +345,10 @@ controller: replicaCount: 1 + # -- Define either 'minAvailable' or 'maxUnavailable', never both. minAvailable: 1 + # -- Define either 'minAvailable' or 'maxUnavailable', never both. + # maxUnavailable: 1 ## Define requests resources to avoid probe issues due to CPU utilization in busy nodes ## ref: https://github.com/kubernetes/ingress-nginx/issues/4735#issuecomment-551204903 @@ -361,7 +364,9 @@ controller: # Mutually exclusive with keda autoscaling autoscaling: + apiVersion: autoscaling/v2 enabled: false + annotations: {} minReplicas: 1 maxReplicas: 11 targetCPUUtilizationPercentage: 50 @@ -369,7 +374,7 @@ controller: behavior: {} # scaleDown: # stabilizationWindowSeconds: 300 - # policies: + # policies: # - type: Pods # value: 1 # periodSeconds: 180 @@ -577,15 +582,21 @@ controller: # image: busybox # command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;'] + # -- Modules, which are mounted into the core nginx image. See values.yaml for a sample to add opentelemetry module extraModules: [] - ## Modules, which are mounted into the core nginx image - # - name: opentelemetry - # image: registry.k8s.io/ingress-nginx/opentelemetry:v20220415-controller-v1.2.0-beta.0-2-g81c2afd97@sha256:ce61e2cf0b347dffebb2dcbf57c33891d2217c1bad9c0959c878e5be671ef941 + # containerSecurityContext: + # allowPrivilegeEscalation: false # # The image must contain a `/usr/local/bin/init_module.sh` executable, which # will be executed as initContainers, to move its config files within the # mounted volume. + opentelemetry: + enabled: false + image: registry.k8s.io/ingress-nginx/opentelemetry:v20221114-controller-v1.5.1-6-ga66ee73c5@sha256:41076fd9fb4255677c1a3da1ac3fc41477f06eba3c7ebf37ffc8f734dad51d7c + containerSecurityContext: + allowPrivilegeEscalation: false + admissionWebhooks: annotations: {} # ignore-check.kube-linter.io/no-read-only-rootfs: "This deployment needs write access to root filesystem". @@ -615,6 +626,7 @@ controller: # -- Use an existing PSP instead of creating one existingPsp: "" + networkPolicyEnabled: false service: annotations: {} @@ -626,6 +638,8 @@ controller: type: ClusterIP createSecretJob: + securityContext: + allowPrivilegeEscalation: false resources: {} # limits: # cpu: 10m @@ -635,6 +649,8 @@ controller: # memory: 20Mi patchWebhookJob: + securityContext: + allowPrivilegeEscalation: false resources: {} patch: @@ -645,8 +661,8 @@ controller: ## for backwards compatibility consider setting the full image url via the repository value below ## use *either* current default registry/image or repository format or installing chart by providing the values.yaml will fail ## repository: - tag: v1.1.1 - digest: sha256:64d8c73dca984af206adf9d6d7e46aa550362b1d7a01f3a0a91b20cc67868660 + tag: v20220916-gd32f8c343 + digest: sha256:39c5b2e3310dc4264d638ad28d9d1d96c4cbb2b2dcfb52368fe4e3c63f61e10f pullPolicy: IfNotPresent # -- Provide a priority class name to the webhook patching job ## @@ -657,11 +673,26 @@ controller: tolerations: [] # -- Labels to be added to patch job resources labels: {} - runAsUser: 2000 - fsGroup: 2000 + securityContext: + runAsNonRoot: true + runAsUser: 2000 + fsGroup: 2000 + + # Use certmanager to generate webhook certs + certManager: + enabled: false + # self-signed root certificate + rootCert: + duration: "" # default to be 5y + admissionCert: + duration: "" # default to be 1y + # issuerRef: + # name: "issuer" + # kind: "ClusterIssuer" metrics: port: 10254 + portName: metrics # if this port is changed, change healthz-port: in extraArgs: accordingly enabled: false diff --git a/scripts/helmcharts/openreplay/charts/integrations/Chart.yaml b/scripts/helmcharts/openreplay/charts/integrations/Chart.yaml index 4d70e1667..800055e86 100644 --- a/scripts/helmcharts/openreplay/charts/integrations/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/integrations/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" diff --git a/scripts/helmcharts/openreplay/charts/integrations/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/integrations/templates/deployment.yaml index 0f9ead73c..522316d81 100644 --- a/scripts/helmcharts/openreplay/charts/integrations/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/integrations/templates/deployment.yaml @@ -61,6 +61,10 @@ spec: - name: POSTGRES_STRING value: 'postgres://{{ .Values.global.postgresql.postgresqlUser }}:$(pg_password)@{{ .Values.global.postgresql.postgresqlHost }}:{{ .Values.global.postgresql.postgresqlPort }}/{{ .Values.global.postgresql.postgresqlDatabase }}' {{- include "openreplay.env.redis_string" .Values.global.redis | nindent 12 }} + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} value: '{{ $val }}' diff --git a/scripts/helmcharts/openreplay/charts/peers/Chart.yaml b/scripts/helmcharts/openreplay/charts/peers/Chart.yaml index 6810b4353..e004a7f53 100644 --- a/scripts/helmcharts/openreplay/charts/peers/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/peers/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" diff --git a/scripts/helmcharts/openreplay/charts/peers/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/peers/templates/deployment.yaml index 2cbd395d9..98c290708 100644 --- a/scripts/helmcharts/openreplay/charts/peers/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/peers/templates/deployment.yaml @@ -54,6 +54,10 @@ spec: {{- else }} value: {{ .Values.global.s3.accessKey }} {{- end }} + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} value: '{{ $val }}' diff --git a/scripts/helmcharts/openreplay/charts/quickwit/Chart.yaml b/scripts/helmcharts/openreplay/charts/quickwit/Chart.yaml index 651b738a8..ffdb0b3d3 100644 --- a/scripts/helmcharts/openreplay/charts/quickwit/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/quickwit/Chart.yaml @@ -21,4 +21,4 @@ version: 0.3.1 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" diff --git a/scripts/helmcharts/openreplay/charts/quickwit/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/quickwit/templates/deployment.yaml index 3ac58c215..34c9ddd73 100644 --- a/scripts/helmcharts/openreplay/charts/quickwit/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/quickwit/templates/deployment.yaml @@ -57,6 +57,10 @@ spec: value: {{ .Values.global.s3.secretKey }} - name: QW_DATA_DIR value: /opt/openreplay/ + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} ports: {{- range $key, $val := .Values.service.ports }} - name: {{ $key }} diff --git a/scripts/helmcharts/openreplay/charts/sink/Chart.yaml b/scripts/helmcharts/openreplay/charts/sink/Chart.yaml index e83ed11f2..b5ebc0631 100644 --- a/scripts/helmcharts/openreplay/charts/sink/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/sink/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" diff --git a/scripts/helmcharts/openreplay/charts/sink/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/sink/templates/deployment.yaml index 7381541a1..88bd89c1f 100644 --- a/scripts/helmcharts/openreplay/charts/sink/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/sink/templates/deployment.yaml @@ -70,6 +70,10 @@ spec: value: '{{ .Values.global.s3.endpoint }}/{{.Values.global.s3.assetsBucket}}' {{- end }} {{- include "openreplay.env.redis_string" .Values.global.redis | nindent 12 }} + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} value: '{{ $val }}' diff --git a/scripts/helmcharts/openreplay/charts/sourcemapreader/Chart.yaml b/scripts/helmcharts/openreplay/charts/sourcemapreader/Chart.yaml index 1b7a7935a..ada7db898 100644 --- a/scripts/helmcharts/openreplay/charts/sourcemapreader/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/sourcemapreader/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" diff --git a/scripts/helmcharts/openreplay/charts/sourcemapreader/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/sourcemapreader/templates/deployment.yaml index 7abca821c..1d8041c5b 100644 --- a/scripts/helmcharts/openreplay/charts/sourcemapreader/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/sourcemapreader/templates/deployment.yaml @@ -79,6 +79,10 @@ spec: # S3 compatible storage value: '{{ .Values.global.s3.endpoint }}/{{.Values.global.s3.assetsBucket}}' {{- end }} + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} value: '{{ $val }}' diff --git a/scripts/helmcharts/openreplay/charts/storage/Chart.yaml b/scripts/helmcharts/openreplay/charts/storage/Chart.yaml index 3f6ed0843..b04396d89 100644 --- a/scripts/helmcharts/openreplay/charts/storage/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/storage/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" diff --git a/scripts/helmcharts/openreplay/charts/storage/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/storage/templates/deployment.yaml index 9cb2cca22..aff40a227 100644 --- a/scripts/helmcharts/openreplay/charts/storage/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/storage/templates/deployment.yaml @@ -78,6 +78,10 @@ spec: - name: KAFKA_USE_SSL value: '{{ .Values.global.kafka.kafkaUseSsl }}' {{- include "openreplay.env.redis_string" .Values.global.redis | nindent 12 }} + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} value: '{{ $val }}' diff --git a/scripts/helmcharts/openreplay/charts/utilities/Chart.yaml b/scripts/helmcharts/openreplay/charts/utilities/Chart.yaml index b92bba869..c91f503e8 100644 --- a/scripts/helmcharts/openreplay/charts/utilities/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/utilities/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.9.0" +AppVersion: "v1.10.0" diff --git a/scripts/helmcharts/openreplay/charts/utilities/templates/efs-cron.yaml b/scripts/helmcharts/openreplay/charts/utilities/templates/efs-cron.yaml index c7d790211..31b6caea8 100644 --- a/scripts/helmcharts/openreplay/charts/utilities/templates/efs-cron.yaml +++ b/scripts/helmcharts/openreplay/charts/utilities/templates/efs-cron.yaml @@ -1,4 +1,9 @@ +{{- $kubeTargetVersion := .Capabilities.KubeVersion.GitVersion }} +{{- if semverCompare ">=1.22.0-0" $kubeTargetVersion }} +apiVersion: batch/v1 +{{- else }} apiVersion: batch/v1beta1 +{{- end }} kind: CronJob metadata: name: efs-cleaner @@ -11,6 +16,11 @@ spec: spec: backoffLimit: 0 # Don't restart the failed jobs template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 12 }} + {{- end }} spec: restartPolicy: Never containers: diff --git a/scripts/helmcharts/openreplay/charts/utilities/templates/report-cron.yaml b/scripts/helmcharts/openreplay/charts/utilities/templates/report-cron.yaml index 17fa52720..b3f258df4 100644 --- a/scripts/helmcharts/openreplay/charts/utilities/templates/report-cron.yaml +++ b/scripts/helmcharts/openreplay/charts/utilities/templates/report-cron.yaml @@ -1,5 +1,10 @@ {{- if .Values.global.enterpriseEditionLicense }} +{{- $kubeTargetVersion := .Capabilities.KubeVersion.GitVersion }} +{{- if semverCompare ">=1.22.0-0" $kubeTargetVersion }} +apiVersion: batch/v1 +{{- else }} apiVersion: batch/v1beta1 +{{- end }} kind: CronJob metadata: name: report-cron @@ -12,6 +17,11 @@ spec: spec: backoffLimit: 0 # Don't restart the failed jobs template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 12 }} + {{- end }} spec: restartPolicy: Never containers: diff --git a/scripts/helmcharts/openreplay/charts/utilities/templates/sessions-cleaner-cron.yaml b/scripts/helmcharts/openreplay/charts/utilities/templates/sessions-cleaner-cron.yaml index 49bc8cfed..d724d44cc 100644 --- a/scripts/helmcharts/openreplay/charts/utilities/templates/sessions-cleaner-cron.yaml +++ b/scripts/helmcharts/openreplay/charts/utilities/templates/sessions-cleaner-cron.yaml @@ -1,5 +1,10 @@ {{- if .Values.global.enterpriseEditionLicense }} +{{- $kubeTargetVersion := .Capabilities.KubeVersion.GitVersion }} +{{- if semverCompare ">=1.22.0-0" $kubeTargetVersion }} +apiVersion: batch/v1 +{{- else }} apiVersion: batch/v1beta1 +{{- end }} kind: CronJob metadata: name: sessions-cleaner-cron @@ -12,6 +17,11 @@ spec: spec: backoffLimit: 0 # Don't restart the failed jobs template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 12 }} + {{- end }} spec: restartPolicy: Never containers: diff --git a/scripts/helmcharts/openreplay/charts/utilities/templates/telemetry-cron.yaml b/scripts/helmcharts/openreplay/charts/utilities/templates/telemetry-cron.yaml index b9044664f..2ada4cd10 100644 --- a/scripts/helmcharts/openreplay/charts/utilities/templates/telemetry-cron.yaml +++ b/scripts/helmcharts/openreplay/charts/utilities/templates/telemetry-cron.yaml @@ -1,5 +1,10 @@ {{- if .Values.global.enterpriseEditionLicense }} +{{- $kubeTargetVersion := .Capabilities.KubeVersion.GitVersion }} +{{- if semverCompare ">=1.22.0-0" $kubeTargetVersion }} +apiVersion: batch/v1 +{{- else }} apiVersion: batch/v1beta1 +{{- end }} kind: CronJob metadata: name: telemetry-cron @@ -12,6 +17,11 @@ spec: spec: backoffLimit: 0 # Don't restart the failed jobs template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 12 }} + {{- end }} spec: restartPolicy: Never containers: diff --git a/scripts/helmcharts/openreplay/charts/utilities/values.yaml b/scripts/helmcharts/openreplay/charts/utilities/values.yaml index 0da3a40fb..97ee29798 100644 --- a/scripts/helmcharts/openreplay/charts/utilities/values.yaml +++ b/scripts/helmcharts/openreplay/charts/utilities/values.yaml @@ -81,10 +81,13 @@ fullnameOverride: "utilities-openreplay" # 5 3 * * 1 “At 03:05 on Monday.” # refer: https://crontab.guru/#5_3_*_*_1 -cron: "5 3 */3 * *" +cron: "5 3 * * 1" # Pod configurations +podAnnotations: + linkerd.io/inject: disabled + securityContext: runAsUser: 1001 runAsGroup: 1001 diff --git a/scripts/helmcharts/openreplay/files/clickhouse.sh b/scripts/helmcharts/openreplay/files/clickhouse.sh index 5b11f4e91..9f6a1cc73 100644 --- a/scripts/helmcharts/openreplay/files/clickhouse.sh +++ b/scripts/helmcharts/openreplay/files/clickhouse.sh @@ -4,6 +4,10 @@ set -ex clickhousedir=/opt/openreplay/openreplay/scripts/schema/db/init_dbs/clickhouse +[[ "${CH_PASSWORD}" == "" ]] || { + CH_PASSWORD="--password $CH_PASSWORD" +} + function migrate() { echo "Starting clickhouse migration" IFS=',' read -r -a migration_versions <<< "$1" @@ -11,7 +15,7 @@ function migrate() { echo "Migrating clickhouse version $version" # For now, we can ignore the clickhouse db inject errors. # TODO: Better error handling in script - clickhouse-client -h ${CH_HOST} --port ${CH_PORT} --multiquery < ${clickhousedir}/${version}/${version}.sql || true + clickhouse-client -h ${CH_HOST} --port ${CH_PORT} --user ${CH_USERNAME} ${CH_PASSWORD} --multiquery < ${clickhousedir}/${version}/${version}.sql || true done } @@ -19,7 +23,7 @@ function init() { echo "Initializing clickhouse" for file in `ls ${clickhousedir}/create/*.sql`; do echo "Injecting $file" - clickhouse-client -h ${CH_HOST} --port ${CH_PORT} --multiquery < $file || true + clickhouse-client -h ${CH_HOST} --user ${CH_USERNAME} ${CH_PASSWORD} --port ${CH_PORT} --multiquery < $file || true done } diff --git a/scripts/helmcharts/openreplay/files/dbops.sh b/scripts/helmcharts/openreplay/files/dbops.sh index 21ace9d2c..6402a3c88 100644 --- a/scripts/helmcharts/openreplay/files/dbops.sh +++ b/scripts/helmcharts/openreplay/files/dbops.sh @@ -33,7 +33,13 @@ function migration() { # Checking migration versions cd /opt/openreplay/openreplay/scripts/schema - migration_versions=(`ls -l db/init_dbs/$db | grep -E ^d | awk -v number=${PREVIOUS_APP_VERSION} '$NF > number {print $NF}' | grep -v create`) + + # We need to remove version dots + function normalise_version { + echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }' + } + all_versions=(`ls -l db/init_dbs/$db | grep -E ^d | grep -v create | awk '{print $NF}'`) + migration_versions=(`for ver in ${all_versions[*]}; do if [[ $(normalise_version $ver) > $(normalise_version "${PREVIOUS_APP_VERSION}") ]]; then echo $ver; fi; done`) echo "Migration version: ${migration_versions[*]}" # Can't pass the space seperated array to ansible for migration. So joining them with , joined_migration_versions=$(IFS=, ; echo "${migration_versions[*]}") diff --git a/scripts/helmcharts/openreplay/files/minio.sh b/scripts/helmcharts/openreplay/files/minio.sh index 34229572c..dcfa01588 100644 --- a/scripts/helmcharts/openreplay/files/minio.sh +++ b/scripts/helmcharts/openreplay/files/minio.sh @@ -5,7 +5,7 @@ set -e cd /tmp -buckets=("mobs" "sessions-assets" "sourcemaps" "sessions-mobile-assets" "quickwit" "vault-data") +buckets=("mobs" "sessions-assets" "static" "sourcemaps" "sessions-mobile-assets" "quickwit" "vault-data" "records") mc alias set minio $MINIO_HOST $MINIO_ACCESS_KEY $MINIO_SECRET_KEY diff --git a/scripts/helmcharts/openreplay/templates/job.yaml b/scripts/helmcharts/openreplay/templates/job.yaml index 50a234904..3e0494d7f 100644 --- a/scripts/helmcharts/openreplay/templates/job.yaml +++ b/scripts/helmcharts/openreplay/templates/job.yaml @@ -26,11 +26,19 @@ spec: template: metadata: name: postgresqlMigrate + {{- with .Values.migrationJob.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} spec: initContainers: - name: git image: alpine/git env: + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} - name: ENTERPRISE_EDITION_LICENSE value: "{{ .Values.global.enterpriseEditionLicense }}" command: @@ -103,6 +111,10 @@ spec: {{- else }} value: '{{ .Values.global.postgresql.postgresqlPassword }}' {{- end}} + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} image: bitnami/postgresql:13.3.0-debian-10-r53 command: - /bin/bash @@ -118,6 +130,10 @@ spec: - name: minio image: bitnami/minio:2020.10.9-debian-10-r6 env: + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} - name: FORCE_MIGRATION value: "{{ .Values.forceMigration }}" - name: UPGRADE_FRONTENT @@ -148,6 +164,10 @@ spec: {{- if .Values.vault.enabled }} - name: vault env: + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} - name: FORCE_MIGRATION value: "{{ .Values.forceMigration }}" - name: PGHOST @@ -173,6 +193,10 @@ spec: mountPath: /opt/migrations/ - name: vault-s3-upload env: + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} - name: AWS_ACCESS_KEY_ID value: "{{ .Values.global.s3.accessKey }}" - name: AWS_SECRET_ACCESS_KEY @@ -215,8 +239,12 @@ spec: mountPath: /opt/openreplay {{- end }} - name: clickhouse - image: yandex/clickhouse-client:21.9.4.35 + image: clickhouse/clickhouse-server:22.12-alpine env: + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} - name: FORCE_MIGRATION value: "{{ .Values.forceMigration }}" - name: PREVIOUS_APP_VERSION @@ -227,6 +255,10 @@ spec: value: "{{.Values.global.clickhouse.chHost}}" - name: CH_PORT value: "{{.Values.global.clickhouse.service.webPort}}" + - name: CH_USERNAME + value: "{{.Values.global.clickhouse.username}}" + - name: CH_PASSWORD + value: "{{.Values.global.clickhouse.password}}" command: - /bin/bash - /opt/migrations/dbops.sh @@ -240,6 +272,10 @@ spec: - name: kafka image: bitnami/kafka:2.6.0-debian-10-r30 env: + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} - name: RETENTION_TIME value: "{{ .Values.global.kafka.retentionTime }}" - name: KAFKA_HOST @@ -275,7 +311,7 @@ spec: - name: datadir hostPath: # Ensure the file directory is created. - path: /mnt/efs + path: /openreplay/storage/nfs type: DirectoryOrCreate {{- else }} - name: datadir diff --git a/scripts/helmcharts/openreplay/values.yaml b/scripts/helmcharts/openreplay/values.yaml index 67cf00405..694585180 100644 --- a/scripts/helmcharts/openreplay/values.yaml +++ b/scripts/helmcharts/openreplay/values.yaml @@ -1,3 +1,7 @@ +migrationJob: + podAnnotations: + linkerd.io/inject: disabled + redis: &redis tls: enabled: false @@ -5,15 +9,10 @@ redis: &redis ingress-nginx: enabled: true controller: - name: controller - image: - registry: k8s.gcr.io - image: ingress-nginx/controller - ## for backwards compatibility consider setting the full image url via the repository value below - ## use *either* current default registry/image or repository format or installing chart by providing the values.yaml will fail - ## repository: - tag: "v1.3.0" - digest: "" + admissionWebhooks: + patch: + podAnnotations: + linkerd.io/inject: disabled # For enterpriseEdition Only vault: &vault @@ -38,3 +37,5 @@ global: vault: *vault redis: *redis clusterDomain: "svc.cluster.local" + # In case you've http proxy to access internet. + env: {} diff --git a/scripts/helmcharts/toolings/.helmignore b/scripts/helmcharts/toolings/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/scripts/helmcharts/toolings/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/scripts/helmcharts/toolings/Chart.yaml b/scripts/helmcharts/toolings/Chart.yaml new file mode 100644 index 000000000..9ef9030f3 --- /dev/null +++ b/scripts/helmcharts/toolings/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: tooling +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/scripts/helmcharts/toolings/charts/kyverno/.helmignore b/scripts/helmcharts/toolings/charts/kyverno/.helmignore new file mode 100644 index 000000000..20b07486a --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/.helmignore @@ -0,0 +1,2 @@ +ci/ +README.md.gotmpl diff --git a/scripts/helmcharts/toolings/charts/kyverno/Chart.yaml b/scripts/helmcharts/toolings/charts/kyverno/Chart.yaml new file mode 100644 index 000000000..f0b1aad5c --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/Chart.yaml @@ -0,0 +1,53 @@ +annotations: + artifacthub.io/changes: | + - kind: added + description: Added possibility to define additional init and sidecar container. + - kind: added + description: Added ability to remove namespaces from default resourceFilters list. + - kind: added + description: Prevent installing Kyverno in namespace kube-system. + - kind: fixed + description: Docs for generatecontrollerExtraResources. + - kind: changed + description: Enable autogen internals by default. + - kind: fixed + description: Self signed certificates not using SANs. + - kind: added + description: Extra args support for init container. + - kind: added + description: Allow overriding of test security context and resource block. + - kind: added + description: Added possibility to define custom image registries + - kind: added + description: Enable adding optional annotations to configmaps + - kind: added + description: Add startup probes support + - kind: added + description: Support extra CRD annotations + - kind: added + description: Grafana dashboard. + artifacthub.io/links: | + - name: Documentation + url: https://kyverno.io/docs + artifacthub.io/operator: "false" + artifacthub.io/prerelease: "false" +apiVersion: v2 +appVersion: v1.8.5 +description: Kubernetes Native Policy Management +home: https://kyverno.io/ +icon: https://github.com/kyverno/kyverno/raw/main/img/logo.png +keywords: +- kubernetes +- nirmata +- policy agent +- validating webhook +- admissions controller +kubeVersion: '>=1.16.0-0' +maintainers: +- name: Nirmata + url: https://kyverno.io/ +name: kyverno +sources: +- https://github.com/kyverno/kyverno +type: application +version: 2.6.5 diff --git a/scripts/helmcharts/toolings/charts/kyverno/README.md b/scripts/helmcharts/toolings/charts/kyverno/README.md new file mode 100644 index 000000000..caf65fb85 --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/README.md @@ -0,0 +1,278 @@ +# kyverno + +Kubernetes Native Policy Management + +![Version: 2.6.5](https://img.shields.io/badge/Version-2.6.5-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v1.8.5](https://img.shields.io/badge/AppVersion-v1.8.5-informational?style=flat-square) + +## About + +[Kyverno](https://kyverno.io) is a Kubernetes Native Policy Management engine. + +It allows you to: +- Manage policies as Kubernetes resources (no new language required.) +- Validate, mutate, and generate resource configurations. +- Select resources based on labels and wildcards. +- View policy enforcement as events. +- Scan existing resources for violations. + +This chart bootstraps a Kyverno deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +Access the complete user documentation and guides at: https://kyverno.io. + +## Installing the Chart + +**Add the Kyverno Helm repository:** + +```console +$ helm repo add kyverno https://kyverno.github.io/kyverno/ +``` + +**Create a namespace:** + +You can install Kyverno in any namespace. The examples use `kyverno` as the namespace. + +```console +$ kubectl create namespace kyverno +``` + +**Install the Kyverno chart:** + +```console +$ helm install kyverno --namespace kyverno kyverno/kyverno +``` + +The command deploys Kyverno on the Kubernetes cluster with default configuration. The [installation](https://kyverno.io/docs/installation/) guide lists the parameters that can be configured during installation. + +The Kyverno ClusterRole/ClusterRoleBinding that manages webhook configurations must have the suffix `:webhook`. Ex., `*:webhook` or `kyverno:webhook`. +Other ClusterRole/ClusterRoleBinding names are configurable. + +**Notes on using ArgoCD:** + +When deploying this chart with ArgoCD you will need to enable `Replace` in the `syncOptions`, and you probably want to ignore diff in aggregated cluster roles. + +You can do so by following instructions in these pages of ArgoCD documentation: +- [Enable Replace in the syncOptions](https://argo-cd.readthedocs.io/en/stable/user-guide/sync-options/#replace-resource-instead-of-applying-changes) +- [Ignore diff in aggregated cluster roles](https://argo-cd.readthedocs.io/en/stable/user-guide/diffing/#ignoring-rbac-changes-made-by-aggregateroles) + +ArgoCD uses helm only for templating but applies the results with `kubectl`. + +Unfortunately `kubectl` adds metadata that will cross the limit allowed by Kuberrnetes. Using `Replace` overcomes this limitation. + +Another option is to use server side apply, this will be supported in ArgoCD v2.5. + +Finally, we introduced new CRDs in 1.8 to manage resource-level reports. Those reports are associated with parent resources using an `ownerReference` object. + +As a consequence, ArgoCD will show those reports in the UI, but as they are managed dynamically by Kyverno it can pollute your dashboard. + +You can tell ArgoCD to ignore reports globally by adding them under the `resource.exclusions` stanza in the ArgoCD ConfigMap. + +```yaml + resource.exclusions: | + - apiGroups: + - kyverno.io + kinds: + - AdmissionReport + - BackgroundScanReport + - ClusterAdmissionReport + - ClusterBackgroundScanReport + clusters: + - '*' +``` + +Below is an example of ArgoCD Application manifest that should work with this chart. + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: kyverno + namespace: argocd +spec: + destination: + namespace: kyverno + server: https://kubernetes.default.svc + project: default + source: + chart: kyverno + repoURL: https://kyverno.github.io/kyverno + targetRevision: 2.6.0 + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + - Replace=true +``` + +## Uninstalling the Chart + +To uninstall/delete the `kyverno` deployment: + +```console +$ helm delete -n kyverno kyverno +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| nameOverride | string | `nil` | Override the name of the chart | +| fullnameOverride | string | `nil` | Override the expanded name of the chart | +| namespace | string | `nil` | Namespace the chart deploys to | +| customLabels | object | `{}` | Additional labels | +| rbac.create | bool | `true` | Create ClusterRoles, ClusterRoleBindings, and ServiceAccount | +| rbac.serviceAccount.create | bool | `true` | Create a ServiceAccount | +| rbac.serviceAccount.name | string | `nil` | The ServiceAccount name | +| rbac.serviceAccount.annotations | object | `{}` | Annotations for the ServiceAccount | +| image.registry | string | `nil` | Image registry | +| image.repository | string | `"ghcr.io/kyverno/kyverno"` | Image repository | +| image.tag | string | `nil` | Image tag Defaults to appVersion in Chart.yaml if omitted | +| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy | +| image.pullSecrets | list | `[]` | Image pull secrets | +| initImage.registry | string | `nil` | Image registry | +| initImage.repository | string | `"ghcr.io/kyverno/kyvernopre"` | Image repository | +| initImage.tag | string | `nil` | Image tag If initImage.tag is missing, defaults to image.tag | +| initImage.pullPolicy | string | `nil` | Image pull policy If initImage.pullPolicy is missing, defaults to image.pullPolicy | +| initContainer.extraArgs | list | `["--loggingFormat=text"]` | Extra arguments to give to the kyvernopre binary. | +| testImage.registry | string | `nil` | Image registry | +| testImage.repository | string | `"busybox"` | Image repository | +| testImage.tag | string | `nil` | Image tag Defaults to `latest` if omitted | +| testImage.pullPolicy | string | `nil` | Image pull policy Defaults to image.pullPolicy if omitted | +| replicaCount | int | `nil` | Desired number of pods | +| podLabels | object | `{}` | Additional labels to add to each pod | +| podAnnotations | object | `{}` | Additional annotations to add to each pod | +| podSecurityContext | object | `{}` | Security context for the pod | +| securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"privileged":false,"readOnlyRootFilesystem":true,"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}}` | Security context for the containers | +| testSecurityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"privileged":false,"readOnlyRootFilesystem":true,"runAsGroup":65534,"runAsNonRoot":true,"runAsUser":65534,"seccompProfile":{"type":"RuntimeDefault"}}` | Security context for the test containers | +| priorityClassName | string | `""` | Optional priority class to be used for kyverno pods | +| antiAffinity.enable | bool | `true` | Pod antiAffinities toggle. Enabled by default but can be disabled if you want to schedule pods to the same node. | +| podAntiAffinity | object | See [values.yaml](values.yaml) | Pod anti affinity constraints. | +| podAffinity | object | `{}` | Pod affinity constraints. | +| nodeAffinity | object | `{}` | Node affinity constraints. | +| podDisruptionBudget.minAvailable | int | `1` | Configures the minimum available pods for kyverno disruptions. Cannot be used if `maxUnavailable` is set. | +| podDisruptionBudget.maxUnavailable | string | `nil` | Configures the maximum unavailable pods for kyverno disruptions. Cannot be used if `minAvailable` is set. | +| nodeSelector | object | `{}` | Node labels for pod assignment | +| tolerations | list | `[]` | List of node taints to tolerate | +| hostNetwork | bool | `false` | Change `hostNetwork` to `true` when you want the kyverno's pod to share its host's network namespace. Useful for situations like when you end up dealing with a custom CNI over Amazon EKS. Update the `dnsPolicy` accordingly as well to suit the host network mode. | +| dnsPolicy | string | `"ClusterFirst"` | `dnsPolicy` determines the manner in which DNS resolution happens in the cluster. In case of `hostNetwork: true`, usually, the `dnsPolicy` is suitable to be `ClusterFirstWithHostNet`. For further reference: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy. | +| envVarsInit | object | `{}` | Env variables for initContainers. | +| envVars | object | `{}` | Env variables for containers. | +| extraArgs | list | `["--autogenInternals=true","--loggingFormat=text"]` | Extra arguments to give to the binary. | +| extraInitContainers | list | `[]` | Array of extra init containers | +| extraContainers | list | `[]` | Array of extra containers to run alongside kyverno | +| imagePullSecrets | object | `{}` | Image pull secrets for image verify and imageData policies. This will define the `--imagePullSecrets` Kyverno argument. | +| resources.limits | object | `{"memory":"384Mi"}` | Pod resource limits | +| resources.requests | object | `{"cpu":"100m","memory":"128Mi"}` | Pod resource requests | +| initResources.limits | object | `{"cpu":"100m","memory":"256Mi"}` | Pod resource limits | +| initResources.requests | object | `{"cpu":"10m","memory":"64Mi"}` | Pod resource requests | +| testResources.limits | object | `{"cpu":"100m","memory":"256Mi"}` | Pod resource limits | +| testResources.requests | object | `{"cpu":"10m","memory":"64Mi"}` | Pod resource requests | +| startupProbe | object | See [values.yaml](values.yaml) | Startup probe. The block is directly forwarded into the deployment, so you can use whatever startupProbes configuration you want. ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ | +| livenessProbe | object | See [values.yaml](values.yaml) | Liveness probe. The block is directly forwarded into the deployment, so you can use whatever livenessProbe configuration you want. ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ | +| readinessProbe | object | See [values.yaml](values.yaml) | Readiness Probe. The block is directly forwarded into the deployment, so you can use whatever readinessProbe configuration you want. ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ | +| generatecontrollerExtraResources | list | `[]` | Additional resources to be added to controller RBAC permissions. | +| excludeKyvernoNamespace | bool | `true` | Exclude Kyverno namespace Determines if default Kyverno namespace exclusion is enabled for webhooks and resourceFilters | +| resourceFiltersExcludeNamespaces | list | `[]` | resourceFilter namespace exclude Namespaces to exclude from the default resourceFilters | +| config.resourceFilters | list | See [values.yaml](values.yaml) | Resource types to be skipped by the Kyverno policy engine. Make sure to surround each entry in quotes so that it doesn't get parsed as a nested YAML list. These are joined together without spaces, run through `tpl`, and the result is set in the config map. | +| config.existingConfig | string | `""` | Name of an existing config map (ignores default/provided resourceFilters) | +| config.annotations | object | `{}` | Additional annotations to add to the configmap | +| config.excludeGroupRole | string | `nil` | Exclude group role | +| config.excludeUsername | string | `nil` | Exclude username | +| config.webhooks | string | `nil` | Defines the `namespaceSelector` in the webhook configurations. Note that it takes a list of `namespaceSelector` and/or `objectSelector` in the JSON format, and only the first element will be forwarded to the webhook configurations. The Kyverno namespace is excluded if `excludeKyvernoNamespace` is `true` (default) | +| config.generateSuccessEvents | bool | `false` | Generate success events. | +| config.metricsConfig | object | `{"annotations":{},"namespaces":{"exclude":[],"include":[]}}` | Metrics config. | +| config.metricsConfig.annotations | object | `{}` | Additional annotations to add to the metricsconfigmap | +| updateStrategy | object | See [values.yaml](values.yaml) | Deployment update strategy. Ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy | +| service.port | int | `443` | Service port. | +| service.type | string | `"ClusterIP"` | Service type. | +| service.nodePort | string | `nil` | Service node port. Only used if `service.type` is `NodePort`. | +| service.annotations | object | `{}` | Service annotations. | +| topologySpreadConstraints | list | `[]` | Topology spread constraints. | +| metricsService.create | bool | `true` | Create service. | +| metricsService.port | int | `8000` | Service port. Kyverno's metrics server will be exposed at this port. | +| metricsService.type | string | `"ClusterIP"` | Service type. | +| metricsService.nodePort | string | `nil` | Service node port. Only used if `metricsService.type` is `NodePort`. | +| metricsService.annotations | object | `{}` | Service annotations. | +| serviceMonitor.enabled | bool | `false` | Create a `ServiceMonitor` to collect Prometheus metrics. | +| serviceMonitor.additionalLabels | string | `nil` | Additional labels | +| serviceMonitor.namespace | string | `nil` | Override namespace (default is the same as kyverno) | +| serviceMonitor.interval | string | `"30s"` | Interval to scrape metrics | +| serviceMonitor.scrapeTimeout | string | `"25s"` | Timeout if metrics can't be retrieved in given time interval | +| serviceMonitor.secure | bool | `false` | Is TLS required for endpoint | +| serviceMonitor.tlsConfig | object | `{}` | TLS Configuration for endpoint | +| createSelfSignedCert | bool | `false` | Kyverno requires a certificate key pair and corresponding certificate authority to properly register its webhooks. This can be done in one of 3 ways: 1) Use kube-controller-manager to generate a CA-signed certificate (preferred) 2) Provide your own CA and cert. In this case, you will need to create a certificate with a specific name and data structure. As long as you follow the naming scheme, it will be automatically picked up. kyverno-svc.(namespace).svc.kyverno-tls-ca (with data entries named tls.key and tls.crt) kyverno-svc.kyverno.svc.kyverno-tls-pair (with data entries named tls.key and tls.crt) 3) Let Helm generate a self signed cert, by setting createSelfSignedCert true If letting Kyverno create its own CA or providing your own, make createSelfSignedCert is false | +| installCRDs | bool | `true` | Whether to have Helm install the Kyverno CRDs. If the CRDs are not installed by Helm, they must be added before policies can be created. | +| crds.annotations | object | `{}` | Additional CRDs annotations. | +| networkPolicy.enabled | bool | `false` | When true, use a NetworkPolicy to allow ingress to the webhook This is useful on clusters using Calico and/or native k8s network policies in a default-deny setup. | +| networkPolicy.ingressFrom | list | `[]` | A list of valid from selectors according to https://kubernetes.io/docs/concepts/services-networking/network-policies. | +| webhooksCleanup.enable | bool | `false` | Create a helm pre-delete hook to cleanup webhooks. | +| webhooksCleanup.image | string | `"bitnami/kubectl:latest"` | `kubectl` image to run commands for deleting webhooks. | +| tufRootMountPath | string | `"/.sigstore"` | A writable volume to use for the TUF root initialization. | +| grafana.enabled | bool | `false` | Enable grafana dashboard creation. | +| grafana.namespace | string | `nil` | Namespace to create the grafana dashboard configmap. If not set, it will be created in the same namespace where the chart is deployed. | +| grafana.annotations | object | `{}` | Grafana dashboard configmap annotations. | + +## TLS Configuration + +If `createSelfSignedCert` is `true`, Helm will take care of the steps of creating an external self-signed certificate described in option 2 of the [installation documentation](https://kyverno.io/docs/installation/#option-2-use-your-own-ca-signed-certificate) + +If `createSelfSignedCert` is `false`, Kyverno will generate a self-signed CA and a certificate, or you can provide your own TLS CA and signed-key pair and create the secret yourself as described in the [documentation](https://kyverno.io/docs/installation/#customize-the-installation-of-kyverno). + +## Default resource filters + +[Kyverno resource filters](https://kyverno.io/docs/installation/#resource-filters) are a used to exclude resources from the Kyverno engine rules processing. + +This chart comes with default resource filters that apply exclusions on a couple of namespaces and resource kinds: +- all resources in `kube-system`, `kube-public` and `kube-node-lease` namespaces +- all resources in all namespaces for the following resource kinds: + - `Event` + - `Node` + - `APIService` + - `TokenReview` + - `SubjectAccessReview` + - `SelfSubjectAccessReview` + - `Binding` + - `ReplicaSet` + - `AdmissionReport` + - `ClusterAdmissionReport` + - `BackgroundScanReport` + - `ClusterBackgroundScanReport` +- all resources created by this chart itself + +Those default exclusions are there to prevent disruptions as much as possible. +Under the hood, Kyverno installs an admission controller for critical cluster resources. +A cluster can become unresponsive if Kyverno is not up and running, ultimately preventing pods to be scheduled in the cluster. + +You can however override the default resource filters by setting the `config.resourceFilters` stanza. +It contains an array of string templates that are passed through the `tpl` Helm function and joined together to produce the final `resourceFilters` written in the Kyverno config map. + +Please consult the [values.yaml](./values.yaml) file before overriding `config.resourceFilters` and use the apropriate templates to build your desired exclusions list. + +## High availability + +Running a highly-available Kyverno installation is crucial in a production environment. + +In order to run Kyverno in high availability mode, you should set `replicaCount` to `3` or more. +You should also pay attention to anti affinity rules, spreading pods across nodes and availability zones. + +Please see https://kyverno.io/docs/installation/#security-vs-operability for more informations. + +## Source Code + +* <https://github.com/kyverno/kyverno> + +## Requirements + +Kubernetes: `>=1.16.0-0` + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| Nirmata | | <https://kyverno.io/> | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/scripts/helmcharts/toolings/charts/kyverno/grafana/dashboard.json b/scripts/helmcharts/toolings/charts/kyverno/grafana/dashboard.json new file mode 100644 index 000000000..f98d01124 --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/grafana/dashboard.json @@ -0,0 +1,2854 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS_KYVERNO", + "label": "Prometheus Data Source exposing Kyverno's metrics", + "description": "Prometheus Data Source exposing Kyverno's metrics", + "type": "datasource" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 2, + "iteration": 1628375170149, + "links": [], + "panels": [ + { + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 42, + "options": { + "content": "# Kyverno\nA Kubernetes-native policy management engine\n\n#### About this dashboard\n\nThis dashboard represents generic insights that can be extracted from a cluster with Kyverno running.\n\n#### For more details around the metrics\n\nCheckout the [official docs of Kyverno metrics](https://kyverno.io/docs/monitoring/)", + "mode": "markdown" + }, + "pluginVersion": "8.1.0", + "timeFrom": null, + "timeShift": null, + "transparent": true, + "type": "text" + }, + { + "collapsed": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 6 + }, + "id": 12, + "panels": [], + "title": "Latest Status", + "type": "row" + }, + { + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "red", + "value": 50 + }, + { + "color": "#EAB839", + "value": 75 + }, + { + "color": "green", + "value": 100 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 7 + }, + "id": 29, + "options": { + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "8.1.0", + "targets": [ + { + "exemplar": true, + "expr": "sum(delta(kyverno_policy_results_total{rule_result=\"fail\"}[24h]))*100/sum(delta(kyverno_policy_results_total{}[24h]))", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Rule Execution Failure Rate (Last 24 Hours)", + "transparent": true, + "type": "gauge" + }, + { + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 8, + "y": 7 + }, + "id": 2, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.1.0", + "targets": [ + { + "exemplar": true, + "expr": "count(count(kyverno_policy_rule_info_total{policy_type=\"cluster\"}==1) by (policy_name))", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Cluster Policies", + "type": "stat" + }, + { + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 12, + "y": 7 + }, + "id": 3, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.1.0", + "targets": [ + { + "exemplar": true, + "expr": "count(count(kyverno_policy_rule_info_total{policy_type=\"namespaced\"}==1) by (policy_name))", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Policies", + "type": "stat" + }, + { + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "red", + "value": 50 + }, + { + "color": "#EAB839", + "value": 75 + }, + { + "color": "green", + "value": 100 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 7 + }, + "id": 28, + "options": { + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "8.1.0", + "targets": [ + { + "exemplar": true, + "expr": "sum(delta(kyverno_policy_results_total{rule_result=\"fail\", policy_background_mode=\"true\"}[24h]))*100/sum(delta(kyverno_policy_results_total{policy_background_mode=\"true\"}[24h]))", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Background Scans Failure Rate (Last 24 Hours)", + "transparent": true, + "type": "gauge" + }, + { + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 6, + "y": 12 + }, + "id": 4, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.1.0", + "targets": [ + { + "exemplar": true, + "expr": "count(kyverno_policy_rule_info_total{rule_type=\"validate\"}==1)", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Validate Rules", + "type": "stat" + }, + { + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 10, + "y": 12 + }, + "id": 23, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.1.0", + "targets": [ + { + "exemplar": true, + "expr": "count(kyverno_policy_rule_info_total{rule_type=\"mutate\"}==1)", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Mutate Rules", + "type": "stat" + }, + { + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 14, + "y": 12 + }, + "id": 6, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.1.0", + "targets": [ + { + "exemplar": true, + "expr": "count(kyverno_policy_rule_info_total{rule_type=\"generate\"}==1)", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Generate Rules", + "type": "stat" + }, + { + "collapsed": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 26, + "panels": [], + "title": "Policy-Rule Results", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 17 + }, + "hiddenSeries": false, + "id": 15, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:2021", + "alias": "pass", + "color": "rgb(43, 219, 23)", + "dashes": true + }, + { + "$$hashKey": "object:2029", + "alias": "fail", + "color": "#F2495C" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(delta(kyverno_policy_results_total{rule_execution_cause=\"admission_request\"}[5m])) by (rule_result)", + "interval": "", + "legendFormat": "{{rule_result}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Admission Review Results (per-rule)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:218", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:219", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 17 + }, + "hiddenSeries": false, + "id": 17, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:2021", + "alias": "pass", + "color": "rgb(43, 219, 23)", + "dashes": true + }, + { + "$$hashKey": "object:2029", + "alias": "fail", + "color": "#F2495C" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(delta(kyverno_policy_results_total{rule_execution_cause=\"background_scan\"}[5m])) by (rule_result)", + "interval": "", + "legendFormat": "{{rule_result}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Background Scan Results (per-rule)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:218", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:219", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 16, + "w": 8, + "x": 16, + "y": 17 + }, + "hiddenSeries": false, + "id": 30, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:2021", + "alias": "cluster", + "color": "#5794F2", + "dashes": true + }, + { + "$$hashKey": "object:2029", + "alias": "namespaced", + "color": "#F2495C", + "dashes": true + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(sum(delta(kyverno_policy_results_total{rule_result=\"fail\"}[5m])) by (policy_name, policy_type)) by (policy_type)", + "interval": "", + "legendFormat": "{{policy_type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Policy Failures", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:218", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:219", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 25 + }, + "hiddenSeries": false, + "id": 31, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:2021", + "alias": "pass", + "color": "rgb(43, 219, 23)", + "dashes": true + }, + { + "$$hashKey": "object:2029", + "alias": "fail", + "color": "#F2495C" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(sum(delta(kyverno_policy_results_total{rule_execution_cause=\"admission_request\"}[5m])) by (policy_name, rule_result)) by (rule_result)", + "interval": "", + "legendFormat": "{{rule_result}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Admission Review Results (per-policy)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:218", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:219", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 25 + }, + "hiddenSeries": false, + "id": 32, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:2021", + "alias": "pass", + "color": "rgb(43, 219, 23)", + "dashes": true + }, + { + "$$hashKey": "object:2029", + "alias": "fail", + "color": "#F2495C" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(sum(delta(kyverno_policy_results_total{rule_execution_cause=\"background_scan\"}[5m])) by (policy_name, rule_result)) by (rule_result)", + "interval": "", + "legendFormat": "{{rule_result}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Background Scan Results (per-policy)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:218", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:219", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 33 + }, + "id": 19, + "panels": [], + "title": "Policy-Rule Info", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 34 + }, + "hiddenSeries": false, + "id": 16, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:3795", + "alias": "cluster", + "color": "#5794F2" + }, + { + "$$hashKey": "object:3800", + "alias": "namespaced", + "color": "#FF7383" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "count(count(kyverno_policy_rule_info_total{}==1) by (policy_name, policy_type)) by (policy_type)", + "interval": "", + "legendFormat": "{{policy_type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Active Policies (by policy type)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:218", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:219", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 34 + }, + "hiddenSeries": false, + "id": 20, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:3319", + "alias": "audit", + "color": "#37872D" + }, + { + "$$hashKey": "object:3335", + "alias": "enforce", + "color": "#FF9830" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "count(count(kyverno_policy_rule_info_total{}==1) by (policy_name, policy_validation_mode)) by (policy_validation_mode)", + "interval": "", + "legendFormat": "audit", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Active Policies (by policy validation action)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:218", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:219", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 34 + }, + "hiddenSeries": false, + "id": 24, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:3934", + "alias": "cluster", + "color": "#B877D9" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "count(count(kyverno_policy_rule_info_total{policy_background_mode=\"true\"}==1) by (policy_name, policy_type)) by (policy_type)", + "interval": "", + "legendFormat": "{{policy_type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Active Policies running in background mode", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:218", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:219", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 42 + }, + "hiddenSeries": false, + "id": 21, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "count(count(kyverno_policy_rule_info_total{policy_namespace!=\"-\"}==1) by (policy_name, policy_namespace)) by (policy_namespace)", + "interval": "", + "legendFormat": "{{policy_namespace}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Active Namespaced Policies (by namespaces)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:218", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:219", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 10, + "x": 8, + "y": 42 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:3021", + "alias": "mutate", + "color": "rgb(169, 58, 227)" + }, + { + "$$hashKey": "object:3029", + "alias": "validate", + "color": "rgb(255, 232, 0)" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "count(kyverno_policy_rule_info_total{}==1) by (rule_type)", + "interval": "", + "legendFormat": "{{rule_type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Active Rules (by rule type)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:218", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:219", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 50 + }, + "id": 34, + "panels": [], + "title": "Policy-Rule Execution Latency", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 9, + "x": 0, + "y": 51 + }, + "hiddenSeries": false, + "id": 36, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(kyverno_policy_execution_duration_seconds_sum{}[5m])) by (rule_type) / sum(rate(kyverno_policy_execution_duration_seconds_count{}[5m])) by (rule_type)", + "interval": "", + "legendFormat": "{{rule_type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Average Rule Execution Latency Over Time", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:5548", + "format": "s", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:5549", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 9, + "x": 9, + "y": 51 + }, + "hiddenSeries": false, + "id": 37, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:5526", + "alias": "cluster", + "color": "#5794F2" + }, + { + "$$hashKey": "object:5534", + "alias": "namespaced", + "color": "#F2495C" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(kyverno_policy_execution_duration_seconds_sum{}[5m])) by (policy_type) / sum(rate(kyverno_policy_execution_duration_seconds_count{}[5m])) by (policy_type)", + "interval": "", + "legendFormat": "{{policy_type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Average Policy Execution Latency Over Time", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:5548", + "format": "clocks", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:5549", + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 51 + }, + "id": 39, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.1.0", + "targets": [ + { + "exemplar": true, + "expr": "sum(kyverno_policy_execution_duration_seconds_sum{}) / sum(kyverno_policy_execution_duration_seconds_count{})", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Overall Average Rule Execution Latency", + "type": "stat" + }, + { + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 55 + }, + "id": 40, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.1.0", + "targets": [ + { + "exemplar": true, + "expr": "avg(sum(kyverno_policy_execution_duration_seconds_sum{}) by (policy_name, policy_type) / sum(kyverno_policy_execution_duration_seconds_count{}) by (policy_name, policy_type))", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Overall Average Policy Execution Latency", + "type": "stat" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 59 + }, + "id": 52, + "panels": [], + "title": "Admission Review Latency", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 9, + "x": 0, + "y": 60 + }, + "hiddenSeries": false, + "id": 53, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(kyverno_admission_review_duration_seconds_sum{}[5m])) by (resource_request_operation) / sum(rate(kyverno_admission_review_duration_seconds_count{}[5m])) by (resource_request_operation)", + "interval": "", + "legendFormat": "Resource Operation: {{resource_request_operation}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Avg - Admission Review Duration Over Time (by operation)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transparent": true, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:218", + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:219", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 9, + "x": 9, + "y": 60 + }, + "hiddenSeries": false, + "id": 54, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(kyverno_admission_review_duration_seconds_sum{}[5m])) by (resource_kind) / sum(rate(kyverno_admission_review_duration_seconds_count{}[5m])) by (resource_kind)", + "interval": "", + "legendFormat": "Resource Kind: {{resource_kind}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Avg - Admission Review Duration Over Time (by resource kind)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transparent": true, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:218", + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:219", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 60 + }, + "id": 50, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.1.0", + "targets": [ + { + "exemplar": true, + "expr": "sum(delta(kyverno_admission_requests_total{}[5m]))", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Rate - Incoming Admission Requests (per 5m)", + "type": "stat" + }, + { + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 64 + }, + "id": 55, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.1.0", + "targets": [ + { + "exemplar": true, + "expr": "sum(kyverno_admission_review_duration_seconds_sum{})/sum(kyverno_admission_review_duration_seconds_count{})", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Avg - Overall Admission Review Duration", + "type": "stat" + }, + { + "collapsed": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 68 + }, + "id": 8, + "panels": [], + "title": "Policy Changes", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 9, + "x": 0, + "y": 69 + }, + "hiddenSeries": false, + "id": 10, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:1487", + "alias": "Change type: created", + "color": "#5794F2" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(delta(kyverno_policy_changes_total{}[5m])) by (policy_change_type)", + "interval": "", + "legendFormat": "Change type: {{policy_change_type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Policy Changes Over Time (by change type)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transparent": true, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:218", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:219", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 9, + "x": 9, + "y": 69 + }, + "hiddenSeries": false, + "id": 13, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:1679", + "alias": "cluster", + "color": "#F2495C" + }, + { + "$$hashKey": "object:1769" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(delta(kyverno_policy_changes_total{}[5m])) by (policy_type)", + "interval": "", + "legendFormat": "{{policy_type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Policy Changes Over Time (by policy type)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:218", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:219", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "orange", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 69 + }, + "id": 49, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.1.0", + "targets": [ + { + "exemplar": true, + "expr": "sum(delta(kyverno_policy_changes_total{}[24h]))", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Total Policy Changes (Last 24 Hours)", + "type": "stat" + }, + { + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 73 + }, + "id": 48, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.1.0", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(kyverno_policy_changes_total{}[5m]))", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Rate - Policy Changes Happening (last 5m)", + "type": "stat" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 77 + }, + "id": 44, + "panels": [], + "title": "Admission Requests", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 9, + "x": 0, + "y": 78 + }, + "hiddenSeries": false, + "id": 45, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:1487", + "alias": "Change type: created", + "color": "#5794F2" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(delta(kyverno_admission_requests_total{}[5m])) by (resource_request_operation)", + "interval": "", + "legendFormat": "Resource Operation: {{resource_request_operation}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Admission Requests (by operation)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transparent": true, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:218", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:219", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 9, + "x": 9, + "y": 78 + }, + "hiddenSeries": false, + "id": 46, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:1487", + "alias": "Change type: created", + "color": "#5794F2" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(delta(kyverno_admission_requests_total{}[5m])) by (resource_kind)", + "interval": "", + "legendFormat": "Resource Kind: {{resource_kind}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Admission Requests (by resource kind)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transparent": true, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:218", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:219", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "datasource": "${DS_PROMETHEUS_KYVERNO}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "semi-dark-green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 78 + }, + "id": 47, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.1.0", + "targets": [ + { + "exemplar": true, + "expr": "sum(delta(kyverno_admission_requests_total{}[24h]))", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Total Admission Requests (Last 24 Hours)", + "type": "stat" + } + ], + "refresh": false, + "schemaVersion": 30, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "hide": 0, + "label": "datasource", + "name": "DS_PROMETHEUS_KYVERNO", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "type": "datasource" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Kyverno", + "folder": "Kyverno", + "uid": "Rg8lWBG7k", + "version": "1.4.3" +} \ No newline at end of file diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/NOTES.txt b/scripts/helmcharts/toolings/charts/kyverno/templates/NOTES.txt new file mode 100644 index 000000000..7955e169e --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/NOTES.txt @@ -0,0 +1,16 @@ +Chart version: {{ .Chart.Version }} +Kyverno version: {{ default .Chart.AppVersion (default .Values.image.tag .Values.initImage.tag) }} + +Thank you for installing {{ .Chart.Name }}! Your release is named {{ .Release.Name }}. + +{{- if not .Values.replicaCount }} +⚠️ WARNING: Setting replicas count below 3 means Kyverno is not running in high availability mode. +{{- else if lt (int .Values.replicaCount) 3 }} +⚠️ WARNING: Setting replicas count below 3 means Kyverno is not running in high availability mode. +{{- end }} + +💡 Note: There is a trade-off when deciding which approach to take regarding Namespace exclusions. Please see the documentation at https://kyverno.io/docs/installation/#security-vs-operability to understand the risks. + +{{- if semverCompare "<1.21.0" .Capabilities.KubeVersion.Version }} +⚠️ WARNING: The minimal Kubernetes version officially supported by Kyverno is 1.21. Earlier versions are untested and Kyverno is not guaranteed to work with Kubernetes {{ .Capabilities.KubeVersion.Version }}. +{{- end }} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/_helpers.tpl b/scripts/helmcharts/toolings/charts/kyverno/templates/_helpers.tpl new file mode 100644 index 000000000..5d577b3e7 --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/_helpers.tpl @@ -0,0 +1,167 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* Expand the name of the chart. */}} +{{- define "kyverno.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "kyverno.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* Create chart name and version as used by the chart label. */}} +{{- define "kyverno.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* Helm required labels */}} +{{- define "kyverno.labels" -}} +app.kubernetes.io/component: kyverno +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/name: {{ template "kyverno.name" . }} +app.kubernetes.io/part-of: {{ template "kyverno.name" . }} +app.kubernetes.io/version: "{{ .Chart.Version }}" +helm.sh/chart: {{ template "kyverno.chart" . }} +{{- if .Values.customLabels }} +{{ toYaml .Values.customLabels }} +{{- end }} +{{- end -}} + +{{/* Helm required labels */}} +{{- define "kyverno.test-labels" -}} +app.kubernetes.io/component: kyverno +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/name: {{ template "kyverno.name" . }}-test +app.kubernetes.io/part-of: {{ template "kyverno.name" . }} +app.kubernetes.io/version: "{{ .Chart.Version }}" +helm.sh/chart: {{ template "kyverno.chart" . }} +{{- end -}} + +{{/* matchLabels */}} +{{- define "kyverno.matchLabels" -}} +app.kubernetes.io/name: {{ template "kyverno.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{/* Get the config map name. */}} +{{- define "kyverno.configMapName" -}} +{{- printf "%s" (default (include "kyverno.fullname" .) .Values.config.existingConfig) -}} +{{- end -}} + +{{/* Get the metrics config map name. */}} +{{- define "kyverno.metricsConfigMapName" -}} +{{- printf "%s" (default (printf "%s-metrics" (include "kyverno.fullname" .)) .Values.config.existingMetricsConfig) -}} +{{- end -}} + +{{/* Get the namespace name. */}} +{{- define "kyverno.namespace" -}} +{{- if .Values.namespace -}} + {{- .Values.namespace -}} +{{- else -}} + {{- .Release.Namespace -}} +{{- end -}} +{{- end -}} + +{{/* Create the name of the service to use */}} +{{- define "kyverno.serviceName" -}} +{{- printf "%s-svc" (include "kyverno.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* Create the name of the service account to use */}} +{{- define "kyverno.serviceAccountName" -}} +{{- if .Values.rbac.serviceAccount.create -}} + {{ default (include "kyverno.fullname" .) .Values.rbac.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.rbac.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* Create the default PodDisruptionBudget to use */}} +{{- define "podDisruptionBudget.spec" -}} +{{- if and .Values.podDisruptionBudget.minAvailable .Values.podDisruptionBudget.maxUnavailable }} +{{- fail "Cannot set both .Values.podDisruptionBudget.minAvailable and .Values.podDisruptionBudget.maxUnavailable" -}} +{{- end }} +{{- if not .Values.podDisruptionBudget.maxUnavailable }} +minAvailable: {{ default 1 .Values.podDisruptionBudget.minAvailable }} +{{- end }} +{{- if .Values.podDisruptionBudget.maxUnavailable }} +maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} +{{- end }} +{{- end }} + +{{- define "kyverno.securityContext" -}} +{{- if semverCompare "<1.19" .Capabilities.KubeVersion.Version }} +{{ toYaml (omit .Values.securityContext "seccompProfile") }} +{{- else }} +{{ toYaml .Values.securityContext }} +{{- end }} +{{- end }} + +{{- define "kyverno.testSecurityContext" -}} +{{- if semverCompare "<1.19" .Capabilities.KubeVersion.Version }} +{{ toYaml (omit .Values.testSecurityContext "seccompProfile") }} +{{- else }} +{{ toYaml .Values.testSecurityContext }} +{{- end }} +{{- end }} + +{{- define "kyverno.imagePullSecret" }} +{{- printf "{\"auths\":{\"%s\":{\"auth\":\"%s\"}}}" .registry (printf "%s:%s" .username .password | b64enc) | b64enc }} +{{- end }} + +{{- define "kyverno.image" -}} + {{- if .image.registry -}} +{{ .image.registry }}/{{ required "An image repository is required" .image.repository }}:{{ default .defaultTag .image.tag }} + {{- else -}} +{{ required "An image repository is required" .image.repository }}:{{ default .defaultTag .image.tag }} + {{- end -}} +{{- end }} + +{{- define "kyverno.resourceFilters" -}} +{{- $resourceFilters := .Values.config.resourceFilters }} +{{- if .Values.excludeKyvernoNamespace }} + {{- $resourceFilters = prepend .Values.config.resourceFilters (printf "[*,%s,*]" (include "kyverno.namespace" .)) }} +{{- end }} +{{- range $exclude := .Values.resourceFiltersExcludeNamespaces }} + {{- range $filter := $resourceFilters }} + {{- if (contains (printf ",%s," $exclude) $filter) }} + {{- $resourceFilters = without $resourceFilters $filter }} + {{- end }} + {{- end }} +{{- end }} +{{- tpl (join "" $resourceFilters) . }} +{{- end }} + +{{- define "kyverno.webhooks" -}} +{{- $excludeDefault := dict "key" "kubernetes.io/metadata.name" "operator" "NotIn" "values" (list (include "kyverno.namespace" .)) }} +{{- $newWebhook := list }} +{{- range $webhook := .Values.config.webhooks }} + {{- $namespaceSelector := default dict $webhook.namespaceSelector }} + {{- $matchExpressions := default list $namespaceSelector.matchExpressions }} + {{- $newNamespaceSelector := dict "matchLabels" $namespaceSelector.matchLabels "matchExpressions" (append $matchExpressions $excludeDefault) }} + {{- $newWebhook = append $newWebhook (merge (omit $webhook "namespaceSelector") (dict "namespaceSelector" $newNamespaceSelector)) }} +{{- end }} +{{- $newWebhook | toJson }} +{{- end }} + +{{- define "kyverno.crdAnnotations" -}} +{{- range $key, $value := .Values.crds.annotations }} +{{ $key }}: {{ $value | quote }} +{{- end }} +{{- end }} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/aggregateroles.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/aggregateroles.yaml new file mode 100644 index 000000000..282e8e561 --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/aggregateroles.yaml @@ -0,0 +1,111 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "kyverno.fullname" . }}:admin-policies + labels: {{ include "kyverno.labels" . | nindent 4 }} + rbac.authorization.k8s.io/aggregate-to-admin: "true" + app: kyverno +rules: +- apiGroups: + - kyverno.io + resources: + - policies + - clusterpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: {{ include "kyverno.labels" . | nindent 4 }} + rbac.authorization.k8s.io/aggregate-to-admin: "true" + app: kyverno + name: {{ template "kyverno.fullname" . }}:admin-policyreport +rules: + - apiGroups: + - wgpolicyk8s.io + resources: + - policyreports + - clusterpolicyreports + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: {{ include "kyverno.labels" . | nindent 4 }} + rbac.authorization.k8s.io/aggregate-to-admin: "true" + app: kyverno + name: {{ template "kyverno.fullname" . }}:admin-reports +rules: +- apiGroups: + - kyverno.io + resources: + - admissionreports + - clusteradmissionreports + - backgroundscanreports + - clusterbackgroundscanreports + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: {{ include "kyverno.labels" . | nindent 4 }} + rbac.authorization.k8s.io/aggregate-to-admin: "true" + app: kyverno + name: {{ template "kyverno.fullname" . }}:admin-generaterequest +rules: +- apiGroups: + - kyverno.io + resources: + - generaterequests + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: {{ include "kyverno.labels" . | nindent 4 }} + rbac.authorization.k8s.io/aggregate-to-admin: "true" + app: kyverno + name: {{ template "kyverno.fullname" . }}:admin-updaterequest +rules: +- apiGroups: + - kyverno.io + resources: + - updaterequests + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +{{- end }} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/clusterrole.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/clusterrole.yaml new file mode 100644 index 000000000..4c48b9681 --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/clusterrole.yaml @@ -0,0 +1,188 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "kyverno.fullname" . }} + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno +aggregationRule: + clusterRoleSelectors: + - matchLabels: + {{- include "kyverno.matchLabels" . | nindent 6 }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "kyverno.fullname" . }}:userinfo + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno +rules: +- apiGroups: + - "rbac.authorization.k8s.io" + resources: + - roles + - clusterroles + - rolebindings + - clusterrolebindings + verbs: + - watch + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "kyverno.fullname" . }}:policies + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno +rules: +- apiGroups: + - kyverno.io + resources: + - policies + - policies/status + - clusterpolicies + - clusterpolicies/status + - generaterequests + - generaterequests/status + - updaterequests + - updaterequests/status + - admissionreports + - clusteradmissionreports + - backgroundscanreports + - clusterbackgroundscanreports + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - deletecollection +- apiGroups: + - wgpolicyk8s.io + resources: + - policyreports + - policyreports/status + - clusterpolicyreports + - clusterpolicyreports/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - deletecollection + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "kyverno.fullname" . }}:view + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno +rules: +- apiGroups: + - '*' + resources: + - '*' + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "kyverno.fullname" . }}:generate + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno +rules: + - apiGroups: + - networking.k8s.io + resources: + - ingresses + - ingressclasses + - networkpolicies + verbs: + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - namespaces + - configmaps + - secrets + - resourcequotas + - limitranges + verbs: + - create + - update + - patch + - delete + - apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + - roles + verbs: + - create + - update + - patch + - delete + {{- if .Values.generatecontrollerExtraResources }} + - apiGroups: + - "*" + resources: + {{- range .Values.generatecontrollerExtraResources }} + - {{ . }} + {{- end }} + verbs: + - create + - update + - delete + {{- end }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "kyverno.fullname" . }}:events + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno +rules: +- apiGroups: + - "*" + resources: + - events + verbs: + - create + - update + - patch + - delete +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "kyverno.fullname" . }}:webhook + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno +rules: +- apiGroups: + - 'admissionregistration.k8s.io' + resources: + - mutatingwebhookconfigurations + - validatingwebhookconfigurations + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + +{{- end }} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/clusterrolebinding.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..2ef768436 --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/clusterrolebinding.yaml @@ -0,0 +1,16 @@ +{{- if .Values.rbac.create -}} +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ template "kyverno.fullname" . }} + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "kyverno.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ template "kyverno.serviceAccountName" . }} + namespace: {{ template "kyverno.namespace" . }} +{{- end -}} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/configmap.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/configmap.yaml new file mode 100644 index 000000000..4aa280a4f --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/configmap.yaml @@ -0,0 +1,34 @@ +{{- if (not .Values.config.existingConfig) }} +apiVersion: v1 +kind: ConfigMap +metadata: + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno + {{- with .Values.config.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ template "kyverno.configMapName" . }} + namespace: {{ template "kyverno.namespace" . }} +data: + # resource types to be skipped by kyverno policy engine + {{- if .Values.config.resourceFilters }} + resourceFilters: {{ include "kyverno.resourceFilters" . | quote }} + {{- end -}} + {{- if .Values.config.excludeGroupRole }} + excludeGroupRole: {{ join "," .Values.config.excludeGroupRole | quote }} + {{- end -}} + {{- if .Values.config.excludeUsername }} + excludeUsername: {{ join "," .Values.config.excludeUsername | quote }} + {{- end -}} + {{- if and .Values.config.webhooks .Values.excludeKyvernoNamespace }} + webhooks: {{ include "kyverno.webhooks" . | quote }} + {{- else if .Values.config.webhooks }} + webhooks: {{ .Values.config.webhooks | toJson | quote }} + {{- else if .Values.excludeKyvernoNamespace }} + webhooks: '[{"namespaceSelector": {"matchExpressions": [{"key":"kubernetes.io/metadata.name","operator":"NotIn","values":["{{ include "kyverno.namespace" . }}"]}]}}]' + {{- end -}} + {{- if .Values.config.generateSuccessEvents }} + generateSuccessEvents: {{ .Values.config.generateSuccessEvents | quote }} + {{- end -}} +{{- end -}} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/crds.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/crds.yaml new file mode 100644 index 000000000..ccb34655e --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/crds.yaml @@ -0,0 +1,16706 @@ +{{- if .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + config.kubernetes.io/index: '1' + internal.config.kubernetes.io/index: '1' + {{- trim (include "kyverno.crdAnnotations" .) | nindent 4 }} + creationTimestamp: null + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: kyverno + app.kubernetes.io/name: kyverno + app.kubernetes.io/part-of: kyverno + app.kubernetes.io/version: '{{.Chart.AppVersion}}' + name: admissionreports.kyverno.io +spec: + group: kyverno.io + names: + categories: + - kyverno + kind: AdmissionReport + listKind: AdmissionReportList + plural: admissionreports + shortNames: + - admr + singular: admissionreport + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.ownerReferences[0].apiVersion + name: ApiVersion + priority: 1 + type: string + - jsonPath: .metadata.ownerReferences[0].kind + name: Kind + priority: 1 + type: string + - jsonPath: .metadata.ownerReferences[0].name + name: Subject + priority: 1 + type: string + - jsonPath: .spec.summary.pass + name: Pass + type: integer + - jsonPath: .spec.summary.fail + name: Fail + type: integer + - jsonPath: .spec.summary.warn + name: Warn + type: integer + - jsonPath: .spec.summary.error + name: Error + type: integer + - jsonPath: .spec.summary.skip + name: Skip + type: integer + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .metadata.labels['audit\.kyverno\.io/resource\.hash'] + name: Hash + priority: 1 + type: string + - jsonPath: .metadata.labels['audit\.kyverno\.io/report\.aggregate'] + name: AGGREGATE + priority: 1 + type: string + name: v1alpha2 + schema: + openAPIV3Schema: + description: AdmissionReport is the Schema for the AdmissionReports API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + owner: + description: Owner is a reference to the report owner (e.g. a Deployment, Namespace, or Node) + properties: + apiVersion: + description: API version of the referent. + type: string + blockOwnerDeletion: + description: If true, AND if the owner has the "foregroundDeletion" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion for how the garbage collector interacts with this field and enforces the foreground deletion. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned. + type: boolean + controller: + description: If true, this reference points to the managing controller. + type: boolean + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' + type: string + uid: + description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' + type: string + required: + - apiVersion + - kind + - name + - uid + type: object + x-kubernetes-map-type: atomic + results: + description: PolicyReportResult provides result details + items: + description: PolicyReportResult provides the result for an individual policy + properties: + category: + description: Category indicates policy category + type: string + message: + description: Description is a short user friendly message for the policy rule + type: string + policy: + description: Policy is the name or identifier of the policy + type: string + properties: + additionalProperties: + type: string + description: Properties provides additional information for the policy rule + type: object + resourceSelector: + description: SubjectSelector is an optional label selector for checked Kubernetes resources. For example, a policy result may apply to all pods that match a label. Either a Subject or a SubjectSelector can be specified. If neither are provided, the result is assumed to be for the policy report scope. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + resources: + description: Subjects is an optional reference to the checked Kubernetes resources + items: + description: "ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, \"must refer only to types A and B\" or \"UID not honored\" or \"name must be restricted\". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. \n Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 ." + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + type: object + x-kubernetes-map-type: atomic + type: array + result: + description: Result indicates the outcome of the policy rule execution + enum: + - pass + - fail + - warn + - error + - skip + type: string + rule: + description: Rule is the name or identifier of the rule within the policy + type: string + scored: + description: Scored indicates if this result is scored + type: boolean + severity: + description: Severity indicates policy check result criticality + enum: + - critical + - high + - low + - medium + - info + type: string + source: + description: Source is an identifier for the policy engine that manages this report + type: string + timestamp: + description: Timestamp indicates the time the result was found + properties: + nanos: + description: Non-negative fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values that count forward in time. Must be from 0 to 999,999,999 inclusive. This field may be limited in precision depending on context. + format: int32 + type: integer + seconds: + description: Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive. + format: int64 + type: integer + required: + - nanos + - seconds + type: object + required: + - policy + type: object + type: array + summary: + description: PolicyReportSummary provides a summary of results + properties: + error: + description: Error provides the count of policies that could not be evaluated + type: integer + fail: + description: Fail provides the count of policies whose requirements were not met + type: integer + pass: + description: Pass provides the count of policies whose requirements were met + type: integer + skip: + description: Skip indicates the count of policies that were not selected for evaluation + type: integer + warn: + description: Warn provides the count of non-scored policies whose requirements were not met + type: integer + type: object + required: + - owner + type: object + required: + - spec + type: object + served: true + storage: true + subresources: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + config.kubernetes.io/index: '2' + internal.config.kubernetes.io/index: '2' + {{- trim (include "kyverno.crdAnnotations" .) | nindent 4 }} + creationTimestamp: null + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: kyverno + app.kubernetes.io/name: kyverno + app.kubernetes.io/part-of: kyverno + app.kubernetes.io/version: '{{.Chart.AppVersion}}' + name: backgroundscanreports.kyverno.io +spec: + group: kyverno.io + names: + categories: + - kyverno + kind: BackgroundScanReport + listKind: BackgroundScanReportList + plural: backgroundscanreports + shortNames: + - bgscanr + singular: backgroundscanreport + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.ownerReferences[0].apiVersion + name: ApiVersion + priority: 1 + type: string + - jsonPath: .metadata.ownerReferences[0].kind + name: Kind + priority: 1 + type: string + - jsonPath: .metadata.ownerReferences[0].name + name: Subject + priority: 1 + type: string + - jsonPath: .spec.summary.pass + name: Pass + type: integer + - jsonPath: .spec.summary.fail + name: Fail + type: integer + - jsonPath: .spec.summary.warn + name: Warn + type: integer + - jsonPath: .spec.summary.error + name: Error + type: integer + - jsonPath: .spec.summary.skip + name: Skip + type: integer + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .metadata.labels['audit\.kyverno\.io/resource\.hash'] + name: Hash + priority: 1 + type: string + name: v1alpha2 + schema: + openAPIV3Schema: + description: BackgroundScanReport is the Schema for the BackgroundScanReports API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + results: + description: PolicyReportResult provides result details + items: + description: PolicyReportResult provides the result for an individual policy + properties: + category: + description: Category indicates policy category + type: string + message: + description: Description is a short user friendly message for the policy rule + type: string + policy: + description: Policy is the name or identifier of the policy + type: string + properties: + additionalProperties: + type: string + description: Properties provides additional information for the policy rule + type: object + resourceSelector: + description: SubjectSelector is an optional label selector for checked Kubernetes resources. For example, a policy result may apply to all pods that match a label. Either a Subject or a SubjectSelector can be specified. If neither are provided, the result is assumed to be for the policy report scope. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + resources: + description: Subjects is an optional reference to the checked Kubernetes resources + items: + description: "ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, \"must refer only to types A and B\" or \"UID not honored\" or \"name must be restricted\". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. \n Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 ." + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + type: object + x-kubernetes-map-type: atomic + type: array + result: + description: Result indicates the outcome of the policy rule execution + enum: + - pass + - fail + - warn + - error + - skip + type: string + rule: + description: Rule is the name or identifier of the rule within the policy + type: string + scored: + description: Scored indicates if this result is scored + type: boolean + severity: + description: Severity indicates policy check result criticality + enum: + - critical + - high + - low + - medium + - info + type: string + source: + description: Source is an identifier for the policy engine that manages this report + type: string + timestamp: + description: Timestamp indicates the time the result was found + properties: + nanos: + description: Non-negative fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values that count forward in time. Must be from 0 to 999,999,999 inclusive. This field may be limited in precision depending on context. + format: int32 + type: integer + seconds: + description: Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive. + format: int64 + type: integer + required: + - nanos + - seconds + type: object + required: + - policy + type: object + type: array + summary: + description: PolicyReportSummary provides a summary of results + properties: + error: + description: Error provides the count of policies that could not be evaluated + type: integer + fail: + description: Fail provides the count of policies whose requirements were not met + type: integer + pass: + description: Pass provides the count of policies whose requirements were met + type: integer + skip: + description: Skip indicates the count of policies that were not selected for evaluation + type: integer + warn: + description: Warn provides the count of non-scored policies whose requirements were not met + type: integer + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + config.kubernetes.io/index: '3' + internal.config.kubernetes.io/index: '3' + {{- trim (include "kyverno.crdAnnotations" .) | nindent 4 }} + creationTimestamp: null + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: kyverno + app.kubernetes.io/name: kyverno + app.kubernetes.io/part-of: kyverno + app.kubernetes.io/version: '{{.Chart.AppVersion}}' + name: clusteradmissionreports.kyverno.io +spec: + group: kyverno.io + names: + categories: + - kyverno + kind: ClusterAdmissionReport + listKind: ClusterAdmissionReportList + plural: clusteradmissionreports + shortNames: + - cadmr + singular: clusteradmissionreport + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.ownerReferences[0].apiVersion + name: ApiVersion + priority: 1 + type: string + - jsonPath: .metadata.ownerReferences[0].kind + name: Kind + priority: 1 + type: string + - jsonPath: .metadata.ownerReferences[0].name + name: Subject + priority: 1 + type: string + - jsonPath: .spec.summary.pass + name: Pass + type: integer + - jsonPath: .spec.summary.fail + name: Fail + type: integer + - jsonPath: .spec.summary.warn + name: Warn + type: integer + - jsonPath: .spec.summary.error + name: Error + type: integer + - jsonPath: .spec.summary.skip + name: Skip + type: integer + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .metadata.labels['audit\.kyverno\.io/resource\.hash'] + name: Hash + priority: 1 + type: string + - jsonPath: .metadata.labels['audit\.kyverno\.io/report\.aggregate'] + name: AGGREGATE + priority: 1 + type: string + name: v1alpha2 + schema: + openAPIV3Schema: + description: ClusterAdmissionReport is the Schema for the ClusterAdmissionReports API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + owner: + description: Owner is a reference to the report owner (e.g. a Deployment, Namespace, or Node) + properties: + apiVersion: + description: API version of the referent. + type: string + blockOwnerDeletion: + description: If true, AND if the owner has the "foregroundDeletion" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion for how the garbage collector interacts with this field and enforces the foreground deletion. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned. + type: boolean + controller: + description: If true, this reference points to the managing controller. + type: boolean + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' + type: string + uid: + description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' + type: string + required: + - apiVersion + - kind + - name + - uid + type: object + x-kubernetes-map-type: atomic + results: + description: PolicyReportResult provides result details + items: + description: PolicyReportResult provides the result for an individual policy + properties: + category: + description: Category indicates policy category + type: string + message: + description: Description is a short user friendly message for the policy rule + type: string + policy: + description: Policy is the name or identifier of the policy + type: string + properties: + additionalProperties: + type: string + description: Properties provides additional information for the policy rule + type: object + resourceSelector: + description: SubjectSelector is an optional label selector for checked Kubernetes resources. For example, a policy result may apply to all pods that match a label. Either a Subject or a SubjectSelector can be specified. If neither are provided, the result is assumed to be for the policy report scope. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + resources: + description: Subjects is an optional reference to the checked Kubernetes resources + items: + description: "ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, \"must refer only to types A and B\" or \"UID not honored\" or \"name must be restricted\". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. \n Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 ." + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + type: object + x-kubernetes-map-type: atomic + type: array + result: + description: Result indicates the outcome of the policy rule execution + enum: + - pass + - fail + - warn + - error + - skip + type: string + rule: + description: Rule is the name or identifier of the rule within the policy + type: string + scored: + description: Scored indicates if this result is scored + type: boolean + severity: + description: Severity indicates policy check result criticality + enum: + - critical + - high + - low + - medium + - info + type: string + source: + description: Source is an identifier for the policy engine that manages this report + type: string + timestamp: + description: Timestamp indicates the time the result was found + properties: + nanos: + description: Non-negative fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values that count forward in time. Must be from 0 to 999,999,999 inclusive. This field may be limited in precision depending on context. + format: int32 + type: integer + seconds: + description: Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive. + format: int64 + type: integer + required: + - nanos + - seconds + type: object + required: + - policy + type: object + type: array + summary: + description: PolicyReportSummary provides a summary of results + properties: + error: + description: Error provides the count of policies that could not be evaluated + type: integer + fail: + description: Fail provides the count of policies whose requirements were not met + type: integer + pass: + description: Pass provides the count of policies whose requirements were met + type: integer + skip: + description: Skip indicates the count of policies that were not selected for evaluation + type: integer + warn: + description: Warn provides the count of non-scored policies whose requirements were not met + type: integer + type: object + required: + - owner + type: object + required: + - spec + type: object + served: true + storage: true + subresources: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + config.kubernetes.io/index: '4' + internal.config.kubernetes.io/index: '4' + {{- trim (include "kyverno.crdAnnotations" .) | nindent 4 }} + creationTimestamp: null + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: kyverno + app.kubernetes.io/name: kyverno + app.kubernetes.io/part-of: kyverno + app.kubernetes.io/version: '{{.Chart.AppVersion}}' + name: clusterbackgroundscanreports.kyverno.io +spec: + group: kyverno.io + names: + categories: + - kyverno + kind: ClusterBackgroundScanReport + listKind: ClusterBackgroundScanReportList + plural: clusterbackgroundscanreports + shortNames: + - cbgscanr + singular: clusterbackgroundscanreport + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.ownerReferences[0].apiVersion + name: ApiVersion + priority: 1 + type: string + - jsonPath: .metadata.ownerReferences[0].kind + name: Kind + priority: 1 + type: string + - jsonPath: .metadata.ownerReferences[0].name + name: Subject + priority: 1 + type: string + - jsonPath: .spec.summary.pass + name: Pass + type: integer + - jsonPath: .spec.summary.fail + name: Fail + type: integer + - jsonPath: .spec.summary.warn + name: Warn + type: integer + - jsonPath: .spec.summary.error + name: Error + type: integer + - jsonPath: .spec.summary.skip + name: Skip + type: integer + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .metadata.labels['audit\.kyverno\.io/resource\.hash'] + name: Hash + priority: 1 + type: string + name: v1alpha2 + schema: + openAPIV3Schema: + description: ClusterBackgroundScanReport is the Schema for the ClusterBackgroundScanReports API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + results: + description: PolicyReportResult provides result details + items: + description: PolicyReportResult provides the result for an individual policy + properties: + category: + description: Category indicates policy category + type: string + message: + description: Description is a short user friendly message for the policy rule + type: string + policy: + description: Policy is the name or identifier of the policy + type: string + properties: + additionalProperties: + type: string + description: Properties provides additional information for the policy rule + type: object + resourceSelector: + description: SubjectSelector is an optional label selector for checked Kubernetes resources. For example, a policy result may apply to all pods that match a label. Either a Subject or a SubjectSelector can be specified. If neither are provided, the result is assumed to be for the policy report scope. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + resources: + description: Subjects is an optional reference to the checked Kubernetes resources + items: + description: "ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, \"must refer only to types A and B\" or \"UID not honored\" or \"name must be restricted\". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. \n Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 ." + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + type: object + x-kubernetes-map-type: atomic + type: array + result: + description: Result indicates the outcome of the policy rule execution + enum: + - pass + - fail + - warn + - error + - skip + type: string + rule: + description: Rule is the name or identifier of the rule within the policy + type: string + scored: + description: Scored indicates if this result is scored + type: boolean + severity: + description: Severity indicates policy check result criticality + enum: + - critical + - high + - low + - medium + - info + type: string + source: + description: Source is an identifier for the policy engine that manages this report + type: string + timestamp: + description: Timestamp indicates the time the result was found + properties: + nanos: + description: Non-negative fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values that count forward in time. Must be from 0 to 999,999,999 inclusive. This field may be limited in precision depending on context. + format: int32 + type: integer + seconds: + description: Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive. + format: int64 + type: integer + required: + - nanos + - seconds + type: object + required: + - policy + type: object + type: array + summary: + description: PolicyReportSummary provides a summary of results + properties: + error: + description: Error provides the count of policies that could not be evaluated + type: integer + fail: + description: Fail provides the count of policies whose requirements were not met + type: integer + pass: + description: Pass provides the count of policies whose requirements were met + type: integer + skip: + description: Skip indicates the count of policies that were not selected for evaluation + type: integer + warn: + description: Warn provides the count of non-scored policies whose requirements were not met + type: integer + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + config.kubernetes.io/index: '5' + internal.config.kubernetes.io/index: '5' + {{- trim (include "kyverno.crdAnnotations" .) | nindent 4 }} + creationTimestamp: null + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: kyverno + app.kubernetes.io/name: kyverno + app.kubernetes.io/part-of: kyverno + app.kubernetes.io/version: '{{.Chart.AppVersion}}' + name: clusterpolicies.kyverno.io +spec: + group: kyverno.io + names: + categories: + - kyverno + kind: ClusterPolicy + listKind: ClusterPolicyList + plural: clusterpolicies + shortNames: + - cpol + singular: clusterpolicy + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.background + name: Background + type: boolean + - jsonPath: .spec.validationFailureAction + name: Validate Action + type: string + - jsonPath: .spec.failurePolicy + name: Failure Policy + priority: 1 + type: string + - jsonPath: .status.ready + name: Ready + type: boolean + name: v1 + schema: + openAPIV3Schema: + description: ClusterPolicy declares validation, mutation, and generation behaviors for matching resources. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec declares policy behaviors. + properties: + applyRules: + description: ApplyRules controls how rules in a policy are applied. Rule are processed in the order of declaration. When set to `One` processing stops after a rule has been applied i.e. the rule matches and results in a pass, fail, or error. When set to `All` all rules in the policy are processed. The default is `All`. + enum: + - All + - One + type: string + background: + default: true + description: Background controls if rules are applied to existing resources during a background scan. Optional. Default value is "true". The value must be set to "false" if the policy rule uses variables that are only available in the admission review request (e.g. user name). + type: boolean + failurePolicy: + description: FailurePolicy defines how unexpected policy errors and webhook response timeout errors are handled. Rules within the same policy share the same failure behavior. Allowed values are Ignore or Fail. Defaults to Fail. + enum: + - Ignore + - Fail + type: string + generateExistingOnPolicyUpdate: + description: GenerateExistingOnPolicyUpdate controls whether to trigger generate rule in existing resources If is set to "true" generate rule will be triggered and applied to existing matched resources. Defaults to "false" if not specified. + type: boolean + mutateExistingOnPolicyUpdate: + description: MutateExistingOnPolicyUpdate controls if a mutateExisting policy is applied on policy events. Default value is "false". + type: boolean + rules: + description: Rules is a list of Rule instances. A Policy contains multiple rules and each rule can validate, mutate, or generate resources. + items: + description: Rule defines a validation, mutation, or generation control for matching resources. Each rules contains a match declaration to select resources, and an optional exclude declaration to specify which resources to exclude. + properties: + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + exclude: + description: ExcludeResources defines when this policy rule should not be applied. The exclude criteria can include resource information (e.g. kind, name, namespace, labels) and admission review request information like the name or role. + properties: + all: + description: All allows specifying resources which will be ANDed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + any: + description: Any allows specifying resources which will be ORed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources. Specifying ResourceDescription directly under match is being deprecated. Please specify under "any" or "all" instead. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + generate: + description: Generation is used to create new resources. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + clone: + description: Clone specifies the source resource used to populate each generated resource. At most one of Data or Clone can be specified. If neither are provided, the generated resource will be created with default data only. + properties: + name: + description: Name specifies name of the resource. + type: string + namespace: + description: Namespace specifies source resource namespace. + type: string + type: object + cloneList: + description: CloneList specifies the list of source resource used to populate each generated resource. + properties: + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + namespace: + description: Namespace specifies source resource namespace. + type: string + selector: + description: Selector is a label selector. Label keys and values in `matchLabels`. wildcard characters are not supported. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + data: + description: Data provides the resource declaration used to populate each generated resource. At most one of Data or Clone must be specified. If neither are provided, the generated resource will be created with default data only. + x-kubernetes-preserve-unknown-fields: true + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + synchronize: + description: Synchronize controls if generated resources should be kept in-sync with their source resource. If Synchronize is set to "true" changes to generated resources will be overwritten with resource data from Data or the resource specified in the Clone declaration. Optional. Defaults to "false" if not specified. + type: boolean + type: object + imageExtractors: + additionalProperties: + items: + properties: + key: + description: Key is an optional name of the field within 'path' that will be used to uniquely identify an image. Note - this field MUST be unique. + type: string + name: + description: Name is the entry the image will be available under 'images.<name>' in the context. If this field is not defined, image entries will appear under 'images.custom'. + type: string + path: + description: Path is the path to the object containing the image field in a custom resource. It should be slash-separated. Each slash-separated key must be a valid YAML key or a wildcard '*'. Wildcard keys are expanded in case of arrays or objects. + type: string + value: + description: Value is an optional name of the field within 'path' that points to the image URI. This is useful when a custom 'key' is also defined. + type: string + required: + - path + type: object + type: array + description: ImageExtractors defines a mapping from kinds to ImageExtractorConfigs. This config is only valid for verifyImages rules. + type: object + match: + description: MatchResources defines when this policy rule should be applied. The match criteria can include resource information (e.g. kind, name, namespace, labels) and admission review request information like the user name or role. At least one kind is required. + properties: + all: + description: All allows specifying resources which will be ANDed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + any: + description: Any allows specifying resources which will be ORed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources. Specifying ResourceDescription directly under match is being deprecated. Please specify under "any" or "all" instead. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + mutate: + description: Mutation is used to modify matching resources. + properties: + foreach: + description: ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + items: + description: ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + properties: + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + list: + description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied. + type: string + patchStrategicMerge: + description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. + x-kubernetes-preserve-unknown-fields: true + patchesJson6902: + description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. + type: string + preconditions: + description: 'AnyAllConditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + patchStrategicMerge: + description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. + x-kubernetes-preserve-unknown-fields: true + patchesJson6902: + description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. + type: string + targets: + description: Targets defines the target resources to be mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array + type: object + name: + description: Name is a label to identify the rule, It must be unique within the policy. + maxLength: 63 + type: string + preconditions: + description: 'Preconditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. A direct list of conditions (without `any` or `all` statements is supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/preconditions/' + x-kubernetes-preserve-unknown-fields: true + validate: + description: Validation is used to validate matching resources. + properties: + anyPattern: + description: AnyPattern specifies list of validation patterns. At least one of the patterns must be satisfied for the validation rule to succeed. + x-kubernetes-preserve-unknown-fields: true + deny: + description: Deny defines conditions used to pass or fail a validation rule. + properties: + conditions: + description: 'Multiple conditions can be declared under an `any` or `all` statement. A direct list of conditions (without `any` or `all` statements) is also supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules' + x-kubernetes-preserve-unknown-fields: true + type: object + foreach: + description: ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + items: + description: ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + properties: + anyPattern: + description: AnyPattern specifies list of validation patterns. At least one of the patterns must be satisfied for the validation rule to succeed. + x-kubernetes-preserve-unknown-fields: true + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + deny: + description: Deny defines conditions used to pass or fail a validation rule. + properties: + conditions: + description: 'Multiple conditions can be declared under an `any` or `all` statement. A direct list of conditions (without `any` or `all` statements) is also supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules' + x-kubernetes-preserve-unknown-fields: true + type: object + elementScope: + description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree. + type: boolean + list: + description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied. + type: string + pattern: + description: Pattern specifies an overlay-style pattern used to check resources. + x-kubernetes-preserve-unknown-fields: true + preconditions: + description: 'AnyAllConditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + manifests: + description: Manifest specifies conditions for manifest verification + properties: + annotationDomain: + description: AnnotationDomain is custom domain of annotation for message and signature. Default is "cosign.sigstore.dev". + type: string + attestors: + description: Attestors specified the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + dryRun: + description: DryRun configuration + properties: + enable: + type: boolean + namespace: + type: string + type: object + ignoreFields: + description: Fields which will be ignored while comparing manifests. + items: + properties: + fields: + items: + type: string + type: array + objects: + items: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + type: object + type: array + type: object + type: array + repository: + description: Repository is an optional alternate OCI repository to use for resource bundle reference. The repository can be overridden per Attestor or Attestation. + type: string + type: object + message: + description: Message specifies a custom message to be displayed on failure. + type: string + pattern: + description: Pattern specifies an overlay-style pattern used to check resources. + x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes Pod Security admission by specifying exclusions for Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard controls to be excluded. + items: + description: PodSecurityStandard specifies the Pod Security Standard controls to be excluded. + properties: + controlName: + description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user + type: string + images: + description: 'Images selects matching containers and applies the container level PSS. Each image is the image name consisting of the registry address, repository, image, and tag. Empty list matches no containers, PSS checks are applied at the pod level only. Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.' + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard level to be applied to workloads. Allowed values are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard versions that Kubernetes supports. Allowed values are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object + type: object + verifyImages: + description: VerifyImages is used to verify image signatures and mutate them to add a digest + items: + description: ImageVerification validates that images that match the specified pattern are signed with the supplied public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. Deprecated. + type: object + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. Deprecated. Use annotations per Attestor instead. + type: object + attestations: + description: Attestations are optional checks for signed in-toto Statements used to verify the image. See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the OCI registry and decodes them into a list of Statement declarations. + items: + description: Attestation are checks for signed in-toto Statements that are used to verify the image. See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the OCI registry and decodes them into a list of Statements. + properties: + attestors: + description: Attestors specify the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + conditions: + description: Conditions are used to verify attributes within a Predicate. If no Conditions are specified the attestation check is satisfied as long there are predicates that match the predicate type. + items: + description: AnyAllConditions consists of conditions wrapped denoting a logical criteria to be fulfilled. AnyConditions get fulfilled when at least one of its sub-conditions passes. AllConditions get fulfilled only when all of its sub-conditions pass. + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + type: array + predicateType: + description: PredicateType defines the type of Predicate contained within the Statement. + type: string + required: + - predicateType + type: object + type: array + attestors: + description: Attestors specified the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + image: + description: 'Image is the image name consisting of the registry address, repository, image, and tag. Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images. Deprecated. Use ImageReferences instead.' + type: string + imageReferences: + description: 'ImageReferences is a list of matching image reference patterns. At least one pattern in the list must match the image for the rule to apply. Each image reference consists of a registry address (defaults to docker.io), repository, image, and tag (defaults to latest). Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.' + items: + type: string + type: array + issuer: + description: Issuer is the certificate issuer used for keyless signing. Deprecated. Use KeylessAttestor instead. + type: string + key: + description: Key is the PEM encoded public key that the image or attestation is signed with. Deprecated. Use StaticKeyAttestor instead. + type: string + mutateDigest: + default: true + description: MutateDigest enables replacement of image tags with digests. Defaults to true. + type: boolean + repository: + description: Repository is an optional alternate OCI repository to use for image signatures and attestations that match this rule. If specified Repository will override the default OCI image repository configured for the installation. The repository can also be overridden per Attestor or Attestation. + type: string + required: + default: true + description: Required validates that images are verified i.e. have matched passed a signature or attestation check. + type: boolean + roots: + description: Roots is the PEM encoded Root certificate chain used for keyless signing Deprecated. Use KeylessAttestor instead. + type: string + subject: + description: Subject is the identity used for keyless signing, for example an email address Deprecated. Use KeylessAttestor instead. + type: string + verifyDigest: + default: true + description: VerifyDigest validates that images have a digest. + type: boolean + type: object + type: array + type: object + type: array + schemaValidation: + description: SchemaValidation skips validation checks for policies as well as patched resources. Optional. The default value is set to "true", it must be set to "false" to disable the validation checks. + type: boolean + validationFailureAction: + default: audit + description: ValidationFailureAction defines if a validation policy rule violation should block the admission review request (enforce), or allow (audit) the admission review request and report an error in a policy report. Optional. Allowed values are audit or enforce. The default value is "audit". + enum: + - audit + - enforce + type: string + validationFailureActionOverrides: + description: ValidationFailureActionOverrides is a Cluster Policy attribute that specifies ValidationFailureAction namespace-wise. It overrides ValidationFailureAction for the specified namespaces. + items: + properties: + action: + description: ValidationFailureAction defines the policy validation failure action + enum: + - audit + - enforce + type: string + namespaces: + items: + type: string + type: array + type: object + type: array + webhookTimeoutSeconds: + description: WebhookTimeoutSeconds specifies the maximum time in seconds allowed to apply this policy. After the configured time expires, the admission request may fail, or may simply ignore the policy results, based on the failure policy. The default timeout is 10s, the value must be between 1 and 30 seconds. + format: int32 + type: integer + type: object + status: + description: Status contains policy runtime data. + properties: + autogen: + description: Autogen contains autogen status information + properties: + rules: + description: Rules is a list of Rule instances. It contains auto generated rules added for pod controllers + items: + description: Rule defines a validation, mutation, or generation control for matching resources. Each rules contains a match declaration to select resources, and an optional exclude declaration to specify which resources to exclude. + properties: + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + exclude: + description: ExcludeResources defines when this policy rule should not be applied. The exclude criteria can include resource information (e.g. kind, name, namespace, labels) and admission review request information like the name or role. + properties: + all: + description: All allows specifying resources which will be ANDed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + any: + description: Any allows specifying resources which will be ORed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources. Specifying ResourceDescription directly under match is being deprecated. Please specify under "any" or "all" instead. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + generate: + description: Generation is used to create new resources. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + clone: + description: Clone specifies the source resource used to populate each generated resource. At most one of Data or Clone can be specified. If neither are provided, the generated resource will be created with default data only. + properties: + name: + description: Name specifies name of the resource. + type: string + namespace: + description: Namespace specifies source resource namespace. + type: string + type: object + cloneList: + description: CloneList specifies the list of source resource used to populate each generated resource. + properties: + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + namespace: + description: Namespace specifies source resource namespace. + type: string + selector: + description: Selector is a label selector. Label keys and values in `matchLabels`. wildcard characters are not supported. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + data: + description: Data provides the resource declaration used to populate each generated resource. At most one of Data or Clone must be specified. If neither are provided, the generated resource will be created with default data only. + x-kubernetes-preserve-unknown-fields: true + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + synchronize: + description: Synchronize controls if generated resources should be kept in-sync with their source resource. If Synchronize is set to "true" changes to generated resources will be overwritten with resource data from Data or the resource specified in the Clone declaration. Optional. Defaults to "false" if not specified. + type: boolean + type: object + imageExtractors: + additionalProperties: + items: + properties: + key: + description: Key is an optional name of the field within 'path' that will be used to uniquely identify an image. Note - this field MUST be unique. + type: string + name: + description: Name is the entry the image will be available under 'images.<name>' in the context. If this field is not defined, image entries will appear under 'images.custom'. + type: string + path: + description: Path is the path to the object containing the image field in a custom resource. It should be slash-separated. Each slash-separated key must be a valid YAML key or a wildcard '*'. Wildcard keys are expanded in case of arrays or objects. + type: string + value: + description: Value is an optional name of the field within 'path' that points to the image URI. This is useful when a custom 'key' is also defined. + type: string + required: + - path + type: object + type: array + description: ImageExtractors defines a mapping from kinds to ImageExtractorConfigs. This config is only valid for verifyImages rules. + type: object + match: + description: MatchResources defines when this policy rule should be applied. The match criteria can include resource information (e.g. kind, name, namespace, labels) and admission review request information like the user name or role. At least one kind is required. + properties: + all: + description: All allows specifying resources which will be ANDed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + any: + description: Any allows specifying resources which will be ORed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources. Specifying ResourceDescription directly under match is being deprecated. Please specify under "any" or "all" instead. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + mutate: + description: Mutation is used to modify matching resources. + properties: + foreach: + description: ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + items: + description: ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + properties: + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + list: + description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied. + type: string + patchStrategicMerge: + description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. + x-kubernetes-preserve-unknown-fields: true + patchesJson6902: + description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. + type: string + preconditions: + description: 'AnyAllConditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + patchStrategicMerge: + description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. + x-kubernetes-preserve-unknown-fields: true + patchesJson6902: + description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. + type: string + targets: + description: Targets defines the target resources to be mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array + type: object + name: + description: Name is a label to identify the rule, It must be unique within the policy. + maxLength: 63 + type: string + preconditions: + description: 'Preconditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. A direct list of conditions (without `any` or `all` statements is supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/preconditions/' + x-kubernetes-preserve-unknown-fields: true + validate: + description: Validation is used to validate matching resources. + properties: + anyPattern: + description: AnyPattern specifies list of validation patterns. At least one of the patterns must be satisfied for the validation rule to succeed. + x-kubernetes-preserve-unknown-fields: true + deny: + description: Deny defines conditions used to pass or fail a validation rule. + properties: + conditions: + description: 'Multiple conditions can be declared under an `any` or `all` statement. A direct list of conditions (without `any` or `all` statements) is also supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules' + x-kubernetes-preserve-unknown-fields: true + type: object + foreach: + description: ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + items: + description: ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + properties: + anyPattern: + description: AnyPattern specifies list of validation patterns. At least one of the patterns must be satisfied for the validation rule to succeed. + x-kubernetes-preserve-unknown-fields: true + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + deny: + description: Deny defines conditions used to pass or fail a validation rule. + properties: + conditions: + description: 'Multiple conditions can be declared under an `any` or `all` statement. A direct list of conditions (without `any` or `all` statements) is also supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules' + x-kubernetes-preserve-unknown-fields: true + type: object + elementScope: + description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree. + type: boolean + list: + description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied. + type: string + pattern: + description: Pattern specifies an overlay-style pattern used to check resources. + x-kubernetes-preserve-unknown-fields: true + preconditions: + description: 'AnyAllConditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + manifests: + description: Manifest specifies conditions for manifest verification + properties: + annotationDomain: + description: AnnotationDomain is custom domain of annotation for message and signature. Default is "cosign.sigstore.dev". + type: string + attestors: + description: Attestors specified the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + dryRun: + description: DryRun configuration + properties: + enable: + type: boolean + namespace: + type: string + type: object + ignoreFields: + description: Fields which will be ignored while comparing manifests. + items: + properties: + fields: + items: + type: string + type: array + objects: + items: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + type: object + type: array + type: object + type: array + repository: + description: Repository is an optional alternate OCI repository to use for resource bundle reference. The repository can be overridden per Attestor or Attestation. + type: string + type: object + message: + description: Message specifies a custom message to be displayed on failure. + type: string + pattern: + description: Pattern specifies an overlay-style pattern used to check resources. + x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes Pod Security admission by specifying exclusions for Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard controls to be excluded. + items: + description: PodSecurityStandard specifies the Pod Security Standard controls to be excluded. + properties: + controlName: + description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user + type: string + images: + description: 'Images selects matching containers and applies the container level PSS. Each image is the image name consisting of the registry address, repository, image, and tag. Empty list matches no containers, PSS checks are applied at the pod level only. Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.' + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard level to be applied to workloads. Allowed values are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard versions that Kubernetes supports. Allowed values are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object + type: object + verifyImages: + description: VerifyImages is used to verify image signatures and mutate them to add a digest + items: + description: ImageVerification validates that images that match the specified pattern are signed with the supplied public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. Deprecated. + type: object + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. Deprecated. Use annotations per Attestor instead. + type: object + attestations: + description: Attestations are optional checks for signed in-toto Statements used to verify the image. See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the OCI registry and decodes them into a list of Statement declarations. + items: + description: Attestation are checks for signed in-toto Statements that are used to verify the image. See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the OCI registry and decodes them into a list of Statements. + properties: + attestors: + description: Attestors specify the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + conditions: + description: Conditions are used to verify attributes within a Predicate. If no Conditions are specified the attestation check is satisfied as long there are predicates that match the predicate type. + items: + description: AnyAllConditions consists of conditions wrapped denoting a logical criteria to be fulfilled. AnyConditions get fulfilled when at least one of its sub-conditions passes. AllConditions get fulfilled only when all of its sub-conditions pass. + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + type: array + predicateType: + description: PredicateType defines the type of Predicate contained within the Statement. + type: string + required: + - predicateType + type: object + type: array + attestors: + description: Attestors specified the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + image: + description: 'Image is the image name consisting of the registry address, repository, image, and tag. Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images. Deprecated. Use ImageReferences instead.' + type: string + imageReferences: + description: 'ImageReferences is a list of matching image reference patterns. At least one pattern in the list must match the image for the rule to apply. Each image reference consists of a registry address (defaults to docker.io), repository, image, and tag (defaults to latest). Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.' + items: + type: string + type: array + issuer: + description: Issuer is the certificate issuer used for keyless signing. Deprecated. Use KeylessAttestor instead. + type: string + key: + description: Key is the PEM encoded public key that the image or attestation is signed with. Deprecated. Use StaticKeyAttestor instead. + type: string + mutateDigest: + default: true + description: MutateDigest enables replacement of image tags with digests. Defaults to true. + type: boolean + repository: + description: Repository is an optional alternate OCI repository to use for image signatures and attestations that match this rule. If specified Repository will override the default OCI image repository configured for the installation. The repository can also be overridden per Attestor or Attestation. + type: string + required: + default: true + description: Required validates that images are verified i.e. have matched passed a signature or attestation check. + type: boolean + roots: + description: Roots is the PEM encoded Root certificate chain used for keyless signing Deprecated. Use KeylessAttestor instead. + type: string + subject: + description: Subject is the identity used for keyless signing, for example an email address Deprecated. Use KeylessAttestor instead. + type: string + verifyDigest: + default: true + description: VerifyDigest validates that images have a digest. + type: boolean + type: object + type: array + type: object + type: array + type: object + conditions: + description: Conditions is a list of conditions that apply to the policy + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + ready: + description: Ready indicates if the policy is ready to serve the admission request. Deprecated in favor of Conditions + type: boolean + required: + - ready + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.background + name: Background + type: boolean + - jsonPath: .spec.validationFailureAction + name: Validate Action + type: string + - jsonPath: .spec.failurePolicy + name: Failure Policy + priority: 1 + type: string + - jsonPath: .status.ready + name: Ready + type: boolean + name: v2beta1 + schema: + openAPIV3Schema: + description: ClusterPolicy declares validation, mutation, and generation behaviors for matching resources. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec declares policy behaviors. + properties: + applyRules: + description: ApplyRules controls how rules in a policy are applied. Rule are processed in the order of declaration. When set to `One` processing stops after a rule has been applied i.e. the rule matches and results in a pass, fail, or error. When set to `All` all rules in the policy are processed. The default is `All`. + enum: + - All + - One + type: string + background: + default: true + description: Background controls if rules are applied to existing resources during a background scan. Optional. Default value is "true". The value must be set to "false" if the policy rule uses variables that are only available in the admission review request (e.g. user name). + type: boolean + failurePolicy: + description: FailurePolicy defines how unexpected policy errors and webhook response timeout errors are handled. Rules within the same policy share the same failure behavior. Allowed values are Ignore or Fail. Defaults to Fail. + enum: + - Ignore + - Fail + type: string + generateExistingOnPolicyUpdate: + description: GenerateExistingOnPolicyUpdate controls whether to trigger generate rule in existing resources If is set to "true" generate rule will be triggered and applied to existing matched resources. Defaults to "false" if not specified. + type: boolean + mutateExistingOnPolicyUpdate: + description: MutateExistingOnPolicyUpdate controls if a mutateExisting policy is applied on policy events. Default value is "false". + type: boolean + rules: + description: Rules is a list of Rule instances. A Policy contains multiple rules and each rule can validate, mutate, or generate resources. + items: + description: Rule defines a validation, mutation, or generation control for matching resources. Each rules contains a match declaration to select resources, and an optional exclude declaration to specify which resources to exclude. + properties: + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + exclude: + description: ExcludeResources defines when this policy rule should not be applied. The exclude criteria can include resource information (e.g. kind, name, namespace, labels) and admission review request information like the name or role. + properties: + all: + description: All allows specifying resources which will be ANDed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + any: + description: Any allows specifying resources which will be ORed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + type: object + generate: + description: Generation is used to create new resources. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + clone: + description: Clone specifies the source resource used to populate each generated resource. At most one of Data or Clone can be specified. If neither are provided, the generated resource will be created with default data only. + properties: + name: + description: Name specifies name of the resource. + type: string + namespace: + description: Namespace specifies source resource namespace. + type: string + type: object + cloneList: + description: CloneList specifies the list of source resource used to populate each generated resource. + properties: + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + namespace: + description: Namespace specifies source resource namespace. + type: string + selector: + description: Selector is a label selector. Label keys and values in `matchLabels`. wildcard characters are not supported. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + data: + description: Data provides the resource declaration used to populate each generated resource. At most one of Data or Clone must be specified. If neither are provided, the generated resource will be created with default data only. + x-kubernetes-preserve-unknown-fields: true + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + synchronize: + description: Synchronize controls if generated resources should be kept in-sync with their source resource. If Synchronize is set to "true" changes to generated resources will be overwritten with resource data from Data or the resource specified in the Clone declaration. Optional. Defaults to "false" if not specified. + type: boolean + type: object + imageExtractors: + additionalProperties: + items: + properties: + key: + description: Key is an optional name of the field within 'path' that will be used to uniquely identify an image. Note - this field MUST be unique. + type: string + name: + description: Name is the entry the image will be available under 'images.<name>' in the context. If this field is not defined, image entries will appear under 'images.custom'. + type: string + path: + description: Path is the path to the object containing the image field in a custom resource. It should be slash-separated. Each slash-separated key must be a valid YAML key or a wildcard '*'. Wildcard keys are expanded in case of arrays or objects. + type: string + value: + description: Value is an optional name of the field within 'path' that points to the image URI. This is useful when a custom 'key' is also defined. + type: string + required: + - path + type: object + type: array + description: ImageExtractors defines a mapping from kinds to ImageExtractorConfigs. This config is only valid for verifyImages rules. + type: object + match: + description: MatchResources defines when this policy rule should be applied. The match criteria can include resource information (e.g. kind, name, namespace, labels) and admission review request information like the user name or role. At least one kind is required. + properties: + all: + description: All allows specifying resources which will be ANDed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + any: + description: Any allows specifying resources which will be ORed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + type: object + mutate: + description: Mutation is used to modify matching resources. + properties: + foreach: + description: ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + items: + description: ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + properties: + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + list: + description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied. + type: string + patchStrategicMerge: + description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. + x-kubernetes-preserve-unknown-fields: true + patchesJson6902: + description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. + type: string + preconditions: + description: 'AnyAllConditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + patchStrategicMerge: + description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. + x-kubernetes-preserve-unknown-fields: true + patchesJson6902: + description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. + type: string + targets: + description: Targets defines the target resources to be mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array + type: object + name: + description: Name is a label to identify the rule, It must be unique within the policy. + maxLength: 63 + type: string + preconditions: + description: 'Preconditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. A direct list of conditions (without `any` or `all` statements is supported for backwards compatibility but See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - AnyIn + - AllIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - AnyIn + - AllIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + validate: + description: Validation is used to validate matching resources. + properties: + anyPattern: + description: AnyPattern specifies list of validation patterns. At least one of the patterns must be satisfied for the validation rule to succeed. + x-kubernetes-preserve-unknown-fields: true + deny: + description: Deny defines conditions used to pass or fail a validation rule. + properties: + conditions: + description: 'Multiple conditions can be declared under an `any` or `all` statement. A direct list of conditions (without `any` or `all` statements) is also supported for backwards compatibility See: https://kyverno.io/docs/writing-policies/validate/#deny-rules' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - AnyIn + - AllIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - AnyIn + - AllIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + type: object + foreach: + description: ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + items: + description: ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + properties: + anyPattern: + description: AnyPattern specifies list of validation patterns. At least one of the patterns must be satisfied for the validation rule to succeed. + x-kubernetes-preserve-unknown-fields: true + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + deny: + description: Deny defines conditions used to pass or fail a validation rule. + properties: + conditions: + description: 'Multiple conditions can be declared under an `any` or `all` statement. A direct list of conditions (without `any` or `all` statements) is also supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules' + x-kubernetes-preserve-unknown-fields: true + type: object + elementScope: + description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree. + type: boolean + list: + description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied. + type: string + pattern: + description: Pattern specifies an overlay-style pattern used to check resources. + x-kubernetes-preserve-unknown-fields: true + preconditions: + description: 'AnyAllConditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + manifests: + description: Manifest specifies conditions for manifest verification + properties: + annotationDomain: + description: AnnotationDomain is custom domain of annotation for message and signature. Default is "cosign.sigstore.dev". + type: string + attestors: + description: Attestors specified the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + dryRun: + description: DryRun configuration + properties: + enable: + type: boolean + namespace: + type: string + type: object + ignoreFields: + description: Fields which will be ignored while comparing manifests. + items: + properties: + fields: + items: + type: string + type: array + objects: + items: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + type: object + type: array + type: object + type: array + repository: + description: Repository is an optional alternate OCI repository to use for resource bundle reference. The repository can be overridden per Attestor or Attestation. + type: string + type: object + message: + description: Message specifies a custom message to be displayed on failure. + type: string + pattern: + description: Pattern specifies an overlay-style pattern used to check resources. + x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes Pod Security admission by specifying exclusions for Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard controls to be excluded. + items: + description: PodSecurityStandard specifies the Pod Security Standard controls to be excluded. + properties: + controlName: + description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user + type: string + images: + description: 'Images selects matching containers and applies the container level PSS. Each image is the image name consisting of the registry address, repository, image, and tag. Empty list matches no containers, PSS checks are applied at the pod level only. Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.' + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard level to be applied to workloads. Allowed values are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard versions that Kubernetes supports. Allowed values are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object + type: object + verifyImages: + description: VerifyImages is used to verify image signatures and mutate them to add a digest + items: + description: ImageVerification validates that images that match the specified pattern are signed with the supplied public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. + properties: + attestations: + description: Attestations are optional checks for signed in-toto Statements used to verify the image. See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the OCI registry and decodes them into a list of Statement declarations. + items: + description: Attestation are checks for signed in-toto Statements that are used to verify the image. See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the OCI registry and decodes them into a list of Statements. + properties: + attestors: + description: Attestors specify the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + conditions: + description: Conditions are used to verify attributes within a Predicate. If no Conditions are specified the attestation check is satisfied as long there are predicates that match the predicate type. + items: + description: AnyAllConditions consists of conditions wrapped denoting a logical criteria to be fulfilled. AnyConditions get fulfilled when at least one of its sub-conditions passes. AllConditions get fulfilled only when all of its sub-conditions pass. + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + type: array + predicateType: + description: PredicateType defines the type of Predicate contained within the Statement. + type: string + required: + - predicateType + type: object + type: array + attestors: + description: Attestors specified the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + imageReferences: + description: 'ImageReferences is a list of matching image reference patterns. At least one pattern in the list must match the image for the rule to apply. Each image reference consists of a registry address (defaults to docker.io), repository, image, and tag (defaults to latest). Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.' + items: + type: string + type: array + mutateDigest: + default: true + description: MutateDigest enables replacement of image tags with digests. Defaults to true. + type: boolean + repository: + description: Repository is an optional alternate OCI repository to use for image signatures and attestations that match this rule. If specified Repository will override the default OCI image repository configured for the installation. The repository can also be overridden per Attestor or Attestation. + type: string + required: + default: true + description: Required validates that images are verified i.e. have matched passed a signature or attestation check. + type: boolean + verifyDigest: + default: true + description: VerifyDigest validates that images have a digest. + type: boolean + type: object + type: array + type: object + type: array + schemaValidation: + description: SchemaValidation skips validation checks for policies as well as patched resources. Optional. The default value is set to "true", it must be set to "false" to disable the validation checks. + type: boolean + validationFailureAction: + default: audit + description: ValidationFailureAction defines if a validation policy rule violation should block the admission review request (enforce), or allow (audit) the admission review request and report an error in a policy report. Optional. Allowed values are audit or enforce. The default value is "audit". + enum: + - audit + - enforce + type: string + validationFailureActionOverrides: + description: ValidationFailureActionOverrides is a Cluster Policy attribute that specifies ValidationFailureAction namespace-wise. It overrides ValidationFailureAction for the specified namespaces. + items: + properties: + action: + description: ValidationFailureAction defines the policy validation failure action + enum: + - audit + - enforce + type: string + namespaces: + items: + type: string + type: array + type: object + type: array + webhookTimeoutSeconds: + description: WebhookTimeoutSeconds specifies the maximum time in seconds allowed to apply this policy. After the configured time expires, the admission request may fail, or may simply ignore the policy results, based on the failure policy. The default timeout is 10s, the value must be between 1 and 30 seconds. + format: int32 + type: integer + type: object + status: + description: Status contains policy runtime data. + properties: + autogen: + description: Autogen contains autogen status information + properties: + rules: + description: Rules is a list of Rule instances. It contains auto generated rules added for pod controllers + items: + description: Rule defines a validation, mutation, or generation control for matching resources. Each rules contains a match declaration to select resources, and an optional exclude declaration to specify which resources to exclude. + properties: + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + exclude: + description: ExcludeResources defines when this policy rule should not be applied. The exclude criteria can include resource information (e.g. kind, name, namespace, labels) and admission review request information like the name or role. + properties: + all: + description: All allows specifying resources which will be ANDed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + any: + description: Any allows specifying resources which will be ORed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources. Specifying ResourceDescription directly under match is being deprecated. Please specify under "any" or "all" instead. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + generate: + description: Generation is used to create new resources. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + clone: + description: Clone specifies the source resource used to populate each generated resource. At most one of Data or Clone can be specified. If neither are provided, the generated resource will be created with default data only. + properties: + name: + description: Name specifies name of the resource. + type: string + namespace: + description: Namespace specifies source resource namespace. + type: string + type: object + cloneList: + description: CloneList specifies the list of source resource used to populate each generated resource. + properties: + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + namespace: + description: Namespace specifies source resource namespace. + type: string + selector: + description: Selector is a label selector. Label keys and values in `matchLabels`. wildcard characters are not supported. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + data: + description: Data provides the resource declaration used to populate each generated resource. At most one of Data or Clone must be specified. If neither are provided, the generated resource will be created with default data only. + x-kubernetes-preserve-unknown-fields: true + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + synchronize: + description: Synchronize controls if generated resources should be kept in-sync with their source resource. If Synchronize is set to "true" changes to generated resources will be overwritten with resource data from Data or the resource specified in the Clone declaration. Optional. Defaults to "false" if not specified. + type: boolean + type: object + imageExtractors: + additionalProperties: + items: + properties: + key: + description: Key is an optional name of the field within 'path' that will be used to uniquely identify an image. Note - this field MUST be unique. + type: string + name: + description: Name is the entry the image will be available under 'images.<name>' in the context. If this field is not defined, image entries will appear under 'images.custom'. + type: string + path: + description: Path is the path to the object containing the image field in a custom resource. It should be slash-separated. Each slash-separated key must be a valid YAML key or a wildcard '*'. Wildcard keys are expanded in case of arrays or objects. + type: string + value: + description: Value is an optional name of the field within 'path' that points to the image URI. This is useful when a custom 'key' is also defined. + type: string + required: + - path + type: object + type: array + description: ImageExtractors defines a mapping from kinds to ImageExtractorConfigs. This config is only valid for verifyImages rules. + type: object + match: + description: MatchResources defines when this policy rule should be applied. The match criteria can include resource information (e.g. kind, name, namespace, labels) and admission review request information like the user name or role. At least one kind is required. + properties: + all: + description: All allows specifying resources which will be ANDed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + any: + description: Any allows specifying resources which will be ORed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources. Specifying ResourceDescription directly under match is being deprecated. Please specify under "any" or "all" instead. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + mutate: + description: Mutation is used to modify matching resources. + properties: + foreach: + description: ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + items: + description: ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + properties: + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + list: + description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied. + type: string + patchStrategicMerge: + description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. + x-kubernetes-preserve-unknown-fields: true + patchesJson6902: + description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. + type: string + preconditions: + description: 'AnyAllConditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + patchStrategicMerge: + description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. + x-kubernetes-preserve-unknown-fields: true + patchesJson6902: + description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. + type: string + targets: + description: Targets defines the target resources to be mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array + type: object + name: + description: Name is a label to identify the rule, It must be unique within the policy. + maxLength: 63 + type: string + preconditions: + description: 'Preconditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. A direct list of conditions (without `any` or `all` statements is supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/preconditions/' + x-kubernetes-preserve-unknown-fields: true + validate: + description: Validation is used to validate matching resources. + properties: + anyPattern: + description: AnyPattern specifies list of validation patterns. At least one of the patterns must be satisfied for the validation rule to succeed. + x-kubernetes-preserve-unknown-fields: true + deny: + description: Deny defines conditions used to pass or fail a validation rule. + properties: + conditions: + description: 'Multiple conditions can be declared under an `any` or `all` statement. A direct list of conditions (without `any` or `all` statements) is also supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules' + x-kubernetes-preserve-unknown-fields: true + type: object + foreach: + description: ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + items: + description: ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + properties: + anyPattern: + description: AnyPattern specifies list of validation patterns. At least one of the patterns must be satisfied for the validation rule to succeed. + x-kubernetes-preserve-unknown-fields: true + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + deny: + description: Deny defines conditions used to pass or fail a validation rule. + properties: + conditions: + description: 'Multiple conditions can be declared under an `any` or `all` statement. A direct list of conditions (without `any` or `all` statements) is also supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules' + x-kubernetes-preserve-unknown-fields: true + type: object + elementScope: + description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree. + type: boolean + list: + description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied. + type: string + pattern: + description: Pattern specifies an overlay-style pattern used to check resources. + x-kubernetes-preserve-unknown-fields: true + preconditions: + description: 'AnyAllConditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + manifests: + description: Manifest specifies conditions for manifest verification + properties: + annotationDomain: + description: AnnotationDomain is custom domain of annotation for message and signature. Default is "cosign.sigstore.dev". + type: string + attestors: + description: Attestors specified the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + dryRun: + description: DryRun configuration + properties: + enable: + type: boolean + namespace: + type: string + type: object + ignoreFields: + description: Fields which will be ignored while comparing manifests. + items: + properties: + fields: + items: + type: string + type: array + objects: + items: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + type: object + type: array + type: object + type: array + repository: + description: Repository is an optional alternate OCI repository to use for resource bundle reference. The repository can be overridden per Attestor or Attestation. + type: string + type: object + message: + description: Message specifies a custom message to be displayed on failure. + type: string + pattern: + description: Pattern specifies an overlay-style pattern used to check resources. + x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes Pod Security admission by specifying exclusions for Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard controls to be excluded. + items: + description: PodSecurityStandard specifies the Pod Security Standard controls to be excluded. + properties: + controlName: + description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user + type: string + images: + description: 'Images selects matching containers and applies the container level PSS. Each image is the image name consisting of the registry address, repository, image, and tag. Empty list matches no containers, PSS checks are applied at the pod level only. Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.' + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard level to be applied to workloads. Allowed values are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard versions that Kubernetes supports. Allowed values are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object + type: object + verifyImages: + description: VerifyImages is used to verify image signatures and mutate them to add a digest + items: + description: ImageVerification validates that images that match the specified pattern are signed with the supplied public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. Deprecated. + type: object + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. Deprecated. Use annotations per Attestor instead. + type: object + attestations: + description: Attestations are optional checks for signed in-toto Statements used to verify the image. See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the OCI registry and decodes them into a list of Statement declarations. + items: + description: Attestation are checks for signed in-toto Statements that are used to verify the image. See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the OCI registry and decodes them into a list of Statements. + properties: + attestors: + description: Attestors specify the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + conditions: + description: Conditions are used to verify attributes within a Predicate. If no Conditions are specified the attestation check is satisfied as long there are predicates that match the predicate type. + items: + description: AnyAllConditions consists of conditions wrapped denoting a logical criteria to be fulfilled. AnyConditions get fulfilled when at least one of its sub-conditions passes. AllConditions get fulfilled only when all of its sub-conditions pass. + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + type: array + predicateType: + description: PredicateType defines the type of Predicate contained within the Statement. + type: string + required: + - predicateType + type: object + type: array + attestors: + description: Attestors specified the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + image: + description: 'Image is the image name consisting of the registry address, repository, image, and tag. Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images. Deprecated. Use ImageReferences instead.' + type: string + imageReferences: + description: 'ImageReferences is a list of matching image reference patterns. At least one pattern in the list must match the image for the rule to apply. Each image reference consists of a registry address (defaults to docker.io), repository, image, and tag (defaults to latest). Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.' + items: + type: string + type: array + issuer: + description: Issuer is the certificate issuer used for keyless signing. Deprecated. Use KeylessAttestor instead. + type: string + key: + description: Key is the PEM encoded public key that the image or attestation is signed with. Deprecated. Use StaticKeyAttestor instead. + type: string + mutateDigest: + default: true + description: MutateDigest enables replacement of image tags with digests. Defaults to true. + type: boolean + repository: + description: Repository is an optional alternate OCI repository to use for image signatures and attestations that match this rule. If specified Repository will override the default OCI image repository configured for the installation. The repository can also be overridden per Attestor or Attestation. + type: string + required: + default: true + description: Required validates that images are verified i.e. have matched passed a signature or attestation check. + type: boolean + roots: + description: Roots is the PEM encoded Root certificate chain used for keyless signing Deprecated. Use KeylessAttestor instead. + type: string + subject: + description: Subject is the identity used for keyless signing, for example an email address Deprecated. Use KeylessAttestor instead. + type: string + verifyDigest: + default: true + description: VerifyDigest validates that images have a digest. + type: boolean + type: object + type: array + type: object + type: array + type: object + conditions: + description: Conditions is a list of conditions that apply to the policy + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + ready: + description: Ready indicates if the policy is ready to serve the admission request. Deprecated in favor of Conditions + type: boolean + required: + - ready + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + config.kubernetes.io/index: '6' + internal.config.kubernetes.io/index: '6' + {{- trim (include "kyverno.crdAnnotations" .) | nindent 4 }} + creationTimestamp: null + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: kyverno + app.kubernetes.io/name: kyverno + app.kubernetes.io/part-of: kyverno + app.kubernetes.io/version: '{{.Chart.AppVersion}}' + name: clusterpolicyreports.wgpolicyk8s.io +spec: + group: wgpolicyk8s.io + names: + kind: ClusterPolicyReport + listKind: ClusterPolicyReportList + plural: clusterpolicyreports + shortNames: + - cpolr + singular: clusterpolicyreport + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .scope.kind + name: Kind + priority: 1 + type: string + - jsonPath: .scope.name + name: Name + priority: 1 + type: string + - jsonPath: .summary.pass + name: Pass + type: integer + - jsonPath: .summary.fail + name: Fail + type: integer + - jsonPath: .summary.warn + name: Warn + type: integer + - jsonPath: .summary.error + name: Error + type: integer + - jsonPath: .summary.skip + name: Skip + type: integer + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: ClusterPolicyReport is the Schema for the clusterpolicyreports API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + results: + description: PolicyReportResult provides result details + items: + description: PolicyReportResult provides the result for an individual policy + properties: + category: + description: Category indicates policy category + type: string + message: + description: Description is a short user friendly message for the policy rule + type: string + policy: + description: Policy is the name or identifier of the policy + type: string + properties: + additionalProperties: + type: string + description: Properties provides additional information for the policy rule + type: object + resourceSelector: + description: SubjectSelector is an optional label selector for checked Kubernetes resources. For example, a policy result may apply to all pods that match a label. Either a Subject or a SubjectSelector can be specified. If neither are provided, the result is assumed to be for the policy report scope. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + resources: + description: Subjects is an optional reference to the checked Kubernetes resources + items: + description: "ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, \"must refer only to types A and B\" or \"UID not honored\" or \"name must be restricted\". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. \n Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 ." + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + type: object + x-kubernetes-map-type: atomic + type: array + result: + description: Result indicates the outcome of the policy rule execution + enum: + - pass + - fail + - warn + - error + - skip + type: string + rule: + description: Rule is the name or identifier of the rule within the policy + type: string + scored: + description: Scored indicates if this result is scored + type: boolean + severity: + description: Severity indicates policy check result criticality + enum: + - critical + - high + - low + - medium + - info + type: string + source: + description: Source is an identifier for the policy engine that manages this report + type: string + timestamp: + description: Timestamp indicates the time the result was found + properties: + nanos: + description: Non-negative fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values that count forward in time. Must be from 0 to 999,999,999 inclusive. This field may be limited in precision depending on context. + format: int32 + type: integer + seconds: + description: Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive. + format: int64 + type: integer + required: + - nanos + - seconds + type: object + required: + - policy + type: object + type: array + scope: + description: Scope is an optional reference to the report scope (e.g. a Deployment, Namespace, or Node) + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + type: object + x-kubernetes-map-type: atomic + scopeSelector: + description: ScopeSelector is an optional selector for multiple scopes (e.g. Pods). Either one of, or none of, but not both of, Scope or ScopeSelector should be specified. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + summary: + description: PolicyReportSummary provides a summary of results + properties: + error: + description: Error provides the count of policies that could not be evaluated + type: integer + fail: + description: Fail provides the count of policies whose requirements were not met + type: integer + pass: + description: Pass provides the count of policies whose requirements were met + type: integer + skip: + description: Skip indicates the count of policies that were not selected for evaluation + type: integer + warn: + description: Warn provides the count of non-scored policies whose requirements were not met + type: integer + type: object + type: object + served: true + storage: true + subresources: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + config.kubernetes.io/index: '7' + internal.config.kubernetes.io/index: '7' + {{- trim (include "kyverno.crdAnnotations" .) | nindent 4 }} + creationTimestamp: null + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: kyverno + app.kubernetes.io/name: kyverno + app.kubernetes.io/part-of: kyverno + app.kubernetes.io/version: '{{.Chart.AppVersion}}' + name: generaterequests.kyverno.io +spec: + group: kyverno.io + names: + kind: GenerateRequest + listKind: GenerateRequestList + plural: generaterequests + shortNames: + - gr + singular: generaterequest + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.policy + name: Policy + type: string + - jsonPath: .spec.resource.kind + name: ResourceKind + type: string + - jsonPath: .spec.resource.name + name: ResourceName + type: string + - jsonPath: .spec.resource.namespace + name: ResourceNamespace + type: string + - jsonPath: .status.state + name: status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: GenerateRequest is a request to process generate rule. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec is the information to identify the generate request. + properties: + context: + description: Context ... + properties: + admissionRequestInfo: + description: AdmissionRequestInfoObject stores the admission request and operation details + properties: + admissionRequest: + type: string + operation: + description: Operation is the type of resource operation being checked for admission control + type: string + type: object + userInfo: + description: RequestInfo contains permission info carried in an admission request. + properties: + clusterRoles: + description: ClusterRoles is a list of possible clusterRoles send the request. + items: + type: string + nullable: true + type: array + roles: + description: Roles is a list of possible role send the request. + items: + type: string + nullable: true + type: array + userInfo: + description: UserInfo is the userInfo carried in the admission request. + properties: + extra: + additionalProperties: + description: ExtraValue masks the value so protobuf can generate + items: + type: string + type: array + description: Any additional information provided by the authenticator. + type: object + groups: + description: The names of groups this user is a part of. + items: + type: string + type: array + uid: + description: A unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs. + type: string + username: + description: The name that uniquely identifies this user among all active users. + type: string + type: object + type: object + type: object + policy: + description: Specifies the name of the policy. + type: string + resource: + description: ResourceSpec is the information to identify the generate request. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + required: + - context + - policy + - resource + type: object + status: + description: Status contains statistics related to generate request. + properties: + generatedResources: + description: This will track the resources that are generated by the generate Policy. Will be used during clean up resources. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array + message: + description: Specifies request status message. + type: string + state: + description: State represents state of the generate request. + type: string + required: + - state + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + config.kubernetes.io/index: '8' + internal.config.kubernetes.io/index: '8' + {{- trim (include "kyverno.crdAnnotations" .) | nindent 4 }} + creationTimestamp: null + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: kyverno + app.kubernetes.io/name: kyverno + app.kubernetes.io/part-of: kyverno + app.kubernetes.io/version: '{{.Chart.AppVersion}}' + name: policies.kyverno.io +spec: + group: kyverno.io + names: + categories: + - kyverno + - all + kind: Policy + listKind: PolicyList + plural: policies + shortNames: + - pol + singular: policy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.background + name: Background + type: boolean + - jsonPath: .spec.validationFailureAction + name: Validate Action + type: string + - jsonPath: .spec.failurePolicy + name: Failure Policy + priority: 1 + type: string + - jsonPath: .status.ready + name: Ready + type: boolean + name: v1 + schema: + openAPIV3Schema: + description: 'Policy declares validation, mutation, and generation behaviors for matching resources. See: https://kyverno.io/docs/writing-policies/ for more information.' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines policy behaviors and contains one or more rules. + properties: + applyRules: + description: ApplyRules controls how rules in a policy are applied. Rule are processed in the order of declaration. When set to `One` processing stops after a rule has been applied i.e. the rule matches and results in a pass, fail, or error. When set to `All` all rules in the policy are processed. The default is `All`. + enum: + - All + - One + type: string + background: + default: true + description: Background controls if rules are applied to existing resources during a background scan. Optional. Default value is "true". The value must be set to "false" if the policy rule uses variables that are only available in the admission review request (e.g. user name). + type: boolean + failurePolicy: + description: FailurePolicy defines how unexpected policy errors and webhook response timeout errors are handled. Rules within the same policy share the same failure behavior. Allowed values are Ignore or Fail. Defaults to Fail. + enum: + - Ignore + - Fail + type: string + generateExistingOnPolicyUpdate: + description: GenerateExistingOnPolicyUpdate controls whether to trigger generate rule in existing resources If is set to "true" generate rule will be triggered and applied to existing matched resources. Defaults to "false" if not specified. + type: boolean + mutateExistingOnPolicyUpdate: + description: MutateExistingOnPolicyUpdate controls if a mutateExisting policy is applied on policy events. Default value is "false". + type: boolean + rules: + description: Rules is a list of Rule instances. A Policy contains multiple rules and each rule can validate, mutate, or generate resources. + items: + description: Rule defines a validation, mutation, or generation control for matching resources. Each rules contains a match declaration to select resources, and an optional exclude declaration to specify which resources to exclude. + properties: + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + exclude: + description: ExcludeResources defines when this policy rule should not be applied. The exclude criteria can include resource information (e.g. kind, name, namespace, labels) and admission review request information like the name or role. + properties: + all: + description: All allows specifying resources which will be ANDed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + any: + description: Any allows specifying resources which will be ORed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources. Specifying ResourceDescription directly under match is being deprecated. Please specify under "any" or "all" instead. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + generate: + description: Generation is used to create new resources. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + clone: + description: Clone specifies the source resource used to populate each generated resource. At most one of Data or Clone can be specified. If neither are provided, the generated resource will be created with default data only. + properties: + name: + description: Name specifies name of the resource. + type: string + namespace: + description: Namespace specifies source resource namespace. + type: string + type: object + cloneList: + description: CloneList specifies the list of source resource used to populate each generated resource. + properties: + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + namespace: + description: Namespace specifies source resource namespace. + type: string + selector: + description: Selector is a label selector. Label keys and values in `matchLabels`. wildcard characters are not supported. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + data: + description: Data provides the resource declaration used to populate each generated resource. At most one of Data or Clone must be specified. If neither are provided, the generated resource will be created with default data only. + x-kubernetes-preserve-unknown-fields: true + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + synchronize: + description: Synchronize controls if generated resources should be kept in-sync with their source resource. If Synchronize is set to "true" changes to generated resources will be overwritten with resource data from Data or the resource specified in the Clone declaration. Optional. Defaults to "false" if not specified. + type: boolean + type: object + imageExtractors: + additionalProperties: + items: + properties: + key: + description: Key is an optional name of the field within 'path' that will be used to uniquely identify an image. Note - this field MUST be unique. + type: string + name: + description: Name is the entry the image will be available under 'images.<name>' in the context. If this field is not defined, image entries will appear under 'images.custom'. + type: string + path: + description: Path is the path to the object containing the image field in a custom resource. It should be slash-separated. Each slash-separated key must be a valid YAML key or a wildcard '*'. Wildcard keys are expanded in case of arrays or objects. + type: string + value: + description: Value is an optional name of the field within 'path' that points to the image URI. This is useful when a custom 'key' is also defined. + type: string + required: + - path + type: object + type: array + description: ImageExtractors defines a mapping from kinds to ImageExtractorConfigs. This config is only valid for verifyImages rules. + type: object + match: + description: MatchResources defines when this policy rule should be applied. The match criteria can include resource information (e.g. kind, name, namespace, labels) and admission review request information like the user name or role. At least one kind is required. + properties: + all: + description: All allows specifying resources which will be ANDed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + any: + description: Any allows specifying resources which will be ORed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources. Specifying ResourceDescription directly under match is being deprecated. Please specify under "any" or "all" instead. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + mutate: + description: Mutation is used to modify matching resources. + properties: + foreach: + description: ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + items: + description: ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + properties: + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + list: + description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied. + type: string + patchStrategicMerge: + description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. + x-kubernetes-preserve-unknown-fields: true + patchesJson6902: + description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. + type: string + preconditions: + description: 'AnyAllConditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + patchStrategicMerge: + description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. + x-kubernetes-preserve-unknown-fields: true + patchesJson6902: + description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. + type: string + targets: + description: Targets defines the target resources to be mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array + type: object + name: + description: Name is a label to identify the rule, It must be unique within the policy. + maxLength: 63 + type: string + preconditions: + description: 'Preconditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. A direct list of conditions (without `any` or `all` statements is supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/preconditions/' + x-kubernetes-preserve-unknown-fields: true + validate: + description: Validation is used to validate matching resources. + properties: + anyPattern: + description: AnyPattern specifies list of validation patterns. At least one of the patterns must be satisfied for the validation rule to succeed. + x-kubernetes-preserve-unknown-fields: true + deny: + description: Deny defines conditions used to pass or fail a validation rule. + properties: + conditions: + description: 'Multiple conditions can be declared under an `any` or `all` statement. A direct list of conditions (without `any` or `all` statements) is also supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules' + x-kubernetes-preserve-unknown-fields: true + type: object + foreach: + description: ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + items: + description: ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + properties: + anyPattern: + description: AnyPattern specifies list of validation patterns. At least one of the patterns must be satisfied for the validation rule to succeed. + x-kubernetes-preserve-unknown-fields: true + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + deny: + description: Deny defines conditions used to pass or fail a validation rule. + properties: + conditions: + description: 'Multiple conditions can be declared under an `any` or `all` statement. A direct list of conditions (without `any` or `all` statements) is also supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules' + x-kubernetes-preserve-unknown-fields: true + type: object + elementScope: + description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree. + type: boolean + list: + description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied. + type: string + pattern: + description: Pattern specifies an overlay-style pattern used to check resources. + x-kubernetes-preserve-unknown-fields: true + preconditions: + description: 'AnyAllConditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + manifests: + description: Manifest specifies conditions for manifest verification + properties: + annotationDomain: + description: AnnotationDomain is custom domain of annotation for message and signature. Default is "cosign.sigstore.dev". + type: string + attestors: + description: Attestors specified the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + dryRun: + description: DryRun configuration + properties: + enable: + type: boolean + namespace: + type: string + type: object + ignoreFields: + description: Fields which will be ignored while comparing manifests. + items: + properties: + fields: + items: + type: string + type: array + objects: + items: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + type: object + type: array + type: object + type: array + repository: + description: Repository is an optional alternate OCI repository to use for resource bundle reference. The repository can be overridden per Attestor or Attestation. + type: string + type: object + message: + description: Message specifies a custom message to be displayed on failure. + type: string + pattern: + description: Pattern specifies an overlay-style pattern used to check resources. + x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes Pod Security admission by specifying exclusions for Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard controls to be excluded. + items: + description: PodSecurityStandard specifies the Pod Security Standard controls to be excluded. + properties: + controlName: + description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user + type: string + images: + description: 'Images selects matching containers and applies the container level PSS. Each image is the image name consisting of the registry address, repository, image, and tag. Empty list matches no containers, PSS checks are applied at the pod level only. Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.' + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard level to be applied to workloads. Allowed values are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard versions that Kubernetes supports. Allowed values are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object + type: object + verifyImages: + description: VerifyImages is used to verify image signatures and mutate them to add a digest + items: + description: ImageVerification validates that images that match the specified pattern are signed with the supplied public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. Deprecated. + type: object + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. Deprecated. Use annotations per Attestor instead. + type: object + attestations: + description: Attestations are optional checks for signed in-toto Statements used to verify the image. See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the OCI registry and decodes them into a list of Statement declarations. + items: + description: Attestation are checks for signed in-toto Statements that are used to verify the image. See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the OCI registry and decodes them into a list of Statements. + properties: + attestors: + description: Attestors specify the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + conditions: + description: Conditions are used to verify attributes within a Predicate. If no Conditions are specified the attestation check is satisfied as long there are predicates that match the predicate type. + items: + description: AnyAllConditions consists of conditions wrapped denoting a logical criteria to be fulfilled. AnyConditions get fulfilled when at least one of its sub-conditions passes. AllConditions get fulfilled only when all of its sub-conditions pass. + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + type: array + predicateType: + description: PredicateType defines the type of Predicate contained within the Statement. + type: string + required: + - predicateType + type: object + type: array + attestors: + description: Attestors specified the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + image: + description: 'Image is the image name consisting of the registry address, repository, image, and tag. Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images. Deprecated. Use ImageReferences instead.' + type: string + imageReferences: + description: 'ImageReferences is a list of matching image reference patterns. At least one pattern in the list must match the image for the rule to apply. Each image reference consists of a registry address (defaults to docker.io), repository, image, and tag (defaults to latest). Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.' + items: + type: string + type: array + issuer: + description: Issuer is the certificate issuer used for keyless signing. Deprecated. Use KeylessAttestor instead. + type: string + key: + description: Key is the PEM encoded public key that the image or attestation is signed with. Deprecated. Use StaticKeyAttestor instead. + type: string + mutateDigest: + default: true + description: MutateDigest enables replacement of image tags with digests. Defaults to true. + type: boolean + repository: + description: Repository is an optional alternate OCI repository to use for image signatures and attestations that match this rule. If specified Repository will override the default OCI image repository configured for the installation. The repository can also be overridden per Attestor or Attestation. + type: string + required: + default: true + description: Required validates that images are verified i.e. have matched passed a signature or attestation check. + type: boolean + roots: + description: Roots is the PEM encoded Root certificate chain used for keyless signing Deprecated. Use KeylessAttestor instead. + type: string + subject: + description: Subject is the identity used for keyless signing, for example an email address Deprecated. Use KeylessAttestor instead. + type: string + verifyDigest: + default: true + description: VerifyDigest validates that images have a digest. + type: boolean + type: object + type: array + type: object + type: array + schemaValidation: + description: SchemaValidation skips validation checks for policies as well as patched resources. Optional. The default value is set to "true", it must be set to "false" to disable the validation checks. + type: boolean + validationFailureAction: + default: audit + description: ValidationFailureAction defines if a validation policy rule violation should block the admission review request (enforce), or allow (audit) the admission review request and report an error in a policy report. Optional. Allowed values are audit or enforce. The default value is "audit". + enum: + - audit + - enforce + type: string + validationFailureActionOverrides: + description: ValidationFailureActionOverrides is a Cluster Policy attribute that specifies ValidationFailureAction namespace-wise. It overrides ValidationFailureAction for the specified namespaces. + items: + properties: + action: + description: ValidationFailureAction defines the policy validation failure action + enum: + - audit + - enforce + type: string + namespaces: + items: + type: string + type: array + type: object + type: array + webhookTimeoutSeconds: + description: WebhookTimeoutSeconds specifies the maximum time in seconds allowed to apply this policy. After the configured time expires, the admission request may fail, or may simply ignore the policy results, based on the failure policy. The default timeout is 10s, the value must be between 1 and 30 seconds. + format: int32 + type: integer + type: object + status: + description: Status contains policy runtime information. Deprecated. Policy metrics are available via the metrics endpoint + properties: + autogen: + description: Autogen contains autogen status information + properties: + rules: + description: Rules is a list of Rule instances. It contains auto generated rules added for pod controllers + items: + description: Rule defines a validation, mutation, or generation control for matching resources. Each rules contains a match declaration to select resources, and an optional exclude declaration to specify which resources to exclude. + properties: + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + exclude: + description: ExcludeResources defines when this policy rule should not be applied. The exclude criteria can include resource information (e.g. kind, name, namespace, labels) and admission review request information like the name or role. + properties: + all: + description: All allows specifying resources which will be ANDed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + any: + description: Any allows specifying resources which will be ORed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources. Specifying ResourceDescription directly under match is being deprecated. Please specify under "any" or "all" instead. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + generate: + description: Generation is used to create new resources. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + clone: + description: Clone specifies the source resource used to populate each generated resource. At most one of Data or Clone can be specified. If neither are provided, the generated resource will be created with default data only. + properties: + name: + description: Name specifies name of the resource. + type: string + namespace: + description: Namespace specifies source resource namespace. + type: string + type: object + cloneList: + description: CloneList specifies the list of source resource used to populate each generated resource. + properties: + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + namespace: + description: Namespace specifies source resource namespace. + type: string + selector: + description: Selector is a label selector. Label keys and values in `matchLabels`. wildcard characters are not supported. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + data: + description: Data provides the resource declaration used to populate each generated resource. At most one of Data or Clone must be specified. If neither are provided, the generated resource will be created with default data only. + x-kubernetes-preserve-unknown-fields: true + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + synchronize: + description: Synchronize controls if generated resources should be kept in-sync with their source resource. If Synchronize is set to "true" changes to generated resources will be overwritten with resource data from Data or the resource specified in the Clone declaration. Optional. Defaults to "false" if not specified. + type: boolean + type: object + imageExtractors: + additionalProperties: + items: + properties: + key: + description: Key is an optional name of the field within 'path' that will be used to uniquely identify an image. Note - this field MUST be unique. + type: string + name: + description: Name is the entry the image will be available under 'images.<name>' in the context. If this field is not defined, image entries will appear under 'images.custom'. + type: string + path: + description: Path is the path to the object containing the image field in a custom resource. It should be slash-separated. Each slash-separated key must be a valid YAML key or a wildcard '*'. Wildcard keys are expanded in case of arrays or objects. + type: string + value: + description: Value is an optional name of the field within 'path' that points to the image URI. This is useful when a custom 'key' is also defined. + type: string + required: + - path + type: object + type: array + description: ImageExtractors defines a mapping from kinds to ImageExtractorConfigs. This config is only valid for verifyImages rules. + type: object + match: + description: MatchResources defines when this policy rule should be applied. The match criteria can include resource information (e.g. kind, name, namespace, labels) and admission review request information like the user name or role. At least one kind is required. + properties: + all: + description: All allows specifying resources which will be ANDed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + any: + description: Any allows specifying resources which will be ORed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources. Specifying ResourceDescription directly under match is being deprecated. Please specify under "any" or "all" instead. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + mutate: + description: Mutation is used to modify matching resources. + properties: + foreach: + description: ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + items: + description: ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + properties: + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + list: + description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied. + type: string + patchStrategicMerge: + description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. + x-kubernetes-preserve-unknown-fields: true + patchesJson6902: + description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. + type: string + preconditions: + description: 'AnyAllConditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + patchStrategicMerge: + description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. + x-kubernetes-preserve-unknown-fields: true + patchesJson6902: + description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. + type: string + targets: + description: Targets defines the target resources to be mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array + type: object + name: + description: Name is a label to identify the rule, It must be unique within the policy. + maxLength: 63 + type: string + preconditions: + description: 'Preconditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. A direct list of conditions (without `any` or `all` statements is supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/preconditions/' + x-kubernetes-preserve-unknown-fields: true + validate: + description: Validation is used to validate matching resources. + properties: + anyPattern: + description: AnyPattern specifies list of validation patterns. At least one of the patterns must be satisfied for the validation rule to succeed. + x-kubernetes-preserve-unknown-fields: true + deny: + description: Deny defines conditions used to pass or fail a validation rule. + properties: + conditions: + description: 'Multiple conditions can be declared under an `any` or `all` statement. A direct list of conditions (without `any` or `all` statements) is also supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules' + x-kubernetes-preserve-unknown-fields: true + type: object + foreach: + description: ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + items: + description: ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + properties: + anyPattern: + description: AnyPattern specifies list of validation patterns. At least one of the patterns must be satisfied for the validation rule to succeed. + x-kubernetes-preserve-unknown-fields: true + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + deny: + description: Deny defines conditions used to pass or fail a validation rule. + properties: + conditions: + description: 'Multiple conditions can be declared under an `any` or `all` statement. A direct list of conditions (without `any` or `all` statements) is also supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules' + x-kubernetes-preserve-unknown-fields: true + type: object + elementScope: + description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree. + type: boolean + list: + description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied. + type: string + pattern: + description: Pattern specifies an overlay-style pattern used to check resources. + x-kubernetes-preserve-unknown-fields: true + preconditions: + description: 'AnyAllConditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + manifests: + description: Manifest specifies conditions for manifest verification + properties: + annotationDomain: + description: AnnotationDomain is custom domain of annotation for message and signature. Default is "cosign.sigstore.dev". + type: string + attestors: + description: Attestors specified the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + dryRun: + description: DryRun configuration + properties: + enable: + type: boolean + namespace: + type: string + type: object + ignoreFields: + description: Fields which will be ignored while comparing manifests. + items: + properties: + fields: + items: + type: string + type: array + objects: + items: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + type: object + type: array + type: object + type: array + repository: + description: Repository is an optional alternate OCI repository to use for resource bundle reference. The repository can be overridden per Attestor or Attestation. + type: string + type: object + message: + description: Message specifies a custom message to be displayed on failure. + type: string + pattern: + description: Pattern specifies an overlay-style pattern used to check resources. + x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes Pod Security admission by specifying exclusions for Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard controls to be excluded. + items: + description: PodSecurityStandard specifies the Pod Security Standard controls to be excluded. + properties: + controlName: + description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user + type: string + images: + description: 'Images selects matching containers and applies the container level PSS. Each image is the image name consisting of the registry address, repository, image, and tag. Empty list matches no containers, PSS checks are applied at the pod level only. Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.' + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard level to be applied to workloads. Allowed values are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard versions that Kubernetes supports. Allowed values are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object + type: object + verifyImages: + description: VerifyImages is used to verify image signatures and mutate them to add a digest + items: + description: ImageVerification validates that images that match the specified pattern are signed with the supplied public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. Deprecated. + type: object + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. Deprecated. Use annotations per Attestor instead. + type: object + attestations: + description: Attestations are optional checks for signed in-toto Statements used to verify the image. See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the OCI registry and decodes them into a list of Statement declarations. + items: + description: Attestation are checks for signed in-toto Statements that are used to verify the image. See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the OCI registry and decodes them into a list of Statements. + properties: + attestors: + description: Attestors specify the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + conditions: + description: Conditions are used to verify attributes within a Predicate. If no Conditions are specified the attestation check is satisfied as long there are predicates that match the predicate type. + items: + description: AnyAllConditions consists of conditions wrapped denoting a logical criteria to be fulfilled. AnyConditions get fulfilled when at least one of its sub-conditions passes. AllConditions get fulfilled only when all of its sub-conditions pass. + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + type: array + predicateType: + description: PredicateType defines the type of Predicate contained within the Statement. + type: string + required: + - predicateType + type: object + type: array + attestors: + description: Attestors specified the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + image: + description: 'Image is the image name consisting of the registry address, repository, image, and tag. Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images. Deprecated. Use ImageReferences instead.' + type: string + imageReferences: + description: 'ImageReferences is a list of matching image reference patterns. At least one pattern in the list must match the image for the rule to apply. Each image reference consists of a registry address (defaults to docker.io), repository, image, and tag (defaults to latest). Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.' + items: + type: string + type: array + issuer: + description: Issuer is the certificate issuer used for keyless signing. Deprecated. Use KeylessAttestor instead. + type: string + key: + description: Key is the PEM encoded public key that the image or attestation is signed with. Deprecated. Use StaticKeyAttestor instead. + type: string + mutateDigest: + default: true + description: MutateDigest enables replacement of image tags with digests. Defaults to true. + type: boolean + repository: + description: Repository is an optional alternate OCI repository to use for image signatures and attestations that match this rule. If specified Repository will override the default OCI image repository configured for the installation. The repository can also be overridden per Attestor or Attestation. + type: string + required: + default: true + description: Required validates that images are verified i.e. have matched passed a signature or attestation check. + type: boolean + roots: + description: Roots is the PEM encoded Root certificate chain used for keyless signing Deprecated. Use KeylessAttestor instead. + type: string + subject: + description: Subject is the identity used for keyless signing, for example an email address Deprecated. Use KeylessAttestor instead. + type: string + verifyDigest: + default: true + description: VerifyDigest validates that images have a digest. + type: boolean + type: object + type: array + type: object + type: array + type: object + conditions: + description: Conditions is a list of conditions that apply to the policy + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + ready: + description: Ready indicates if the policy is ready to serve the admission request. Deprecated in favor of Conditions + type: boolean + required: + - ready + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.background + name: Background + type: boolean + - jsonPath: .spec.validationFailureAction + name: Validate Action + type: string + - jsonPath: .spec.failurePolicy + name: Failure Policy + priority: 1 + type: string + - jsonPath: .status.ready + name: Ready + type: boolean + name: v2beta1 + schema: + openAPIV3Schema: + description: 'Policy declares validation, mutation, and generation behaviors for matching resources. See: https://kyverno.io/docs/writing-policies/ for more information.' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines policy behaviors and contains one or more rules. + properties: + applyRules: + description: ApplyRules controls how rules in a policy are applied. Rule are processed in the order of declaration. When set to `One` processing stops after a rule has been applied i.e. the rule matches and results in a pass, fail, or error. When set to `All` all rules in the policy are processed. The default is `All`. + enum: + - All + - One + type: string + background: + default: true + description: Background controls if rules are applied to existing resources during a background scan. Optional. Default value is "true". The value must be set to "false" if the policy rule uses variables that are only available in the admission review request (e.g. user name). + type: boolean + failurePolicy: + description: FailurePolicy defines how unexpected policy errors and webhook response timeout errors are handled. Rules within the same policy share the same failure behavior. Allowed values are Ignore or Fail. Defaults to Fail. + enum: + - Ignore + - Fail + type: string + generateExistingOnPolicyUpdate: + description: GenerateExistingOnPolicyUpdate controls whether to trigger generate rule in existing resources If is set to "true" generate rule will be triggered and applied to existing matched resources. Defaults to "false" if not specified. + type: boolean + mutateExistingOnPolicyUpdate: + description: MutateExistingOnPolicyUpdate controls if a mutateExisting policy is applied on policy events. Default value is "false". + type: boolean + rules: + description: Rules is a list of Rule instances. A Policy contains multiple rules and each rule can validate, mutate, or generate resources. + items: + description: Rule defines a validation, mutation, or generation control for matching resources. Each rules contains a match declaration to select resources, and an optional exclude declaration to specify which resources to exclude. + properties: + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + exclude: + description: ExcludeResources defines when this policy rule should not be applied. The exclude criteria can include resource information (e.g. kind, name, namespace, labels) and admission review request information like the name or role. + properties: + all: + description: All allows specifying resources which will be ANDed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + any: + description: Any allows specifying resources which will be ORed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + type: object + generate: + description: Generation is used to create new resources. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + clone: + description: Clone specifies the source resource used to populate each generated resource. At most one of Data or Clone can be specified. If neither are provided, the generated resource will be created with default data only. + properties: + name: + description: Name specifies name of the resource. + type: string + namespace: + description: Namespace specifies source resource namespace. + type: string + type: object + cloneList: + description: CloneList specifies the list of source resource used to populate each generated resource. + properties: + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + namespace: + description: Namespace specifies source resource namespace. + type: string + selector: + description: Selector is a label selector. Label keys and values in `matchLabels`. wildcard characters are not supported. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + data: + description: Data provides the resource declaration used to populate each generated resource. At most one of Data or Clone must be specified. If neither are provided, the generated resource will be created with default data only. + x-kubernetes-preserve-unknown-fields: true + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + synchronize: + description: Synchronize controls if generated resources should be kept in-sync with their source resource. If Synchronize is set to "true" changes to generated resources will be overwritten with resource data from Data or the resource specified in the Clone declaration. Optional. Defaults to "false" if not specified. + type: boolean + type: object + imageExtractors: + additionalProperties: + items: + properties: + key: + description: Key is an optional name of the field within 'path' that will be used to uniquely identify an image. Note - this field MUST be unique. + type: string + name: + description: Name is the entry the image will be available under 'images.<name>' in the context. If this field is not defined, image entries will appear under 'images.custom'. + type: string + path: + description: Path is the path to the object containing the image field in a custom resource. It should be slash-separated. Each slash-separated key must be a valid YAML key or a wildcard '*'. Wildcard keys are expanded in case of arrays or objects. + type: string + value: + description: Value is an optional name of the field within 'path' that points to the image URI. This is useful when a custom 'key' is also defined. + type: string + required: + - path + type: object + type: array + description: ImageExtractors defines a mapping from kinds to ImageExtractorConfigs. This config is only valid for verifyImages rules. + type: object + match: + description: MatchResources defines when this policy rule should be applied. The match criteria can include resource information (e.g. kind, name, namespace, labels) and admission review request information like the user name or role. At least one kind is required. + properties: + all: + description: All allows specifying resources which will be ANDed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + any: + description: Any allows specifying resources which will be ORed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + type: object + mutate: + description: Mutation is used to modify matching resources. + properties: + foreach: + description: ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + items: + description: ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + properties: + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + list: + description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied. + type: string + patchStrategicMerge: + description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. + x-kubernetes-preserve-unknown-fields: true + patchesJson6902: + description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. + type: string + preconditions: + description: 'AnyAllConditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + patchStrategicMerge: + description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. + x-kubernetes-preserve-unknown-fields: true + patchesJson6902: + description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. + type: string + targets: + description: Targets defines the target resources to be mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array + type: object + name: + description: Name is a label to identify the rule, It must be unique within the policy. + maxLength: 63 + type: string + preconditions: + description: 'Preconditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. A direct list of conditions (without `any` or `all` statements is supported for backwards compatibility but See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - AnyIn + - AllIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - AnyIn + - AllIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + validate: + description: Validation is used to validate matching resources. + properties: + anyPattern: + description: AnyPattern specifies list of validation patterns. At least one of the patterns must be satisfied for the validation rule to succeed. + x-kubernetes-preserve-unknown-fields: true + deny: + description: Deny defines conditions used to pass or fail a validation rule. + properties: + conditions: + description: 'Multiple conditions can be declared under an `any` or `all` statement. A direct list of conditions (without `any` or `all` statements) is also supported for backwards compatibility See: https://kyverno.io/docs/writing-policies/validate/#deny-rules' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - AnyIn + - AllIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - AnyIn + - AllIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + type: object + foreach: + description: ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + items: + description: ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + properties: + anyPattern: + description: AnyPattern specifies list of validation patterns. At least one of the patterns must be satisfied for the validation rule to succeed. + x-kubernetes-preserve-unknown-fields: true + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + deny: + description: Deny defines conditions used to pass or fail a validation rule. + properties: + conditions: + description: 'Multiple conditions can be declared under an `any` or `all` statement. A direct list of conditions (without `any` or `all` statements) is also supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules' + x-kubernetes-preserve-unknown-fields: true + type: object + elementScope: + description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree. + type: boolean + list: + description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied. + type: string + pattern: + description: Pattern specifies an overlay-style pattern used to check resources. + x-kubernetes-preserve-unknown-fields: true + preconditions: + description: 'AnyAllConditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + manifests: + description: Manifest specifies conditions for manifest verification + properties: + annotationDomain: + description: AnnotationDomain is custom domain of annotation for message and signature. Default is "cosign.sigstore.dev". + type: string + attestors: + description: Attestors specified the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + dryRun: + description: DryRun configuration + properties: + enable: + type: boolean + namespace: + type: string + type: object + ignoreFields: + description: Fields which will be ignored while comparing manifests. + items: + properties: + fields: + items: + type: string + type: array + objects: + items: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + type: object + type: array + type: object + type: array + repository: + description: Repository is an optional alternate OCI repository to use for resource bundle reference. The repository can be overridden per Attestor or Attestation. + type: string + type: object + message: + description: Message specifies a custom message to be displayed on failure. + type: string + pattern: + description: Pattern specifies an overlay-style pattern used to check resources. + x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes Pod Security admission by specifying exclusions for Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard controls to be excluded. + items: + description: PodSecurityStandard specifies the Pod Security Standard controls to be excluded. + properties: + controlName: + description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user + type: string + images: + description: 'Images selects matching containers and applies the container level PSS. Each image is the image name consisting of the registry address, repository, image, and tag. Empty list matches no containers, PSS checks are applied at the pod level only. Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.' + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard level to be applied to workloads. Allowed values are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard versions that Kubernetes supports. Allowed values are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object + type: object + verifyImages: + description: VerifyImages is used to verify image signatures and mutate them to add a digest + items: + description: ImageVerification validates that images that match the specified pattern are signed with the supplied public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. + properties: + attestations: + description: Attestations are optional checks for signed in-toto Statements used to verify the image. See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the OCI registry and decodes them into a list of Statement declarations. + items: + description: Attestation are checks for signed in-toto Statements that are used to verify the image. See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the OCI registry and decodes them into a list of Statements. + properties: + attestors: + description: Attestors specify the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + conditions: + description: Conditions are used to verify attributes within a Predicate. If no Conditions are specified the attestation check is satisfied as long there are predicates that match the predicate type. + items: + description: AnyAllConditions consists of conditions wrapped denoting a logical criteria to be fulfilled. AnyConditions get fulfilled when at least one of its sub-conditions passes. AllConditions get fulfilled only when all of its sub-conditions pass. + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + type: array + predicateType: + description: PredicateType defines the type of Predicate contained within the Statement. + type: string + required: + - predicateType + type: object + type: array + attestors: + description: Attestors specified the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + imageReferences: + description: 'ImageReferences is a list of matching image reference patterns. At least one pattern in the list must match the image for the rule to apply. Each image reference consists of a registry address (defaults to docker.io), repository, image, and tag (defaults to latest). Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.' + items: + type: string + type: array + mutateDigest: + default: true + description: MutateDigest enables replacement of image tags with digests. Defaults to true. + type: boolean + repository: + description: Repository is an optional alternate OCI repository to use for image signatures and attestations that match this rule. If specified Repository will override the default OCI image repository configured for the installation. The repository can also be overridden per Attestor or Attestation. + type: string + required: + default: true + description: Required validates that images are verified i.e. have matched passed a signature or attestation check. + type: boolean + verifyDigest: + default: true + description: VerifyDigest validates that images have a digest. + type: boolean + type: object + type: array + type: object + type: array + schemaValidation: + description: SchemaValidation skips validation checks for policies as well as patched resources. Optional. The default value is set to "true", it must be set to "false" to disable the validation checks. + type: boolean + validationFailureAction: + default: audit + description: ValidationFailureAction defines if a validation policy rule violation should block the admission review request (enforce), or allow (audit) the admission review request and report an error in a policy report. Optional. Allowed values are audit or enforce. The default value is "audit". + enum: + - audit + - enforce + type: string + validationFailureActionOverrides: + description: ValidationFailureActionOverrides is a Cluster Policy attribute that specifies ValidationFailureAction namespace-wise. It overrides ValidationFailureAction for the specified namespaces. + items: + properties: + action: + description: ValidationFailureAction defines the policy validation failure action + enum: + - audit + - enforce + type: string + namespaces: + items: + type: string + type: array + type: object + type: array + webhookTimeoutSeconds: + description: WebhookTimeoutSeconds specifies the maximum time in seconds allowed to apply this policy. After the configured time expires, the admission request may fail, or may simply ignore the policy results, based on the failure policy. The default timeout is 10s, the value must be between 1 and 30 seconds. + format: int32 + type: integer + type: object + status: + description: Status contains policy runtime data. + properties: + autogen: + description: Autogen contains autogen status information + properties: + rules: + description: Rules is a list of Rule instances. It contains auto generated rules added for pod controllers + items: + description: Rule defines a validation, mutation, or generation control for matching resources. Each rules contains a match declaration to select resources, and an optional exclude declaration to specify which resources to exclude. + properties: + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + exclude: + description: ExcludeResources defines when this policy rule should not be applied. The exclude criteria can include resource information (e.g. kind, name, namespace, labels) and admission review request information like the name or role. + properties: + all: + description: All allows specifying resources which will be ANDed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + any: + description: Any allows specifying resources which will be ORed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources. Specifying ResourceDescription directly under match is being deprecated. Please specify under "any" or "all" instead. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + generate: + description: Generation is used to create new resources. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + clone: + description: Clone specifies the source resource used to populate each generated resource. At most one of Data or Clone can be specified. If neither are provided, the generated resource will be created with default data only. + properties: + name: + description: Name specifies name of the resource. + type: string + namespace: + description: Namespace specifies source resource namespace. + type: string + type: object + cloneList: + description: CloneList specifies the list of source resource used to populate each generated resource. + properties: + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + namespace: + description: Namespace specifies source resource namespace. + type: string + selector: + description: Selector is a label selector. Label keys and values in `matchLabels`. wildcard characters are not supported. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + data: + description: Data provides the resource declaration used to populate each generated resource. At most one of Data or Clone must be specified. If neither are provided, the generated resource will be created with default data only. + x-kubernetes-preserve-unknown-fields: true + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + synchronize: + description: Synchronize controls if generated resources should be kept in-sync with their source resource. If Synchronize is set to "true" changes to generated resources will be overwritten with resource data from Data or the resource specified in the Clone declaration. Optional. Defaults to "false" if not specified. + type: boolean + type: object + imageExtractors: + additionalProperties: + items: + properties: + key: + description: Key is an optional name of the field within 'path' that will be used to uniquely identify an image. Note - this field MUST be unique. + type: string + name: + description: Name is the entry the image will be available under 'images.<name>' in the context. If this field is not defined, image entries will appear under 'images.custom'. + type: string + path: + description: Path is the path to the object containing the image field in a custom resource. It should be slash-separated. Each slash-separated key must be a valid YAML key or a wildcard '*'. Wildcard keys are expanded in case of arrays or objects. + type: string + value: + description: Value is an optional name of the field within 'path' that points to the image URI. This is useful when a custom 'key' is also defined. + type: string + required: + - path + type: object + type: array + description: ImageExtractors defines a mapping from kinds to ImageExtractorConfigs. This config is only valid for verifyImages rules. + type: object + match: + description: MatchResources defines when this policy rule should be applied. The match criteria can include resource information (e.g. kind, name, namespace, labels) and admission review request information like the user name or role. At least one kind is required. + properties: + all: + description: All allows specifying resources which will be ANDed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + any: + description: Any allows specifying resources which will be ORed + items: + description: ResourceFilter allow users to "AND" or "OR" between resources + properties: + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: array + clusterRoles: + description: ClusterRoles is the list of cluster-wide role names for the user. + items: + type: string + type: array + resources: + description: ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources. Specifying ResourceDescription directly under match is being deprecated. Please specify under "any" or "all" instead. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a map of annotations (key-value pairs of type string). Annotation keys and values support the wildcard characters "*" (matches zero or many characters) and "?" (matches at least one character). + type: object + kinds: + description: Kinds is a list of resource kinds. + items: + type: string + type: array + name: + description: 'Name is the name of the resource. The name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). NOTE: "Name" is being deprecated in favor of "Names".' + type: string + names: + description: Names are the names of the resources. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + namespaceSelector: + description: 'NamespaceSelector is a label selector for the resource namespace. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character).Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Namespaces is a list of namespaces names. Each name supports wildcard characters "*" (matches zero or many characters) and "?" (at least one character). + items: + type: string + type: array + selector: + description: 'Selector is a label selector. Label keys and values in `matchLabels` support the wildcard characters `*` (matches zero or many characters) and `?` (matches one character). Wildcards allows writing label selectors like ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but does not match an empty label set.' + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + roles: + description: Roles is the list of namespaced role names for the user. + items: + type: string + type: array + subjects: + description: Subjects is the list of subject names like users, user groups, and service accounts. + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + type: object + mutate: + description: Mutation is used to modify matching resources. + properties: + foreach: + description: ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + items: + description: ForEach applies mutation rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + properties: + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + list: + description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied. + type: string + patchStrategicMerge: + description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. + x-kubernetes-preserve-unknown-fields: true + patchesJson6902: + description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. + type: string + preconditions: + description: 'AnyAllConditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + patchStrategicMerge: + description: PatchStrategicMerge is a strategic merge patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/. + x-kubernetes-preserve-unknown-fields: true + patchesJson6902: + description: PatchesJSON6902 is a list of RFC 6902 JSON Patch declarations used to modify resources. See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/. + type: string + targets: + description: Targets defines the target resources to be mutated. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array + type: object + name: + description: Name is a label to identify the rule, It must be unique within the policy. + maxLength: 63 + type: string + preconditions: + description: 'Preconditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. A direct list of conditions (without `any` or `all` statements is supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/preconditions/' + x-kubernetes-preserve-unknown-fields: true + validate: + description: Validation is used to validate matching resources. + properties: + anyPattern: + description: AnyPattern specifies list of validation patterns. At least one of the patterns must be satisfied for the validation rule to succeed. + x-kubernetes-preserve-unknown-fields: true + deny: + description: Deny defines conditions used to pass or fail a validation rule. + properties: + conditions: + description: 'Multiple conditions can be declared under an `any` or `all` statement. A direct list of conditions (without `any` or `all` statements) is also supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules' + x-kubernetes-preserve-unknown-fields: true + type: object + foreach: + description: ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + items: + description: ForEach applies validate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic. + properties: + anyPattern: + description: AnyPattern specifies list of validation patterns. At least one of the patterns must be satisfied for the validation rule to succeed. + x-kubernetes-preserve-unknown-fields: true + context: + description: Context defines variables and data sources that can be used during rule execution. + items: + description: ContextEntry adds variables and data sources to a rule Context. Either a ConfigMap reference or a APILookup must be provided. + properties: + apiCall: + description: APICall defines an HTTP request to the Kubernetes API server. The JSON data retrieved is stored in the context. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the JSON response returned from the API server. For example a JMESPath of "items | length(@)" applied to the API server response to the URLPath "/apis/apps/v1/deployments" will return the total count of deployments across all namespaces. + type: string + urlPath: + description: URLPath is the URL path to be used in the HTTP GET request to the Kubernetes API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments"). The format required is the same format used by the `kubectl get --raw` command. + type: string + required: + - urlPath + type: object + configMap: + description: ConfigMap is the ConfigMap reference. + properties: + name: + description: Name is the ConfigMap name. + type: string + namespace: + description: Namespace is the ConfigMap namespace. + type: string + required: + - name + type: object + imageRegistry: + description: ImageRegistry defines requests to an OCI/Docker V2 registry to fetch image details. + properties: + jmesPath: + description: JMESPath is an optional JSON Match Expression that can be used to transform the ImageData struct returned as a result of processing the image reference. + type: string + reference: + description: 'Reference is image reference to a container image in the registry. Example: ghcr.io/kyverno/kyverno:latest' + type: string + required: + - reference + type: object + name: + description: Name is the variable name. + type: string + variable: + description: Variable defines an arbitrary JMESPath context variable that can be defined inline. + properties: + default: + description: Default is an optional arbitrary JSON object that the variable may take if the JMESPath expression evaluates to nil + x-kubernetes-preserve-unknown-fields: true + jmesPath: + description: JMESPath is an optional JMESPath Expression that can be used to transform the variable. + type: string + value: + description: Value is any arbitrary JSON object representable in YAML or JSON form. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + type: array + deny: + description: Deny defines conditions used to pass or fail a validation rule. + properties: + conditions: + description: 'Multiple conditions can be declared under an `any` or `all` statement. A direct list of conditions (without `any` or `all` statements) is also supported for backwards compatibility but will be deprecated in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules' + x-kubernetes-preserve-unknown-fields: true + type: object + elementScope: + description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree. + type: boolean + list: + description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied. + type: string + pattern: + description: Pattern specifies an overlay-style pattern used to check resources. + x-kubernetes-preserve-unknown-fields: true + preconditions: + description: 'AnyAllConditions are used to determine if a policy rule should be applied by evaluating a set of conditions. The declaration can contain nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/' + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + manifests: + description: Manifest specifies conditions for manifest verification + properties: + annotationDomain: + description: AnnotationDomain is custom domain of annotation for message and signature. Default is "cosign.sigstore.dev". + type: string + attestors: + description: Attestors specified the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + dryRun: + description: DryRun configuration + properties: + enable: + type: boolean + namespace: + type: string + type: object + ignoreFields: + description: Fields which will be ignored while comparing manifests. + items: + properties: + fields: + items: + type: string + type: array + objects: + items: + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + version: + type: string + type: object + type: array + type: object + type: array + repository: + description: Repository is an optional alternate OCI repository to use for resource bundle reference. The repository can be overridden per Attestor or Attestation. + type: string + type: object + message: + description: Message specifies a custom message to be displayed on failure. + type: string + pattern: + description: Pattern specifies an overlay-style pattern used to check resources. + x-kubernetes-preserve-unknown-fields: true + podSecurity: + description: PodSecurity applies exemptions for Kubernetes Pod Security admission by specifying exclusions for Pod Security Standards controls. + properties: + exclude: + description: Exclude specifies the Pod Security Standard controls to be excluded. + items: + description: PodSecurityStandard specifies the Pod Security Standard controls to be excluded. + properties: + controlName: + description: 'ControlName specifies the name of the Pod Security Standard control. See: https://kubernetes.io/docs/concepts/security/pod-security-standards/' + enum: + - HostProcess + - Host Namespaces + - Privileged Containers + - Capabilities + - HostPath Volumes + - Host Ports + - AppArmor + - SELinux + - /proc Mount Type + - Seccomp + - Sysctls + - Volume Types + - Privilege Escalation + - Running as Non-root + - Running as Non-root user + type: string + images: + description: 'Images selects matching containers and applies the container level PSS. Each image is the image name consisting of the registry address, repository, image, and tag. Empty list matches no containers, PSS checks are applied at the pod level only. Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.' + items: + type: string + type: array + required: + - controlName + type: object + type: array + level: + description: Level defines the Pod Security Standard level to be applied to workloads. Allowed values are privileged, baseline, and restricted. + enum: + - privileged + - baseline + - restricted + type: string + version: + description: Version defines the Pod Security Standard versions that Kubernetes supports. Allowed values are v1.19, v1.20, v1.21, v1.22, v1.23, v1.24, v1.25, latest. Defaults to latest. + enum: + - v1.19 + - v1.20 + - v1.21 + - v1.22 + - v1.23 + - v1.24 + - v1.25 + - latest + type: string + type: object + type: object + verifyImages: + description: VerifyImages is used to verify image signatures and mutate them to add a digest + items: + description: ImageVerification validates that images that match the specified pattern are signed with the supplied public key. Once the image is verified it is mutated to include the SHA digest retrieved during the registration. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. Deprecated. + type: object + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. Deprecated. Use annotations per Attestor instead. + type: object + attestations: + description: Attestations are optional checks for signed in-toto Statements used to verify the image. See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the OCI registry and decodes them into a list of Statement declarations. + items: + description: Attestation are checks for signed in-toto Statements that are used to verify the image. See https://github.com/in-toto/attestation. Kyverno fetches signed attestations from the OCI registry and decodes them into a list of Statements. + properties: + attestors: + description: Attestors specify the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + conditions: + description: Conditions are used to verify attributes within a Predicate. If no Conditions are specified the attestation check is satisfied as long there are predicates that match the predicate type. + items: + description: AnyAllConditions consists of conditions wrapped denoting a logical criteria to be fulfilled. AnyConditions get fulfilled when at least one of its sub-conditions passes. AllConditions get fulfilled only when all of its sub-conditions pass. + properties: + all: + description: AllConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, all of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + any: + description: AnyConditions enable variable-based conditional rule execution. This is useful for finer control of when an rule is applied. A condition can reference object data using JMESPath notation. Here, at least one of the conditions need to pass + items: + description: Condition defines variable-based conditional criteria for rule execution. + properties: + key: + description: Key is the context entry (using JMESPath) for conditional rule evaluation. + x-kubernetes-preserve-unknown-fields: true + operator: + description: 'Operator is the conditional operation to perform. Valid operators are: Equals, NotEquals, In, AnyIn, AllIn, NotIn, AnyNotIn, AllNotIn, GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, DurationGreaterThanOrEquals, DurationGreaterThan, DurationLessThanOrEquals, DurationLessThan' + enum: + - Equals + - NotEquals + - In + - AnyIn + - AllIn + - NotIn + - AnyNotIn + - AllNotIn + - GreaterThanOrEquals + - GreaterThan + - LessThanOrEquals + - LessThan + - DurationGreaterThanOrEquals + - DurationGreaterThan + - DurationLessThanOrEquals + - DurationLessThan + type: string + value: + description: Value is the conditional value, or set of values. The values can be fixed set or can be variables declared using JMESPath. + x-kubernetes-preserve-unknown-fields: true + type: object + type: array + type: object + type: array + predicateType: + description: PredicateType defines the type of Predicate contained within the Statement. + type: string + required: + - predicateType + type: object + type: array + attestors: + description: Attestors specified the required attestors (i.e. authorities) + items: + properties: + count: + description: Count specifies the required number of entries that must match. If the count is null, all entries must match (a logical AND). If the count is 1, at least one entry must match (a logical OR). If the count contains a value N, then N must be less than or equal to the size of entries, and at least N entries must match. + minimum: 1 + type: integer + entries: + description: Entries contains the available attestors. An attestor can be a static key, attributes for keyless verification, or a nested attestor declaration. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations are used for image verification. Every specified key-value pair must exist and match in the verified payload. The payload may contain other key-value pairs. + type: object + attestor: + description: Attestor is a nested AttestorSet used to specify a more complex set of match authorities + x-kubernetes-preserve-unknown-fields: true + certificates: + description: Certificates specifies one or more certificates + properties: + cert: + description: Certificate is an optional PEM encoded public certificate. + type: string + certChain: + description: CertificateChain is an optional PEM encoded set of certificates used to verify + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + keyless: + description: Keyless is a set of attribute used to verify a Sigstore keyless attestor. See https://github.com/sigstore/cosign/blob/main/KEYLESS.md. + properties: + additionalExtensions: + additionalProperties: + type: string + description: AdditionalExtensions are certificate-extensions used for keyless signing. + type: object + issuer: + description: Issuer is the certificate issuer used for keyless signing. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked and a root certificate chain is expected instead. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + roots: + description: Roots is an optional set of PEM encoded trusted root certificates. If not provided, the system roots are used. + type: string + subject: + description: Subject is the verified identity used for keyless signing, for example the email address + type: string + type: object + keys: + description: Keys specifies one or more public keys + properties: + publicKeys: + description: Keys is a set of X.509 public keys used to verify image signatures. The keys can be directly specified or can be a variable reference to a key specified in a ConfigMap (see https://kyverno.io/docs/writing-policies/variables/). When multiple keys are specified each key is processed as a separate staticKey entry (.attestors[*].entries.keys) within the set of attestors and the count is applied across the keys. + type: string + rekor: + description: Rekor provides configuration for the Rekor transparency log service. If the value is nil, Rekor is not checked. If an empty object is provided the public instance of Rekor (https://rekor.sigstore.dev) is used. + properties: + url: + description: URL is the address of the transparency log. Defaults to the public log https://rekor.sigstore.dev. + type: string + required: + - url + type: object + type: object + repository: + description: Repository is an optional alternate OCI repository to use for signatures and attestations that match this rule. If specified Repository will override other OCI image repository locations for this Attestor. + type: string + type: object + type: array + type: object + type: array + image: + description: 'Image is the image name consisting of the registry address, repository, image, and tag. Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images. Deprecated. Use ImageReferences instead.' + type: string + imageReferences: + description: 'ImageReferences is a list of matching image reference patterns. At least one pattern in the list must match the image for the rule to apply. Each image reference consists of a registry address (defaults to docker.io), repository, image, and tag (defaults to latest). Wildcards (''*'' and ''?'') are allowed. See: https://kubernetes.io/docs/concepts/containers/images.' + items: + type: string + type: array + issuer: + description: Issuer is the certificate issuer used for keyless signing. Deprecated. Use KeylessAttestor instead. + type: string + key: + description: Key is the PEM encoded public key that the image or attestation is signed with. Deprecated. Use StaticKeyAttestor instead. + type: string + mutateDigest: + default: true + description: MutateDigest enables replacement of image tags with digests. Defaults to true. + type: boolean + repository: + description: Repository is an optional alternate OCI repository to use for image signatures and attestations that match this rule. If specified Repository will override the default OCI image repository configured for the installation. The repository can also be overridden per Attestor or Attestation. + type: string + required: + default: true + description: Required validates that images are verified i.e. have matched passed a signature or attestation check. + type: boolean + roots: + description: Roots is the PEM encoded Root certificate chain used for keyless signing Deprecated. Use KeylessAttestor instead. + type: string + subject: + description: Subject is the identity used for keyless signing, for example an email address Deprecated. Use KeylessAttestor instead. + type: string + verifyDigest: + default: true + description: VerifyDigest validates that images have a digest. + type: boolean + type: object + type: array + type: object + type: array + type: object + conditions: + description: Conditions is a list of conditions that apply to the policy + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + ready: + description: Ready indicates if the policy is ready to serve the admission request. Deprecated in favor of Conditions + type: boolean + required: + - ready + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + config.kubernetes.io/index: '9' + internal.config.kubernetes.io/index: '9' + {{- trim (include "kyverno.crdAnnotations" .) | nindent 4 }} + creationTimestamp: null + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: kyverno + app.kubernetes.io/name: kyverno + app.kubernetes.io/part-of: kyverno + app.kubernetes.io/version: '{{.Chart.AppVersion}}' + name: policyreports.wgpolicyk8s.io +spec: + group: wgpolicyk8s.io + names: + kind: PolicyReport + listKind: PolicyReportList + plural: policyreports + shortNames: + - polr + singular: policyreport + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .scope.kind + name: Kind + priority: 1 + type: string + - jsonPath: .scope.name + name: Name + priority: 1 + type: string + - jsonPath: .summary.pass + name: Pass + type: integer + - jsonPath: .summary.fail + name: Fail + type: integer + - jsonPath: .summary.warn + name: Warn + type: integer + - jsonPath: .summary.error + name: Error + type: integer + - jsonPath: .summary.skip + name: Skip + type: integer + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: PolicyReport is the Schema for the policyreports API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + results: + description: PolicyReportResult provides result details + items: + description: PolicyReportResult provides the result for an individual policy + properties: + category: + description: Category indicates policy category + type: string + message: + description: Description is a short user friendly message for the policy rule + type: string + policy: + description: Policy is the name or identifier of the policy + type: string + properties: + additionalProperties: + type: string + description: Properties provides additional information for the policy rule + type: object + resourceSelector: + description: SubjectSelector is an optional label selector for checked Kubernetes resources. For example, a policy result may apply to all pods that match a label. Either a Subject or a SubjectSelector can be specified. If neither are provided, the result is assumed to be for the policy report scope. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + resources: + description: Subjects is an optional reference to the checked Kubernetes resources + items: + description: "ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, \"must refer only to types A and B\" or \"UID not honored\" or \"name must be restricted\". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. \n Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 ." + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + type: object + x-kubernetes-map-type: atomic + type: array + result: + description: Result indicates the outcome of the policy rule execution + enum: + - pass + - fail + - warn + - error + - skip + type: string + rule: + description: Rule is the name or identifier of the rule within the policy + type: string + scored: + description: Scored indicates if this result is scored + type: boolean + severity: + description: Severity indicates policy check result criticality + enum: + - critical + - high + - low + - medium + - info + type: string + source: + description: Source is an identifier for the policy engine that manages this report + type: string + timestamp: + description: Timestamp indicates the time the result was found + properties: + nanos: + description: Non-negative fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values that count forward in time. Must be from 0 to 999,999,999 inclusive. This field may be limited in precision depending on context. + format: int32 + type: integer + seconds: + description: Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive. + format: int64 + type: integer + required: + - nanos + - seconds + type: object + required: + - policy + type: object + type: array + scope: + description: Scope is an optional reference to the report scope (e.g. a Deployment, Namespace, or Node) + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + type: object + x-kubernetes-map-type: atomic + scopeSelector: + description: ScopeSelector is an optional selector for multiple scopes (e.g. Pods). Either one of, or none of, but not both of, Scope or ScopeSelector should be specified. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + summary: + description: PolicyReportSummary provides a summary of results + properties: + error: + description: Error provides the count of policies that could not be evaluated + type: integer + fail: + description: Fail provides the count of policies whose requirements were not met + type: integer + pass: + description: Pass provides the count of policies whose requirements were met + type: integer + skip: + description: Skip indicates the count of policies that were not selected for evaluation + type: integer + warn: + description: Warn provides the count of non-scored policies whose requirements were not met + type: integer + type: object + type: object + served: true + storage: true + subresources: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + config.kubernetes.io/index: '10' + internal.config.kubernetes.io/index: '10' + {{- trim (include "kyverno.crdAnnotations" .) | nindent 4 }} + creationTimestamp: null + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: kyverno + app.kubernetes.io/name: kyverno + app.kubernetes.io/part-of: kyverno + app.kubernetes.io/version: '{{.Chart.AppVersion}}' + name: updaterequests.kyverno.io +spec: + group: kyverno.io + names: + categories: + - kyverno + kind: UpdateRequest + listKind: UpdateRequestList + plural: updaterequests + shortNames: + - ur + singular: updaterequest + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.policy + name: Policy + type: string + - jsonPath: .spec.requestType + name: RuleType + type: string + - jsonPath: .spec.resource.kind + name: ResourceKind + type: string + - jsonPath: .spec.resource.name + name: ResourceName + type: string + - jsonPath: .spec.resource.namespace + name: ResourceNamespace + type: string + - jsonPath: .status.state + name: status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: UpdateRequest is a request to process mutate and generate rules in background. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec is the information to identify the update request. + properties: + context: + description: Context ... + properties: + admissionRequestInfo: + description: AdmissionRequestInfoObject stores the admission request and operation details + properties: + admissionRequest: + description: AdmissionRequest describes the admission.Attributes for the admission request. + properties: + dryRun: + description: DryRun indicates that modifications will definitely not be persisted for this request. Defaults to false. + type: boolean + kind: + description: Kind is the fully-qualified type of object being submitted (for example, v1.Pod or autoscaling.v1.Scale) + properties: + group: + type: string + kind: + type: string + version: + type: string + required: + - group + - kind + - version + type: object + name: + description: Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and rely on the server to generate the name. If that is the case, this field will contain an empty string. + type: string + namespace: + description: Namespace is the namespace associated with the request (if any). + type: string + object: + description: Object is the object from the incoming request. + type: object + x-kubernetes-preserve-unknown-fields: true + oldObject: + description: OldObject is the existing object. Only populated for DELETE and UPDATE requests. + type: object + x-kubernetes-preserve-unknown-fields: true + operation: + description: Operation is the operation being performed. This may be different than the operation requested. e.g. a patch can result in either a CREATE or UPDATE Operation. + type: string + options: + description: Options is the operation option structure of the operation being performed. e.g. `meta.k8s.io/v1.DeleteOptions` or `meta.k8s.io/v1.CreateOptions`. This may be different than the options the caller provided. e.g. for a patch request the performed Operation might be a CREATE, in which case the Options will a `meta.k8s.io/v1.CreateOptions` even though the caller provided `meta.k8s.io/v1.PatchOptions`. + type: object + x-kubernetes-preserve-unknown-fields: true + requestKind: + description: "RequestKind is the fully-qualified type of the original API request (for example, v1.Pod or autoscaling.v1.Scale). If this is specified and differs from the value in \"kind\", an equivalent match and conversion was performed. \n For example, if deployments can be modified via apps/v1 and apps/v1beta1, and a webhook registered a rule of `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]` and `matchPolicy: Equivalent`, an API request to apps/v1beta1 deployments would be converted and sent to the webhook with `kind: {group:\"apps\", version:\"v1\", kind:\"Deployment\"}` (matching the rule the webhook registered for), and `requestKind: {group:\"apps\", version:\"v1beta1\", kind:\"Deployment\"}` (indicating the kind of the original API request). \n See documentation for the \"matchPolicy\" field in the webhook configuration type for more details." + properties: + group: + type: string + kind: + type: string + version: + type: string + required: + - group + - kind + - version + type: object + requestResource: + description: "RequestResource is the fully-qualified resource of the original API request (for example, v1.pods). If this is specified and differs from the value in \"resource\", an equivalent match and conversion was performed. \n For example, if deployments can be modified via apps/v1 and apps/v1beta1, and a webhook registered a rule of `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]` and `matchPolicy: Equivalent`, an API request to apps/v1beta1 deployments would be converted and sent to the webhook with `resource: {group:\"apps\", version:\"v1\", resource:\"deployments\"}` (matching the resource the webhook registered for), and `requestResource: {group:\"apps\", version:\"v1beta1\", resource:\"deployments\"}` (indicating the resource of the original API request). \n See documentation for the \"matchPolicy\" field in the webhook configuration type." + properties: + group: + type: string + resource: + type: string + version: + type: string + required: + - group + - resource + - version + type: object + requestSubResource: + description: RequestSubResource is the name of the subresource of the original API request, if any (for example, "status" or "scale") If this is specified and differs from the value in "subResource", an equivalent match and conversion was performed. See documentation for the "matchPolicy" field in the webhook configuration type. + type: string + resource: + description: Resource is the fully-qualified resource being requested (for example, v1.pods) + properties: + group: + type: string + resource: + type: string + version: + type: string + required: + - group + - resource + - version + type: object + subResource: + description: SubResource is the subresource being requested, if any (for example, "status" or "scale") + type: string + uid: + description: UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are otherwise identical (parallel requests, requests when earlier requests did not modify etc) The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request. It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging. + type: string + userInfo: + description: UserInfo is information about the requesting user + properties: + extra: + additionalProperties: + description: ExtraValue masks the value so protobuf can generate + items: + type: string + type: array + description: Any additional information provided by the authenticator. + type: object + groups: + description: The names of groups this user is a part of. + items: + type: string + type: array + uid: + description: A unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs. + type: string + username: + description: The name that uniquely identifies this user among all active users. + type: string + type: object + required: + - kind + - operation + - resource + - uid + - userInfo + type: object + operation: + description: Operation is the type of resource operation being checked for admission control + type: string + type: object + userInfo: + description: RequestInfo contains permission info carried in an admission request. + properties: + clusterRoles: + description: ClusterRoles is a list of possible clusterRoles send the request. + items: + type: string + nullable: true + type: array + roles: + description: Roles is a list of possible role send the request. + items: + type: string + nullable: true + type: array + userInfo: + description: UserInfo is the userInfo carried in the admission request. + properties: + extra: + additionalProperties: + description: ExtraValue masks the value so protobuf can generate + items: + type: string + type: array + description: Any additional information provided by the authenticator. + type: object + groups: + description: The names of groups this user is a part of. + items: + type: string + type: array + uid: + description: A unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs. + type: string + username: + description: The name that uniquely identifies this user among all active users. + type: string + type: object + type: object + type: object + policy: + description: Specifies the name of the policy. + type: string + requestType: + description: Type represents request type for background processing + enum: + - mutate + - generate + type: string + resource: + description: ResourceSpec is the information to identify the update request. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + required: + - context + - policy + - resource + type: object + status: + description: Status contains statistics related to update request. + properties: + generatedResources: + description: This will track the resources that are updated by the generate Policy. Will be used during clean up resources. + items: + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + type: object + type: array + handler: + description: Handler represents the instance ID that handles the UR + type: string + message: + description: Specifies request status message. + type: string + state: + description: State represents state of the update request. + type: string + required: + - state + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/deployment.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/deployment.yaml new file mode 100644 index 000000000..6c83eb580 --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/deployment.yaml @@ -0,0 +1,167 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "kyverno.fullname" . }} + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno + namespace: {{ template "kyverno.namespace" . }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: {{ include "kyverno.matchLabels" . | nindent 6 }} + {{- if .Values.updateStrategy }} + strategy: + {{ toYaml .Values.updateStrategy | nindent 4 | trim }} + {{- end }} + template: + metadata: + labels: {{ include "kyverno.labels" . | nindent 8 }} + app: kyverno + {{- range $key, $value := .Values.podLabels }} + {{ $key }}: {{ $value }} + {{- end }} + {{- with .Values.podAnnotations }} + annotations: {{ tpl (toYaml .) $ | nindent 8 }} + {{- end }} + spec: + {{- with .Values.image.pullSecrets }} + imagePullSecrets: {{ tpl (toYaml .) $ | nindent 8 }} + {{- end }} + {{- with .Values.podSecurityContext }} + securityContext: {{ tpl (toYaml .) $ | nindent 8 }} + {{- end }} + {{- if or .Values.antiAffinity.enable .Values.podAffinity .Values.nodeAffinity }} + affinity: + {{- if and .Values.antiAffinity.enable .Values.podAntiAffinity }} + podAntiAffinity: + {{- toYaml .Values.podAntiAffinity | nindent 10 }} + {{- end }} + {{- if .Values.podAffinity }} + podAffinity: + {{- toYaml .Values.podAffinity | nindent 10 }} + {{- end }} + {{- if .Values.nodeAffinity }} + nodeAffinity: + {{- toYaml .Values.nodeAffinity | nindent 10 }} + {{- end }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: {{ tpl (toYaml .) $ | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: {{ tpl (toYaml .) $ | nindent 8 }} + {{- end }} + {{- with .Values.topologySpreadConstraints }} + topologySpreadConstraints: {{ tpl (toYaml .) $ | nindent 8 }} + {{- end }} + serviceAccountName: {{ template "kyverno.serviceAccountName" . }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName | quote }} + {{- end }} + {{- if .Values.hostNetwork }} + hostNetwork: {{ .Values.hostNetwork }} + {{- end }} + {{- if .Values.dnsPolicy }} + dnsPolicy: {{ .Values.dnsPolicy }} + {{- end }} + initContainers: + {{- if .Values.extraInitContainers }} + {{- toYaml .Values.extraInitContainers | nindent 8 }} + {{- end }} + - name: kyverno-pre + image: {{ include "kyverno.image" (dict "image" .Values.initImage "defaultTag" (default .Chart.AppVersion .Values.image.tag)) | quote }} + imagePullPolicy: {{ default .Values.image.pullPolicy .Values.initImage.pullPolicy }} + {{- if .Values.initContainer.extraArgs }} + args: + {{ tpl (toYaml .Values.initContainer.extraArgs) . }} + {{- end }} + {{- with .Values.initResources }} + resources: {{ tpl (toYaml .) $ | nindent 12 }} + {{- end }} + {{- if .Values.securityContext }} + securityContext: {{ include "kyverno.securityContext" . | nindent 12 }} + {{- end }} + env: + - name: METRICS_CONFIG + value: {{ template "kyverno.metricsConfigMapName" . }} + - name: KYVERNO_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: KYVERNO_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: KYVERNO_DEPLOYMENT + value: {{ template "kyverno.fullname" . }} + {{- with .Values.envVarsInit }} + {{- toYaml . | nindent 10 }} + {{- end }} + containers: + {{- if .Values.extraContainers }} + {{- toYaml .Values.extraContainers | nindent 8 }} + {{- end }} + - name: kyverno + image: {{ include "kyverno.image" (dict "image" .Values.image "defaultTag" .Chart.AppVersion) | quote }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if or .Values.extraArgs .Values.imagePullSecrets }} + args: + {{- if .Values.extraArgs -}} + {{ tpl (toYaml .Values.extraArgs) . | nindent 12 }} + {{- end }} + {{- if .Values.imagePullSecrets }} + - --imagePullSecrets={{ keys .Values.imagePullSecrets | join "," }} + {{- end }} + {{- end }} + {{- with .Values.resources }} + resources: {{ tpl (toYaml .) $ | nindent 12 }} + {{- end }} + {{- if .Values.securityContext }} + securityContext: {{ include "kyverno.securityContext" . | nindent 12 }} + {{- end }} + ports: + - containerPort: 9443 + name: https + protocol: TCP + - containerPort: 8000 + name: metrics-port + protocol: TCP + env: + - name: INIT_CONFIG + value: {{ template "kyverno.configMapName" . }} + - name: METRICS_CONFIG + value: {{ template "kyverno.metricsConfigMapName" . }} + - name: KYVERNO_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: KYVERNO_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: KYVERNO_SERVICEACCOUNT_NAME + value: {{ template "kyverno.serviceAccountName" . }} + - name: KYVERNO_SVC + value: {{ template "kyverno.serviceName" . }} + - name: TUF_ROOT + value: {{ .Values.tufRootMountPath }} + {{- with .Values.envVars }} + {{- toYaml . | nindent 10 }} + {{- end }} + - name: KYVERNO_DEPLOYMENT + value: {{ template "kyverno.fullname" . }} + {{- with .Values.startupProbe }} + startupProbe: {{ tpl (toYaml .) $ | nindent 12 }} + {{- end }} + {{- with .Values.livenessProbe }} + livenessProbe: {{ tpl (toYaml .) $ | nindent 12 }} + {{- end }} + {{- with .Values.readinessProbe }} + readinessProbe: {{ tpl (toYaml .) $ | nindent 12 }} + {{- end }} + volumeMounts: + - mountPath: {{ .Values.tufRootMountPath }} + name: sigstore + volumes: + - name: sigstore + emptyDir: {} \ No newline at end of file diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/grafana.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/grafana.yaml new file mode 100644 index 000000000..7f4fb3eea --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/grafana.yaml @@ -0,0 +1,13 @@ +{{- if .Values.grafana.enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "kyverno.configMapName" . }}-grafana + namespace: {{ default (include "kyverno.namespace" .) .Values.grafana.namespace }} + annotations: + {{- toYaml .Values.grafana.annotations | nindent 4 }} + labels: + grafana_dashboard: "1" +data: +{{ (.Files.Glob "grafana/*").AsConfig | indent 2 }} +{{- end -}} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/helm-pre-delete-hook.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/helm-pre-delete-hook.yaml new file mode 100644 index 000000000..d3004bdd7 --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/helm-pre-delete-hook.yaml @@ -0,0 +1,29 @@ +{{- if .Values.webhooksCleanup.enable }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ template "kyverno.fullname" . }}-hook-pre-delete + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno + annotations: + "helm.sh/hook": pre-delete + "helm.sh/hook-delete-policy": hook-succeeded,hook-failed +spec: + template: + spec: + serviceAccount: {{ template "kyverno.serviceAccountName" . }} + containers: + - name: kubectl + image: {{ .Values.webhooksCleanup.image }} + command: + - sh + - '-c' + - >- + kubectl delete validatingwebhookconfiguration kyverno-policy-validating-webhook-cfg; + kubectl delete validatingwebhookconfiguration kyverno-resource-validating-webhook-cfg; + kubectl delete mutatingwebhookconfiguration kyverno-policy-mutating-webhook-cfg; + kubectl delete mutatingwebhookconfiguration kyverno-resource-mutating-webhook-cfg; + kubectl delete mutatingwebhookconfiguration kyverno-verify-mutating-webhook-cfg; + restartPolicy: Never + backoffLimit: 2 +{{- end }} \ No newline at end of file diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/imagepullsecret.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/imagepullsecret.yaml new file mode 100644 index 000000000..d6869ef35 --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/imagepullsecret.yaml @@ -0,0 +1,13 @@ +{{ range $name, $secret := .Values.imagePullSecrets }} +--- +apiVersion: v1 +kind: Secret +metadata: + labels: {{ include "kyverno.labels" $ | nindent 4 }} + app: kyverno + name: {{ $name }} + namespace: {{ template "kyverno.namespace" $ }} +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: {{ template "kyverno.imagePullSecret" $secret }} +{{ end }} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/metricsconfigmap.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/metricsconfigmap.yaml new file mode 100644 index 000000000..0e08473fa --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/metricsconfigmap.yaml @@ -0,0 +1,26 @@ +{{- if (not .Values.config.existingMetricsConfig) }} +apiVersion: v1 +kind: ConfigMap +metadata: + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno + {{- with .Values.config.metricsConfig.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ template "kyverno.metricsConfigMapName" . }} + namespace: {{ template "kyverno.namespace" . }} +{{- if .Values.config.metricsConfig }} +data: + {{- if .Values.config.metricsConfig.namespaces }} + namespaces: {{ .Values.config.metricsConfig.namespaces | toJson | quote }} + {{- end -}} + {{- if .Values.config.metricsConfig.metricsRefreshInterval }} + metricsRefreshInterval: {{ .Values.config.metricsConfig.metricsRefreshInterval }} + {{- end -}} +{{- else }} +data: + namespaces: '{"include": [], "exclude": []}' + metricsRefreshInterval: 0s +{{- end }} +{{- end -}} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/networkpolicy.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/networkpolicy.yaml new file mode 100644 index 000000000..0f0433384 --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/networkpolicy.yaml @@ -0,0 +1,33 @@ +{{- if .Values.networkPolicy.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno + name: {{ template "kyverno.fullname" . }} + namespace: {{ template "kyverno.namespace" . }} +spec: + podSelector: + matchLabels: + app: kyverno + policyTypes: + - Ingress + {{- if .Values.networkPolicy.ingressFrom }} + ingress: + - from: + {{- with .Values.networkPolicy.ingressFrom }} + {{- toYaml . | nindent 4 }} + {{- end }} + ports: + - protocol: TCP + port: 9443 # webhook access + # Allow prometheus scrapes for metrics + {{- if .Values.metricsService.create }} + - protocol: TCP + port: {{ .Values.metricsService.port }} + {{- end }} + {{- else }} + ingress: + - {} + {{- end }} +{{- end }} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/poddisruptionbudget.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/poddisruptionbudget.yaml new file mode 100644 index 000000000..ac4b95e85 --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/poddisruptionbudget.yaml @@ -0,0 +1,18 @@ +{{- if (gt (int .Values.replicaCount) 1) }} +{{- if .Capabilities.APIVersions.Has "policy/v1/PodDisruptionBudget" }} +apiVersion: policy/v1 +{{- else }} +apiVersion: policy/v1beta1 +{{- end }} +kind: PodDisruptionBudget +metadata: + name: {{ template "kyverno.fullname" . }} + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno + namespace: {{ template "kyverno.namespace" . }} +spec: +{{- include "podDisruptionBudget.spec" . | indent 2 }} + selector: + matchLabels: {{ include "kyverno.matchLabels" . | nindent 6 }} + app: kyverno +{{- end }} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/role.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/role.yaml new file mode 100644 index 000000000..9ae4fe12f --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/role.yaml @@ -0,0 +1,32 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "kyverno.fullname" . }}:leaderelection + namespace: {{ template "kyverno.namespace" . }} + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno +rules: +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - delete + - get + - patch + - update +# Allow update of Kyverno deployment annotations +- apiGroups: + - apps + resources: + - deployments + verbs: + - get + - list + - patch + - update + - watch + +{{- end }} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/rolebinding.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/rolebinding.yaml new file mode 100644 index 000000000..9f57ef2de --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/rolebinding.yaml @@ -0,0 +1,18 @@ +{{- if .Values.rbac.create }} +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ template "kyverno.fullname" . }}:leaderelection + namespace: {{ template "kyverno.namespace" . }} + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "kyverno.fullname" . }}:leaderelection +subjects: +- kind: ServiceAccount + name: {{ template "kyverno.serviceAccountName" . }} + namespace: {{ template "kyverno.namespace" . }} + +{{- end }} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/secret.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/secret.yaml new file mode 100644 index 000000000..c254b8963 --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/secret.yaml @@ -0,0 +1,28 @@ +{{- if .Values.createSelfSignedCert }} +{{- $ca := genCA (printf "*.%s.svc" (include "kyverno.namespace" .)) 1024 -}} +{{- $svcName := (printf "%s.%s.svc" (include "kyverno.serviceName" .) (include "kyverno.namespace" .)) -}} +{{- $cert := genSignedCert $svcName nil (list $svcName) 1024 $ca -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "kyverno.serviceName" . }}.{{ template "kyverno.namespace" . }}.svc.kyverno-tls-ca + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno +type: kubernetes.io/tls +data: + tls.key: {{ $ca.Key | b64enc }} + tls.crt: {{ $ca.Cert | b64enc }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "kyverno.serviceName" . }}.{{ template "kyverno.namespace" . }}.svc.kyverno-tls-pair + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno + annotations: + self-signed-cert: "true" +type: kubernetes.io/tls +data: + tls.key: {{ $cert.Key | b64enc }} + tls.crt: {{ $cert.Cert | b64enc }} +{{- end -}} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/service.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/service.yaml new file mode 100644 index 000000000..0f234f74a --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/service.yaml @@ -0,0 +1,47 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "kyverno.serviceName" . }} + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno + namespace: {{ template "kyverno.namespace" . }} + {{- with .Values.service.annotations }} + annotations: {{ tpl (toYaml .) $ | nindent 4 }} + {{- end }} +spec: + ports: + - port: {{ .Values.service.port }} + targetPort: https + protocol: TCP + name: https + {{- if and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort)) }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + selector: {{ include "kyverno.matchLabels" . | nindent 4 }} + app: kyverno + type: {{ .Values.service.type }} +--- +{{- if .Values.metricsService.create }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "kyverno.serviceName" . }}-metrics + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno + namespace: {{ template "kyverno.namespace" . }} + {{- with .Values.metricsService.annotations }} + annotations: {{ tpl (toYaml .) $ | nindent 4 }} + {{- end }} +spec: + ports: + - port: {{ .Values.metricsService.port }} + targetPort: 8000 + protocol: TCP + name: metrics-port + {{- if and (eq .Values.metricsService.type "NodePort") (not (empty .Values.metricsService.nodePort)) }} + nodePort: {{ .Values.metricsService.nodePort }} + {{- end }} + selector: {{ include "kyverno.matchLabels" . | nindent 4 }} + app: kyverno + type: {{ .Values.metricsService.type }} + {{- end -}} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/serviceaccount.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/serviceaccount.yaml new file mode 100644 index 000000000..521ff85d9 --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.rbac.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "kyverno.serviceAccountName" . }} + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno + {{- if .Values.rbac.serviceAccount.annotations }} + annotations: {{ toYaml .Values.rbac.serviceAccount.annotations | nindent 4 }} + {{- end }} + namespace: {{ template "kyverno.namespace" . }} +{{- end }} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/servicemonitor.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/servicemonitor.yaml new file mode 100644 index 000000000..bc6456362 --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/servicemonitor.yaml @@ -0,0 +1,32 @@ +{{- if .Values.serviceMonitor.enabled }} +apiVersion: "monitoring.coreos.com/v1" +kind: ServiceMonitor +metadata: + labels: {{ include "kyverno.labels" . | nindent 4 }} + app: kyverno +{{- if .Values.serviceMonitor.additionalLabels }} +{{ toYaml .Values.serviceMonitor.additionalLabels | indent 4 }} +{{- end }} + name: {{ template "kyverno.serviceName" . }}-service-monitor +{{- if .Values.serviceMonitor.namespace }} + namespace: {{ .Values.serviceMonitor.namespace }} +{{- else }} + namespace: {{ template "kyverno.namespace" . }} +{{- end }} +spec: + selector: + matchLabels: {{ include "kyverno.matchLabels" . | nindent 6 }} + app: kyverno + namespaceSelector: + matchNames: + - {{ template "kyverno.namespace" . }} + endpoints: + - port: metrics-port + interval: {{ .Values.serviceMonitor.interval }} + scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }} + {{- if .Values.serviceMonitor.secure }} + scheme: https + tlsConfig: + {{- toYaml .Values.serviceMonitor.tlsConfig | nindent 8 }} + {{- end }} +{{- end }} diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/tests/test.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/tests/test.yaml new file mode 100644 index 000000000..75345dbac --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/tests/test.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ template "kyverno.fullname" . }}-test" + labels: + {{- include "kyverno.test-labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + restartPolicy: Never + containers: + - name: wget + image: {{ include "kyverno.image" (dict "image" .Values.testImage "defaultTag" "latest") | quote }} + imagePullPolicy: {{ default .Values.image.pullPolicy .Values.testImage.pullPolicy }} + {{- with .Values.testResources }} + resources: {{ tpl (toYaml .) $ | nindent 8 }} + {{- end }} + {{- if .Values.testSecurityContext }} + securityContext: {{ include "kyverno.testSecurityContext" . | nindent 8 }} + {{- end }} + command: + - /bin/sh + - -c + - | + sleep 20 ; wget -O- -S --no-check-certificate https://{{ template "kyverno.serviceName" . }}:{{ .Values.service.port }}/health/liveness + - name: wget-metrics + image: {{ include "kyverno.image" (dict "image" .Values.testImage "defaultTag" "latest") | quote }} + imagePullPolicy: {{ default .Values.image.pullPolicy .Values.testImage.pullPolicy }} + {{- with .Values.testResources }} + resources: {{ tpl (toYaml .) $ | nindent 8 }} + {{- end }} + {{- if .Values.testSecurityContext }} + securityContext: {{ include "kyverno.testSecurityContext" . | nindent 8 }} + {{- end }} + command: + - /bin/sh + - -c + - | + sleep 20 ; wget -O- -S --no-check-certificate http://{{ template "kyverno.serviceName" . }}-metrics:{{ .Values.metricsService.port }}/metrics diff --git a/scripts/helmcharts/toolings/charts/kyverno/templates/validate.yaml b/scripts/helmcharts/toolings/charts/kyverno/templates/validate.yaml new file mode 100644 index 000000000..35864e9c1 --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/templates/validate.yaml @@ -0,0 +1,13 @@ +{{- if hasKey .Values "mode" }} + {{ fail "mode is not supported anymore, please remove it from your release and use replicaCount instead." }} +{{- end }} + +{{- if .Values.replicaCount }} + {{- if eq (int .Values.replicaCount) 2 }} + {{ fail "Kyverno does not support running with 2 replicas. For a highly-available deployment, select 3 replicas or for standalone select 1 replica." }} + {{- end }} +{{- end }} + +{{- if eq (include "kyverno.namespace" .) "kube-system" }} + {{ fail "Kyverno cannot be installed in namespace kube-system." }} +{{- end }} diff --git a/scripts/helmcharts/toolings/charts/kyverno/values.yaml b/scripts/helmcharts/toolings/charts/kyverno/values.yaml new file mode 100644 index 000000000..7803a1b95 --- /dev/null +++ b/scripts/helmcharts/toolings/charts/kyverno/values.yaml @@ -0,0 +1,478 @@ +# -- Override the name of the chart +nameOverride: + +# -- Override the expanded name of the chart +fullnameOverride: + +# -- Namespace the chart deploys to +namespace: + +# -- Additional labels +customLabels: {} + +rbac: + # -- Create ClusterRoles, ClusterRoleBindings, and ServiceAccount + create: true + serviceAccount: + # -- Create a ServiceAccount + create: true + # -- The ServiceAccount name + name: + # -- Annotations for the ServiceAccount + annotations: {} + # example.com/annotation: value + +image: + # -- Image registry + registry: + # If you want to manage the registry you should remove it from the repository + # registry: ghcr.io + # repository: kyverno/kyverno + # -- Image repository + repository: ghcr.io/kyverno/kyverno # kyverno: replaced in e2e tests + # -- Image tag + # Defaults to appVersion in Chart.yaml if omitted + tag: # replaced in e2e tests + # -- Image pull policy + pullPolicy: IfNotPresent + # -- Image pull secrets + pullSecrets: [] + # - secretName + +initImage: + # -- Image registry + registry: + # If you want to manage the registry you should remove it from the repository + # registry: ghcr.io + # repository: kyverno/kyvernopre + # -- Image repository + repository: ghcr.io/kyverno/kyvernopre # init: replaced in e2e tests + # -- Image tag + # If initImage.tag is missing, defaults to image.tag + tag: # replaced in e2e tests + # -- Image pull policy + # If initImage.pullPolicy is missing, defaults to image.pullPolicy + pullPolicy: + +initContainer: + # -- Extra arguments to give to the kyvernopre binary. + extraArgs: + - --loggingFormat=text + + +testImage: + # -- Image registry + registry: + # -- Image repository + repository: busybox + # -- Image tag + # Defaults to `latest` if omitted + tag: + # -- Image pull policy + # Defaults to image.pullPolicy if omitted + pullPolicy: + +# -- (int) Desired number of pods +replicaCount: ~ + +# -- Additional labels to add to each pod +podLabels: {} + # example.com/label: foo + +# -- Additional annotations to add to each pod +podAnnotations: {} + # example.com/annotation: foo + +# -- Security context for the pod +podSecurityContext: {} + +# -- Security context for the containers +securityContext: + runAsNonRoot: true + privileged: false + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + seccompProfile: + type: RuntimeDefault + +# -- Security context for the test containers +testSecurityContext: + runAsUser: 65534 + runAsGroup: 65534 + runAsNonRoot: true + privileged: false + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + seccompProfile: + type: RuntimeDefault + +# -- Optional priority class to be used for kyverno pods +priorityClassName: '' + +antiAffinity: + # -- Pod antiAffinities toggle. + # Enabled by default but can be disabled if you want to schedule pods to the same node. + enable: true + +# -- Pod anti affinity constraints. +# @default -- See [values.yaml](values.yaml) +podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - kyverno + topologyKey: kubernetes.io/hostname + +# -- Pod affinity constraints. +podAffinity: {} + +# -- Node affinity constraints. +nodeAffinity: {} + +podDisruptionBudget: + # -- Configures the minimum available pods for kyverno disruptions. + # Cannot be used if `maxUnavailable` is set. + minAvailable: 1 + # -- Configures the maximum unavailable pods for kyverno disruptions. + # Cannot be used if `minAvailable` is set. + maxUnavailable: + +# -- Node labels for pod assignment +nodeSelector: {} + +# -- List of node taints to tolerate +tolerations: [] + +# -- Change `hostNetwork` to `true` when you want the kyverno's pod to share its host's network namespace. +# Useful for situations like when you end up dealing with a custom CNI over Amazon EKS. +# Update the `dnsPolicy` accordingly as well to suit the host network mode. +hostNetwork: false + +# -- `dnsPolicy` determines the manner in which DNS resolution happens in the cluster. +# In case of `hostNetwork: true`, usually, the `dnsPolicy` is suitable to be `ClusterFirstWithHostNet`. +# For further reference: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy. +dnsPolicy: ClusterFirst + +# -- Env variables for initContainers. +envVarsInit: {} + +# -- Env variables for containers. +envVars: {} + +# -- Extra arguments to give to the binary. +extraArgs: + - --autogenInternals=true + - --loggingFormat=text + +# -- Array of extra init containers +extraInitContainers: [] +# Example: +# - name: init-container +# image: busybox +# command: ['sh', '-c', 'echo Hello'] + +# -- Array of extra containers to run alongside kyverno +extraContainers: [] +# Example: +# - name: myapp-container +# image: busybox +# command: ['sh', '-c', 'echo Hello && sleep 3600'] + +# -- Image pull secrets for image verify and imageData policies. +# This will define the `--imagePullSecrets` Kyverno argument. +imagePullSecrets: {} + # Define two image pull secrets + # imagePullSecrets: + # regcred: + # registry: foo.example.com + # username: foobar + # password: secret + # regcred2: + # registry: bar.example.com + # username: barbaz + # password: secret2 + +resources: + # -- Pod resource limits + limits: + memory: 384Mi + # -- Pod resource requests + requests: + cpu: 100m + memory: 128Mi + +initResources: + # -- Pod resource limits + limits: + cpu: 100m + memory: 256Mi + # -- Pod resource requests + requests: + cpu: 10m + memory: 64Mi + +testResources: + # -- Pod resource limits + limits: + cpu: 100m + memory: 256Mi + # -- Pod resource requests + requests: + cpu: 10m + memory: 64Mi + +# -- Startup probe. +# The block is directly forwarded into the deployment, so you can use whatever startupProbes configuration you want. +# ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ +# @default -- See [values.yaml](values.yaml) +startupProbe: + httpGet: + path: /health/liveness + port: 9443 + scheme: HTTPS + failureThreshold: 20 + initialDelaySeconds: 2 + periodSeconds: 6 + +# -- Liveness probe. +# The block is directly forwarded into the deployment, so you can use whatever livenessProbe configuration you want. +# ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ +# @default -- See [values.yaml](values.yaml) +livenessProbe: + httpGet: + path: /health/liveness + port: 9443 + scheme: HTTPS + initialDelaySeconds: 15 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 2 + successThreshold: 1 + +# -- Readiness Probe. +# The block is directly forwarded into the deployment, so you can use whatever readinessProbe configuration you want. +# ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ +# @default -- See [values.yaml](values.yaml) +readinessProbe: + httpGet: + path: /health/readiness + port: 9443 + scheme: HTTPS + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +# -- Additional resources to be added to controller RBAC permissions. +generatecontrollerExtraResources: [] +# - ResourceA +# - ResourceB + +# -- Exclude Kyverno namespace +# Determines if default Kyverno namespace exclusion is enabled for webhooks and resourceFilters +excludeKyvernoNamespace: true + +# -- resourceFilter namespace exclude +# Namespaces to exclude from the default resourceFilters +resourceFiltersExcludeNamespaces: [] + +config: + # -- Resource types to be skipped by the Kyverno policy engine. + # Make sure to surround each entry in quotes so that it doesn't get parsed as a nested YAML list. + # These are joined together without spaces, run through `tpl`, and the result is set in the config map. + # @default -- See [values.yaml](values.yaml) + resourceFilters: + - '[Event,*,*]' + - '[*,kube-system,*]' + - '[*,kube-public,*]' + - '[*,kube-node-lease,*]' + - '[Node,*,*]' + - '[APIService,*,*]' + - '[TokenReview,*,*]' + - '[SubjectAccessReview,*,*]' + - '[SelfSubjectAccessReview,*,*]' + - '[Binding,*,*]' + - '[ReplicaSet,*,*]' + - '[AdmissionReport,*,*]' + - '[ClusterAdmissionReport,*,*]' + - '[BackgroundScanReport,*,*]' + - '[ClusterBackgroundScanReport,*,*]' + # exclude resources from the chart + - '[ClusterRole,*,{{ template "kyverno.fullname" . }}:*]' + - '[ClusterRoleBinding,*,{{ template "kyverno.fullname" . }}:*]' + - '[ServiceAccount,{{ include "kyverno.namespace" . }},{{ template "kyverno.serviceAccountName" . }}]' + - '[ConfigMap,{{ include "kyverno.namespace" . }},{{ template "kyverno.configMapName" . }}]' + - '[ConfigMap,{{ include "kyverno.namespace" . }},{{ template "kyverno.metricsConfigMapName" . }}]' + - '[Deployment,{{ include "kyverno.namespace" . }},{{ template "kyverno.fullname" . }}]' + - '[Job,{{ include "kyverno.namespace" . }},{{ template "kyverno.fullname" . }}-hook-pre-delete]' + - '[NetworkPolicy,{{ include "kyverno.namespace" . }},{{ template "kyverno.fullname" . }}]' + - '[PodDisruptionBudget,{{ include "kyverno.namespace" . }},{{ template "kyverno.fullname" . }}]' + - '[Role,{{ include "kyverno.namespace" . }},{{ template "kyverno.fullname" . }}:*]' + - '[RoleBinding,{{ include "kyverno.namespace" . }},{{ template "kyverno.fullname" . }}:*]' + - '[Secret,{{ include "kyverno.namespace" . }},{{ template "kyverno.serviceName" . }}.{{ template "kyverno.namespace" . }}.svc.*]' + - '[Service,{{ include "kyverno.namespace" . }},{{ template "kyverno.serviceName" . }}]' + - '[Service,{{ include "kyverno.namespace" . }},{{ template "kyverno.serviceName" . }}-metrics]' + - '[ServiceMonitor,{{ if .Values.serviceMonitor.namespace }}{{ .Values.serviceMonitor.namespace }}{{ else }}{{ template "kyverno.namespace" . }}{{ end }},{{ template "kyverno.serviceName" . }}-service-monitor]' + - '[Pod,{{ include "kyverno.namespace" . }},{{ template "kyverno.fullname" . }}-test]' + + # -- Name of an existing config map (ignores default/provided resourceFilters) + existingConfig: '' + # -- Additional annotations to add to the configmap + annotations: {} + # example.com/annotation: foo + # -- Exclude group role + excludeGroupRole: + # - '' + # -- Exclude username + excludeUsername: + # - '' + # -- Defines the `namespaceSelector` in the webhook configurations. + # Note that it takes a list of `namespaceSelector` and/or `objectSelector` in the JSON format, and only the first element + # will be forwarded to the webhook configurations. + # The Kyverno namespace is excluded if `excludeKyvernoNamespace` is `true` (default) + webhooks: + # Exclude namespaces + # - namespaceSelector: + # matchExpressions: + # - key: kubernetes.io/metadata.name + # operator: NotIn + # values: + # - kube-system + # - kyverno + # Exclude objects + # - objectSelector: + # matchExpressions: + # - key: webhooks.kyverno.io/exclude + # operator: DoesNotExist + + # -- Generate success events. + generateSuccessEvents: false + # -- Metrics config. + metricsConfig: + # -- Additional annotations to add to the metricsconfigmap + annotations: {} + # example.com/annotation: foo + namespaces: { + "include": [], + "exclude": [] + } + # 'namespaces.include': list of namespaces to capture metrics for. Default: metrics being captured for all namespaces except excludeNamespaces. + # 'namespaces.exclude': list of namespaces to NOT capture metrics for. Default: [] + + # metricsRefreshInterval: 24h + # rate at which metrics should reset so as to clean up the memory footprint of kyverno metrics, if you might be expecting high memory footprint of Kyverno's metrics. Default: 0, no refresh of metrics + + # Or provide an existing metrics config-map by uncommenting the below line + # existingMetricsConfig: sample-metrics-configmap. Refer to the ./templates/metricsconfigmap.yaml for the structure of metrics configmap. +# -- Deployment update strategy. +# Ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy +# @default -- See [values.yaml](values.yaml) +updateStrategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 40% + type: RollingUpdate + +service: + # -- Service port. + port: 443 + # -- Service type. + type: ClusterIP + # -- Service node port. + # Only used if `service.type` is `NodePort`. + nodePort: + # -- Service annotations. + annotations: {} + +# -- Topology spread constraints. +topologySpreadConstraints: [] + +metricsService: + # -- Create service. + create: true + # -- Service port. + # Kyverno's metrics server will be exposed at this port. + port: 8000 + # -- Service type. + type: ClusterIP + # -- Service node port. + # Only used if `metricsService.type` is `NodePort`. + nodePort: + # -- Service annotations. + annotations: {} + +serviceMonitor: + # -- Create a `ServiceMonitor` to collect Prometheus metrics. + enabled: false + # -- Additional labels + additionalLabels: + # key: value + # -- Override namespace (default is the same as kyverno) + namespace: + # -- Interval to scrape metrics + interval: 30s + # -- Timeout if metrics can't be retrieved in given time interval + scrapeTimeout: 25s + # -- Is TLS required for endpoint + secure: false + # -- TLS Configuration for endpoint + tlsConfig: {} + +# -- Kyverno requires a certificate key pair and corresponding certificate authority +# to properly register its webhooks. This can be done in one of 3 ways: +# 1) Use kube-controller-manager to generate a CA-signed certificate (preferred) +# 2) Provide your own CA and cert. +# In this case, you will need to create a certificate with a specific name and data structure. +# As long as you follow the naming scheme, it will be automatically picked up. +# kyverno-svc.(namespace).svc.kyverno-tls-ca (with data entries named tls.key and tls.crt) +# kyverno-svc.kyverno.svc.kyverno-tls-pair (with data entries named tls.key and tls.crt) +# 3) Let Helm generate a self signed cert, by setting createSelfSignedCert true +# If letting Kyverno create its own CA or providing your own, make createSelfSignedCert is false +createSelfSignedCert: false + +# -- Whether to have Helm install the Kyverno CRDs. +# If the CRDs are not installed by Helm, they must be added before policies can be created. +installCRDs: true + +crds: + # -- Additional CRDs annotations. + annotations: {} + # argocd.argoproj.io/sync-options: Replace=true + # strategy.spinnaker.io/replace: 'true' + +networkPolicy: + # -- When true, use a NetworkPolicy to allow ingress to the webhook + # This is useful on clusters using Calico and/or native k8s network policies in a default-deny setup. + enabled: false + # -- A list of valid from selectors according to https://kubernetes.io/docs/concepts/services-networking/network-policies. + ingressFrom: [] + +webhooksCleanup: + # -- Create a helm pre-delete hook to cleanup webhooks. + enable: false + # -- `kubectl` image to run commands for deleting webhooks. + image: bitnami/kubectl:latest + +# -- A writable volume to use for the TUF root initialization. +tufRootMountPath: /.sigstore + +grafana: + # -- Enable grafana dashboard creation. + enabled: false + # -- Namespace to create the grafana dashboard configmap. + # If not set, it will be created in the same namespace where the chart is deployed. + namespace: + # -- Grafana dashboard configmap annotations. + annotations: {} diff --git a/scripts/helmcharts/toolings/files/cosign.pub b/scripts/helmcharts/toolings/files/cosign.pub new file mode 100644 index 000000000..d56e71e38 --- /dev/null +++ b/scripts/helmcharts/toolings/files/cosign.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuSUrc90YHUpXwB2E7Hu080K6z+Yc +esqGVAEESg9lEjQUaxOUqRkW3nI/vXRQayLEfBs6ugPNqCH+DbuarI9Jkg== +-----END PUBLIC KEY----- diff --git a/scripts/helmcharts/toolings/templates/kyverno.yaml b/scripts/helmcharts/toolings/templates/kyverno.yaml new file mode 100644 index 000000000..c17ff8df8 --- /dev/null +++ b/scripts/helmcharts/toolings/templates/kyverno.yaml @@ -0,0 +1,45 @@ +apiVersion: kyverno.io/v1 +kind: Policy +metadata: + name: cosign + namespace: "{{ .Release.Namespace }}" + annotations: + "helm.sh/hook": post-install, post-upgrade + "helm.sh/hook-weight": "4" # Higher precidence, so the first the config map will get created. +spec: + # validationFailureAction: enforce + validationFailureAction: audit + background: false + webhookTimeoutSeconds: 30 + # failurePolicy: Fail + failurePolicy: Ignore + rules: + - name: openreplay-image-signature + match: + any: + - resources: + kinds: + - Pod + - Deployment + verifyImages: + - imageReferences: + - "public.ecr.aws/p1t3u8a3/*" + attestors: + - count: 1 + entries: + - keys: + publicKeys: |- + -----BEGIN PUBLIC KEY----- + MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoLidzRiNIO3l/sWCYw2f + Ct71YSj7UVerhbR81TNEKYtW0fUqg4GagS+esprcXteHPoBI+ZcfL2xJIs0ZNHZs + A+2VXYrsFRgREtABFCwJ2G51ybusoS3jpBsAmSNjG0uzseDxQMTh0arNOlNbhbmI + Tj1ty2JfyLejDKlxavXheKmJGb+7IdDCMmP3f5mXSsJpsOM8SJo49BkvKhTwzjc0 + 01dsSLo5mk9jeG2C6UvPCQeMIUKaf5GlYWyFx7vLZ+z5be9TPuWDH4GO0RtxJVXt + tqmk32aKe+0KDLH0ak9WRVz3ugYEjs+tqdO3y3ALLoGAAI+yGxGSfWFDnDj5AXpA + 2/XYSJAWRzPu35/H3laSrxaApYWN5an69jI30JY7SoEy/k+10oIGe2FGIihXTdq+ + As3IKPEtvuN9s3RTm2ujV/7rEnVVKWiHvQCwH8rxhsbDTeJCoNs8hSBUq1Muttct + EWML8s/TCIK01PyvH6VNQSnc+lRKAJOd5NpZ/SVMXBbrykCQSZPE8RcaQum3nMxE + Tri24VcWfRHj1WwUYzxpmoVE5F1lw0lqQIXlwz+AFhCLGsePSkjFShFtNFQuX22r + Q73JTt3FX4JEzaaKC5BZwXmkEs3MVpQj43HuEqDyejlsPWwRBYwZIzXpoBhOCFHD + t4PI8n+1dSE+uavu/ijgXl8CAwEAAQ== + -----END PUBLIC KEY----- diff --git a/scripts/helmcharts/toolings/values.yaml b/scripts/helmcharts/toolings/values.yaml new file mode 100644 index 000000000..c34673667 --- /dev/null +++ b/scripts/helmcharts/toolings/values.yaml @@ -0,0 +1,3 @@ +kyverno: + fullnameOverride: kyverno + excludeKyvernoNamespace: false diff --git a/scripts/helmcharts/vars.yaml b/scripts/helmcharts/vars.yaml index f99811bb7..bdb40ae0f 100644 --- a/scripts/helmcharts/vars.yaml +++ b/scripts/helmcharts/vars.yaml @@ -1,4 +1,4 @@ -fromVersion: "v1.9.0" +fromVersion: "v1.10.0" # Databases specific variables postgresql: &postgres # For generating passwords @@ -21,6 +21,8 @@ clickhouse: &clickhouse # For enterpriseEdition enabled: false chHost: clickhouse-openreplay-clickhouse.db.svc.cluster.local + username: default + password: "" service: webPort: 9000 @@ -118,8 +120,9 @@ global: vaultBucket: "vault-data" # This is only for enterpriseEdition quickwitBucket: "quickwit" - # If you're using minio, make sure these variables - # are the same as minio.global.minio.accesskey and secretKey + # if you're using one node installation, where + # you're using local s3, make sure these variables + # are same as minio.global.minio.accesskey and secretKey accessKey: "changeMeMinioAccessKey" secretKey: "changeMeMinioPassword" email: @@ -147,6 +150,7 @@ chalice: # idp_sls_url: '' # idp_name: '' # idp_tenantKey: '' + # enforce_SSO: 'false' # Below is an example on how to override values # chartname: diff --git a/scripts/schema/db/init_dbs/postgresql/1.10.0/1.10.0.sql b/scripts/schema/db/init_dbs/postgresql/1.10.0/1.10.0.sql new file mode 100644 index 000000000..f2fb3f839 --- /dev/null +++ b/scripts/schema/db/init_dbs/postgresql/1.10.0/1.10.0.sql @@ -0,0 +1,629 @@ +BEGIN; +CREATE OR REPLACE FUNCTION openreplay_version() + RETURNS text AS +$$ +SELECT 'v1.10.0' +$$ LANGUAGE sql IMMUTABLE; + +-- Backup dashboard & search data: +DO +$$ + BEGIN + IF NOT (SELECT EXISTS(SELECT schema_name + FROM information_schema.schemata + WHERE schema_name = 'backup_v1_10_0')) THEN + CREATE SCHEMA backup_v1_10_0; + CREATE TABLE backup_v1_10_0.dashboards + ( + dashboard_id integer, + project_id integer, + user_id integer, + name text NOT NULL, + description text NOT NULL DEFAULT '', + is_public boolean NOT NULL DEFAULT TRUE, + is_pinned boolean NOT NULL DEFAULT FALSE, + created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), + deleted_at timestamp NULL DEFAULT NULL + ); + CREATE TABLE backup_v1_10_0.dashboard_widgets + ( + widget_id integer, + dashboard_id integer, + metric_id integer, + user_id integer, + created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), + config jsonb NOT NULL DEFAULT '{}'::jsonb + ); + CREATE TABLE backup_v1_10_0.searches + ( + search_id integer, + project_id integer, + user_id integer, + name text not null, + filter jsonb not null, + created_at timestamp default timezone('utc'::text, now()) not null, + deleted_at timestamp, + is_public boolean NOT NULL DEFAULT False + ); + CREATE TABLE backup_v1_10_0.metrics + ( + metric_id integer, + project_id integer, + user_id integer, + name text NOT NULL, + is_public boolean NOT NULL DEFAULT FALSE, + active boolean NOT NULL DEFAULT TRUE, + created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), + deleted_at timestamp, + edited_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), + metric_type text NOT NULL, + view_type text NOT NULL, + metric_of text NOT NULL DEFAULT 'sessionCount', + metric_value text[] NOT NULL DEFAULT '{}'::text[], + metric_format text, + category text NULL DEFAULT 'custom', + is_pinned boolean NOT NULL DEFAULT FALSE, + is_predefined boolean NOT NULL DEFAULT FALSE, + is_template boolean NOT NULL DEFAULT FALSE, + predefined_key text NULL DEFAULT NULL, + default_config jsonb NOT NULL + ); + CREATE TABLE backup_v1_10_0.metric_series + ( + series_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY, + metric_id integer REFERENCES metrics (metric_id) ON DELETE CASCADE, + index integer NOT NULL, + name text NULL, + filter jsonb NOT NULL, + created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL, + deleted_at timestamp + ); + + INSERT INTO backup_v1_10_0.dashboards(dashboard_id, project_id, user_id, name, description, is_public, + is_pinned, + created_at, deleted_at) + SELECT dashboard_id, + project_id, + user_id, + name, + description, + is_public, + is_pinned, + created_at, + deleted_at + FROM public.dashboards + ORDER BY dashboard_id; + + INSERT INTO backup_v1_10_0.metrics(metric_id, project_id, user_id, name, is_public, active, created_at, + deleted_at, edited_at, metric_type, view_type, metric_of, metric_value, + metric_format, category, is_pinned, is_predefined, is_template, + predefined_key, default_config) + SELECT metric_id, + project_id, + user_id, + name, + is_public, + active, + created_at, + deleted_at, + edited_at, + metric_type, + view_type, + metric_of, + metric_value, + metric_format, + category, + is_pinned, + is_predefined, + is_template, + predefined_key, + default_config + FROM public.metrics + ORDER BY metric_id; + + INSERT INTO backup_v1_10_0.metric_series(series_id, metric_id, index, name, filter, created_at, deleted_at) + SELECT series_id, metric_id, index, name, filter, created_at, deleted_at + FROM public.metric_series + ORDER BY series_id; + + INSERT INTO backup_v1_10_0.dashboard_widgets(widget_id, dashboard_id, metric_id, user_id, created_at, config) + SELECT widget_id, dashboard_id, metric_id, user_id, created_at, config + FROM public.dashboard_widgets + ORDER BY widget_id; + + INSERT INTO backup_v1_10_0.searches(search_id, project_id, user_id, name, filter, created_at, deleted_at, + is_public) + SELECT search_id, + project_id, + user_id, + name, + filter, + created_at, + deleted_at, + is_public + FROM public.searches + ORDER BY search_id; + END IF; + END +$$ LANGUAGE plpgsql; + +ALTER TYPE webhook_type ADD VALUE IF NOT EXISTS 'msteams'; + +UPDATE metrics +SET is_public= TRUE; + +CREATE OR REPLACE FUNCTION get_global_key(key text) + RETURNS text AS +$$ +DECLARE + events_map CONSTANT JSONB := '{ + "SESSIONS": "sessions", + "sessionCount": "sessionCount", + "CLICK": "click", + "INPUT": "input", + "LOCATION": "location", + "CUSTOM": "custom", + "REQUEST": "request", + "FETCH": "fetch", + "GRAPHQL": "graphql", + "STATEACTION": "stateAction", + "ERROR": "error", + "CLICK_IOS": "clickIos", + "INPUT_IOS": "inputIos", + "VIEW_IOS": "viewIos", + "CUSTOM_IOS": "customIos", + "REQUEST_IOS": "requestIos", + "ERROR_IOS": "errorIos", + "DOM_COMPLETE": "domComplete", + "LARGEST_CONTENTFUL_PAINT_TIME": "largestContentfulPaintTime", + "TIME_BETWEEN_EVENTS": "timeBetweenEvents", + "TTFB": "ttfb", + "AVG_CPU_LOAD": "avgCpuLoad", + "AVG_MEMORY_USAGE": "avgMemoryUsage", + "FETCH_FAILED": "fetchFailed", + "FETCH_URL": "fetchUrl", + "FETCH_STATUS_CODE": "fetchStatusCode", + "FETCH_METHOD": "fetchMethod", + "FETCH_DURATION": "fetchDuration", + "FETCH_REQUEST_BODY": "fetchRequestBody", + "FETCH_RESPONSE_BODY": "fetchResponseBody", + "GRAPHQL_NAME": "graphqlName", + "GRAPHQL_METHOD": "graphqlMethod", + "GRAPHQL_REQUEST_BODY": "graphqlRequestBody", + "GRAPHQL_RESPONSE_BODY": "graphqlResponseBody", + "USEROS": "userOs", + "USERBROWSER": "userBrowser", + "USERDEVICE": "userDevice", + "USERCOUNTRY": "userCountry", + "USERID": "userId", + "USERANONYMOUSID": "userAnonymousId", + "REFERRER": "referrer", + "REVID": "revId", + "USEROS_IOS": "userOsIos", + "USERDEVICE_IOS": "userDeviceIos", + "USERCOUNTRY_IOS": "userCountryIos", + "USERID_IOS": "userIdIos", + "USERANONYMOUSID_IOS": "userAnonymousIdIos", + "REVID_IOS": "revIdIos", + "DURATION": "duration", + "PLATFORM": "platform", + "METADATA": "metadata", + "ISSUE": "issue", + "EVENTS_COUNT": "eventsCount", + "UTM_SOURCE": "utmSource", + "UTM_MEDIUM": "utmMedium", + "UTM_CAMPAIGN": "utmCampaign" + }'; +BEGIN + RETURN jsonb_extract_path(events_map, key); +END; +$$ LANGUAGE plpgsql IMMUTABLE; + +ALTER TABLE IF EXISTS metrics + ALTER COLUMN metric_type TYPE text, + ALTER COLUMN metric_type SET DEFAULT 'timeseries', + ALTER COLUMN view_type TYPE text, + ALTER COLUMN view_type SET DEFAULT 'lineChart', + ADD COLUMN IF NOT EXISTS thumbnail text; + +DO +$$ + BEGIN + IF EXISTS(SELECT column_name + FROM information_schema.columns + WHERE table_name = 'metrics' + AND column_name = 'is_predefined' + AND table_schema = 'public') THEN + -- 0. change metric_of + UPDATE metrics + SET metric_of=coalesce(replace(get_global_key(metric_of), '"', ''), + left(metric_of, 1) || right(replace(initcap(metric_of), '_', ''), -1)) + WHERE not is_predefined; + + -- 1. pre transform structure + ALTER TABLE IF EXISTS metrics + ADD COLUMN IF NOT EXISTS o_metric_id INTEGER, + ADD COLUMN IF NOT EXISTS o_widget_id INTEGER; + + -- 2. insert predefined metrics related to dashboards as custom metrics + INSERT INTO metrics(project_id, user_id, name, metric_type, view_type, metric_of, metric_value, + metric_format, default_config, is_public, o_metric_id, o_widget_id) + SELECT dashboards.project_id, + dashboard_widgets.user_id, + metrics.name, + left(category, 1) || right(replace(initcap(category), ' ', ''), -1) AS metric_type, + 'chart' AS view_type, + left(predefined_key, 1) || right(replace(initcap(predefined_key), '_', ''), -1) AS metric_of, + metric_value, + metric_format, + default_config, + TRUE AS is_public, + metrics.metric_id, + dashboard_widgets.widget_id + FROM metrics + INNER JOIN dashboard_widgets USING (metric_id) + INNER JOIN dashboards USING (dashboard_id) + WHERE is_predefined; + + -- 3. update widgets + UPDATE dashboard_widgets + SET metric_id=metrics.metric_id + FROM metrics + WHERE metrics.o_widget_id IS NOT NULL + AND dashboard_widgets.widget_id = metrics.o_widget_id; + + -- 4. delete predefined metrics + DELETE + FROM metrics + WHERE is_predefined; + + ALTER TABLE IF EXISTS metrics + DROP COLUMN IF EXISTS active, + DROP COLUMN IF EXISTS is_predefined, + DROP COLUMN IF EXISTS predefined_key, + DROP COLUMN IF EXISTS is_template, + DROP COLUMN IF EXISTS category, + DROP COLUMN IF EXISTS o_metric_id, + DROP COLUMN IF EXISTS o_widget_id, + DROP CONSTRAINT IF EXISTS null_project_id_for_template_only, + DROP CONSTRAINT IF EXISTS metrics_unique_key, + DROP CONSTRAINT IF EXISTS unique_key; + + END IF; + END; +$$ +LANGUAGE plpgsql; + +DROP TYPE IF EXISTS metric_type; +DROP TYPE IF EXISTS metric_view_type; + +ALTER TABLE IF EXISTS events.clicks + ADD COLUMN IF NOT EXISTS path text; + +DROP INDEX IF EXISTS events.clicks_url_gin_idx; +DROP INDEX IF EXISTS events.inputs_label_value_idx; +DROP INDEX IF EXISTS events.inputs_label_idx; +DROP INDEX IF EXISTS events.pages_base_path_idx; +DROP INDEX IF EXISTS events.pages_base_path_idx1; +DROP INDEX IF EXISTS events.pages_base_path_idx2; +DROP INDEX IF EXISTS events.pages_base_referrer_gin_idx1; +DROP INDEX IF EXISTS events.pages_base_referrer_gin_idx2; +DROP INDEX IF EXISTS events.resources_url_gin_idx; +DROP INDEX IF EXISTS events.resources_url_idx; +DROP INDEX IF EXISTS events.resources_url_hostpath_idx; +DROP INDEX IF EXISTS events.resources_session_id_timestamp_idx; +DROP INDEX IF EXISTS events.resources_duration_durationgt0_idx; +DROP INDEX IF EXISTS events.state_actions_name_idx; +DROP INDEX IF EXISTS events_common.requests_query_nn_idx; +DROP INDEX IF EXISTS events_common.requests_host_nn_idx; +DROP INDEX IF EXISTS events_common.issues_context_string_gin_idx; +DROP INDEX IF EXISTS public.sessions_user_country_gin_idx; +DROP INDEX IF EXISTS public.sessions_user_browser_gin_idx; +DROP INDEX IF EXISTS public.sessions_user_os_gin_idx; +DROP INDEX IF EXISTS public.issues_context_string_gin_idx; + + +ALTER TABLE IF EXISTS projects + ADD COLUMN IF NOT EXISTS beacon_size integer NOT NULL DEFAULT 0; + +-- To migrate saved search data + +SET client_min_messages TO NOTICE; +CREATE OR REPLACE FUNCTION get_new_event_key(key text) + RETURNS text AS +$$ +DECLARE + events_map CONSTANT JSONB := '{ + "CLICK": "click", + "INPUT": "input", + "LOCATION": "location", + "CUSTOM": "custom", + "REQUEST": "request", + "FETCH": "fetch", + "GRAPHQL": "graphql", + "STATEACTION": "stateAction", + "ERROR": "error", + "CLICK_IOS": "clickIos", + "INPUT_IOS": "inputIos", + "VIEW_IOS": "viewIos", + "CUSTOM_IOS": "customIos", + "REQUEST_IOS": "requestIos", + "ERROR_IOS": "errorIos", + "DOM_COMPLETE": "domComplete", + "LARGEST_CONTENTFUL_PAINT_TIME": "largestContentfulPaintTime", + "TIME_BETWEEN_EVENTS": "timeBetweenEvents", + "TTFB": "ttfb", + "AVG_CPU_LOAD": "avgCpuLoad", + "AVG_MEMORY_USAGE": "avgMemoryUsage", + "FETCH_FAILED": "fetchFailed" + }'; +BEGIN + RETURN jsonb_extract_path(events_map, key); +END; +$$ LANGUAGE plpgsql IMMUTABLE; + + +CREATE OR REPLACE FUNCTION get_new_event_filter_key(key text) + RETURNS text AS +$$ +DECLARE + event_filters_map CONSTANT JSONB := '{ + "FETCH_URL": "fetchUrl", + "FETCH_STATUS_CODE": "fetchStatusCode", + "FETCH_METHOD": "fetchMethod", + "FETCH_DURATION": "fetchDuration", + "FETCH_REQUEST_BODY": "fetchRequestBody", + "FETCH_RESPONSE_BODY": "fetchResponseBody", + "GRAPHQL_NAME": "graphqlName", + "GRAPHQL_METHOD": "graphqlMethod", + "GRAPHQL_REQUEST_BODY": "graphqlRequestBody", + "GRAPHQL_RESPONSE_BODY": "graphqlResponseBody" + }'; +BEGIN + RETURN jsonb_extract_path(event_filters_map, key); +END; +$$ LANGUAGE plpgsql IMMUTABLE; + +CREATE OR REPLACE FUNCTION get_new_filter_key(key text) + RETURNS text AS +$$ +DECLARE + filters_map CONSTANT JSONB := '{ + "USEROS": "userOs", + "USERBROWSER": "userBrowser", + "USERDEVICE": "userDevice", + "USERCOUNTRY": "userCountry", + "USERID": "userId", + "USERANONYMOUSID": "userAnonymousId", + "REFERRER": "referrer", + "REVID": "revId", + "USEROS_IOS": "userOsIos", + "USERDEVICE_IOS": "userDeviceIos", + "USERCOUNTRY_IOS": "userCountryIos", + "USERID_IOS": "userIdIos", + "USERANONYMOUSID_IOS": "userAnonymousIdIos", + "REVID_IOS": "revIdIos", + "DURATION": "duration", + "PLATFORM": "platform", + "METADATA": "metadata", + "ISSUE": "issue", + "EVENTS_COUNT": "eventsCount", + "UTM_SOURCE": "utmSource", + "UTM_MEDIUM": "utmMedium", + "UTM_CAMPAIGN": "utmCampaign" + }'; +BEGIN + RETURN jsonb_extract_path(filters_map, key); +END; +$$ LANGUAGE plpgsql IMMUTABLE; + +DO +$$ + DECLARE + row RECORD; + events_att JSONB; + event_filters_att JSONB; + filters_att JSONB; + element JSONB; + s_element JSONB; + new_value TEXT; + new_events JSONB[]; + new_filters JSONB[]; + new_event_filters JSONB[]; + changed BOOLEAN; + planned_update JSONB[]; + BEGIN + planned_update := '{}'::jsonb[]; + FOR row IN SELECT * FROM searches + LOOP + -- Transform events attributes + events_att := row.filter -> 'events'; + IF events_att IS NOT NULL THEN + new_events := '{}'::jsonb[]; + FOR element IN SELECT jsonb_array_elements(events_att) + LOOP + changed := FALSE; + new_value := get_new_event_key(element ->> 'type'); + if new_value IS NOT NULL THEN + changed := TRUE; + new_value := replace(new_value, '"', ''); + element := element || jsonb_build_object('type', new_value); + END IF; + -- Transform event's sub-filters attributes + event_filters_att := element -> 'filters'; + new_event_filters := '{}'::jsonb[]; + IF event_filters_att IS NOT NULL AND jsonb_array_length(event_filters_att) > 0 THEN + FOR s_element IN SELECT jsonb_array_elements(event_filters_att) + LOOP + new_value := get_new_event_filter_key(s_element ->> 'type'); + if new_value IS NOT NULL THEN + changed := TRUE; + new_value := replace(new_value, '"', ''); + s_element := s_element || jsonb_build_object('type', new_value); + new_event_filters := array_append(new_event_filters, s_element); + END IF; + END LOOP; + element := element || jsonb_build_object('filters', new_event_filters); + END IF; + IF changed THEN + new_events := array_append(new_events, element); + END IF; + END LOOP; + IF array_length(new_events, 1) > 0 THEN + row.filter := row.filter || jsonb_build_object('events', new_events); + END IF; + END IF; + + -- Transform filters attributes + filters_att := row.filter -> 'filters'; + IF filters_att IS NOT NULL THEN + new_filters := '{}'::jsonb; + FOR element IN SELECT jsonb_array_elements(filters_att) + LOOP + new_value := get_new_filter_key(element ->> 'type'); + if new_value IS NOT NULL THEN + new_value := replace(new_value, '"', ''); + element := element || jsonb_build_object('type', new_value); + new_filters := array_append(new_filters, element); + END IF; + END LOOP; + IF array_length(new_filters, 1) > 0 THEN + row.filter := row.filter || jsonb_build_object('filters', new_filters); + END IF; + END IF; + + IF array_length(new_events, 1) > 0 OR array_length(new_filters, 1) > 0 THEN + planned_update := array_append(planned_update, + jsonb_build_object('id', row.search_id, 'change', row.filter)); + END IF; + END LOOP; + + -- Update saved search + IF array_length(planned_update, 1) > 0 THEN + raise notice 'must update % elements',array_length(planned_update, 1); + + UPDATE searches + SET filter=changes.change -> 'change' + FROM (SELECT unnest(planned_update)) AS changes(change) + WHERE search_id = (changes.change -> 'id')::integer; + raise notice 'update done'; + ELSE + raise notice 'nothing to update'; + END IF; + END ; +$$ +LANGUAGE plpgsql; + + +-- To migrate metric_series data +DO +$$ + DECLARE + row RECORD; + events_att JSONB; + event_filters_att JSONB; + filters_att JSONB; + element JSONB; + s_element JSONB; + new_value TEXT; + new_events JSONB[]; + new_filters JSONB[]; + new_event_filters JSONB[]; + changed BOOLEAN; + planned_update JSONB[]; + BEGIN + planned_update := '{}'::jsonb[]; + FOR row IN SELECT * FROM metric_series + LOOP + -- Transform events attributes + events_att := row.filter -> 'events'; + IF events_att IS NOT NULL THEN + new_events := '{}'::jsonb[]; + FOR element IN SELECT jsonb_array_elements(events_att) + LOOP + changed := FALSE; + new_value := get_new_event_key(element ->> 'type'); + if new_value IS NOT NULL THEN + changed := TRUE; + new_value := replace(new_value, '"', ''); + element := element || jsonb_build_object('type', new_value); + END IF; + -- Transform event's sub-filters attributes + event_filters_att := element -> 'filters'; + new_event_filters := '{}'::jsonb[]; + IF event_filters_att IS NOT NULL AND jsonb_array_length(event_filters_att) > 0 THEN + FOR s_element IN SELECT jsonb_array_elements(event_filters_att) + LOOP + new_value := get_new_event_filter_key(s_element ->> 'type'); + if new_value IS NOT NULL THEN + changed := TRUE; + new_value := replace(new_value, '"', ''); + s_element := s_element || jsonb_build_object('type', new_value); + new_event_filters := array_append(new_event_filters, s_element); + END IF; + END LOOP; + element := element || jsonb_build_object('filters', new_event_filters); + END IF; + IF changed THEN + new_events := array_append(new_events, element); + END IF; + END LOOP; + IF array_length(new_events, 1) > 0 THEN + row.filter := row.filter || jsonb_build_object('events', new_events); + END IF; + END IF; + + -- Transform filters attributes + filters_att := row.filter -> 'filters'; + IF filters_att IS NOT NULL THEN + new_filters := '{}'::jsonb; + FOR element IN SELECT jsonb_array_elements(filters_att) + LOOP + new_value := get_new_filter_key(element ->> 'type'); + if new_value IS NOT NULL THEN + new_value := replace(new_value, '"', ''); + element := element || jsonb_build_object('type', new_value); + new_filters := array_append(new_filters, element); + END IF; + END LOOP; + IF array_length(new_filters, 1) > 0 THEN + row.filter := row.filter || jsonb_build_object('filters', new_filters); + END IF; + END IF; + + IF array_length(new_events, 1) > 0 OR array_length(new_filters, 1) > 0 THEN + planned_update := array_append(planned_update, + jsonb_build_object('id', row.series_id, 'change', row.filter)); + END IF; + END LOOP; + + -- Update metric_series + IF array_length(planned_update, 1) > 0 THEN + raise notice 'must update % elements',array_length(planned_update, 1); + + UPDATE metric_series + SET filter=changes.change -> 'change' + FROM (SELECT unnest(planned_update)) AS changes(change) + WHERE series_id = (changes.change -> 'id')::integer; + raise notice 'update done'; + ELSE + raise notice 'nothing to update'; + END IF; + END ; +$$ +LANGUAGE plpgsql; + +DROP FUNCTION get_new_filter_key; +DROP FUNCTION get_new_event_filter_key; +DROP FUNCTION get_new_event_key; +DROP FUNCTION get_global_key; + +DROP TABLE IF EXISTS public.funnels; +ALTER TABLE IF EXISTS public.metrics + ADD COLUMN IF NOT EXISTS data jsonb NULL; +COMMIT; + +CREATE INDEX CONCURRENTLY IF NOT EXISTS clicks_selector_idx ON events.clicks (selector); +CREATE INDEX CONCURRENTLY IF NOT EXISTS clicks_path_idx ON events.clicks (path); +CREATE INDEX CONCURRENTLY IF NOT EXISTS clicks_path_gin_idx ON events.clicks USING GIN (path gin_trgm_ops); +CREATE INDEX CONCURRENTLY IF NOT EXISTS issues_project_id_issue_id_idx ON public.issues (project_id, issue_id); diff --git a/scripts/schema/db/init_dbs/postgresql/init_schema.sql b/scripts/schema/db/init_dbs/postgresql/init_schema.sql index 79b0b649c..57dea2a58 100644 --- a/scripts/schema/db/init_dbs/postgresql/init_schema.sql +++ b/scripts/schema/db/init_dbs/postgresql/init_schema.sql @@ -6,10 +6,9 @@ CREATE SCHEMA IF NOT EXISTS events; CREATE OR REPLACE FUNCTION openreplay_version() RETURNS text AS $$ -SELECT 'v1.9.0' +SELECT 'v1.10.0' $$ LANGUAGE sql IMMUTABLE; --- --- accounts.sql --- CREATE OR REPLACE FUNCTION generate_api_key(length integer) RETURNS text AS $$ @@ -29,7 +28,6 @@ begin end; $$ LANGUAGE plpgsql; --- --- events.sql --- CREATE OR REPLACE FUNCTION events.funnel(steps integer[], m integer) RETURNS boolean AS $$ @@ -54,7 +52,6 @@ BEGIN END; $$ LANGUAGE plpgsql IMMUTABLE; --- --- integrations.sql --- CREATE OR REPLACE FUNCTION notify_integration() RETURNS trigger AS $$ @@ -70,7 +67,6 @@ BEGIN END; $$ LANGUAGE plpgsql; --- --- alerts.sql --- CREATE OR REPLACE FUNCTION notify_alert() RETURNS trigger AS $$ @@ -87,7 +83,6 @@ BEGIN END ; $$ LANGUAGE plpgsql; --- --- projects.sql --- CREATE OR REPLACE FUNCTION notify_project() RETURNS trigger AS $$ @@ -110,11 +105,9 @@ $$ ELSE raise notice 'Creating DB'; - -- --- public.sql --- CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS pgcrypto; --- --- accounts.sql --- CREATE TABLE tenants ( @@ -141,9 +134,9 @@ $$ email text NOT NULL UNIQUE, role user_role NOT NULL DEFAULT 'member', name text NOT NULL, - created_at timestamp without time zone NOT NULL default (now() at time zone 'utc'), + created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'), deleted_at timestamp without time zone NULL DEFAULT NULL, - api_key text UNIQUE default generate_api_key(20) not null, + api_key text UNIQUE DEFAULT generate_api_key(20) NOT NULL, jwt_iat timestamp without time zone NULL DEFAULT NULL, data jsonb NOT NULL DEFAULT '{}'::jsonb, weekly_report boolean NOT NULL DEFAULT TRUE @@ -171,7 +164,6 @@ $$ ); CREATE UNIQUE INDEX oauth_authentication_unique_user_id_provider_idx ON oauth_authentication (user_id, provider); --- --- projects.sql --- CREATE TABLE projects ( @@ -201,7 +193,8 @@ $$ "defaultInputMode": "plain" }'::jsonb, first_recorded_session_at timestamp without time zone NULL DEFAULT NULL, - sessions_last_check_at timestamp without time zone NULL DEFAULT NULL + sessions_last_check_at timestamp without time zone NULL DEFAULT NULL, + beacon_size integer NOT NULL DEFAULT 0 ); CREATE INDEX projects_project_key_idx ON public.projects (project_key); @@ -214,25 +207,22 @@ $$ EXECUTE PROCEDURE notify_project(); --- --- webhooks.sql --- + CREATE TYPE webhook_type AS ENUM ('webhook', 'slack', 'email', 'msteams'); - create type webhook_type as enum ('webhook', 'slack', 'email'); - - create table webhooks + CREATE TABLE webhooks ( - webhook_id integer generated by default as identity + webhook_id integer generated by DEFAULT as identity constraint webhooks_pkey primary key, - endpoint text not null, - created_at timestamp default timezone('utc'::text, now()) not null, + endpoint text NOT NULL, + created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL, deleted_at timestamp, auth_header text, - type webhook_type not null, - index integer default 0 not null, + type webhook_type NOT NULL DEFAULT 'webhook', + index integer DEFAULT 0 NOT NULL, name varchar(100) ); --- --- notifications.sql --- CREATE TABLE notifications ( @@ -258,42 +248,23 @@ $$ constraint user_viewed_notifications_pkey primary key (user_id, notification_id) ); --- --- funnels.sql --- - CREATE TABLE funnels + CREATE TYPE announcement_type AS ENUM ('notification', 'alert'); + + CREATE TABLE announcements ( - funnel_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY, - project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE, - user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, - name text not null, - filter jsonb not null, - created_at timestamp default timezone('utc'::text, now()) not null, - deleted_at timestamp, - is_public boolean NOT NULL DEFAULT False - ); - - CREATE INDEX funnels_user_id_is_public_idx ON public.funnels (user_id, is_public); - CREATE INDEX funnels_project_id_idx ON public.funnels (project_id); - --- --- announcements.sql --- - - create type announcement_type as enum ('notification', 'alert'); - - create table announcements - ( - announcement_id serial not null + announcement_id serial NOT NULL constraint announcements_pk primary key, - title text not null, - description text not null, + title text NOT NULL, + description text NOT NULL, button_text varchar(30), button_url text, image_url text, - created_at timestamp default timezone('utc'::text, now()) not null, - type announcement_type default 'notification'::announcement_type not null + created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL, + type announcement_type DEFAULT 'notification'::announcement_type NOT NULL ); --- --- integrations.sql --- CREATE TYPE integration_provider AS ENUM ('bugsnag', 'cloudwatch', 'datadog', 'newrelic', 'rollbar', 'sentry', 'stackdriver', 'sumologic', 'elasticsearch'); --, 'jira', 'github'); CREATE TABLE integrations @@ -312,20 +283,19 @@ $$ EXECUTE PROCEDURE notify_integration(); - create table jira_cloud + CREATE TABLE jira_cloud ( - user_id integer not null + user_id integer NOT NULL constraint jira_cloud_pk primary key constraint jira_cloud_users_fkey references users on delete cascade, - username text not null, - token text not null, + username text NOT NULL, + token text NOT NULL, url text ); --- --- issues.sql --- CREATE TYPE issue_type AS ENUM ( 'click_rage', @@ -358,10 +328,9 @@ $$ context jsonb DEFAULT NULL ); CREATE INDEX issues_issue_id_type_idx ON issues (issue_id, type); - CREATE INDEX issues_context_string_gin_idx ON public.issues USING GIN (context_string gin_trgm_ops); + CREATE INDEX issues_project_id_issue_id_idx ON public.issues (project_id, issue_id); CREATE INDEX issues_project_id_idx ON issues (project_id); --- --- errors.sql --- CREATE TYPE error_source AS ENUM ('js_exception', 'bugsnag', 'cloudwatch', 'datadog', 'newrelic', 'rollbar', 'sentry', 'stackdriver', 'sumologic', 'elasticsearch'); CREATE TYPE error_status AS ENUM ('unresolved', 'resolved', 'ignored'); @@ -406,7 +375,6 @@ $$ CREATE INDEX user_viewed_errors_error_id_idx ON public.user_viewed_errors (error_id); --- --- sessions.sql --- CREATE TYPE device_type AS ENUM ('desktop', 'tablet', 'mobile', 'other'); CREATE TYPE country AS ENUM ('UN', 'RW', 'SO', 'YE', 'IQ', 'SA', 'IR', 'CY', 'TZ', 'SY', 'AM', 'KE', 'CD', 'DJ', 'UG', 'CF', 'SC', 'JO', 'LB', 'KW', 'OM', 'QA', 'BH', 'AE', 'IL', 'TR', 'ET', 'ER', 'EG', 'SD', 'GR', 'BI', 'EE', 'LV', 'AZ', 'LT', 'SJ', 'GE', 'MD', 'BY', 'FI', 'AX', 'UA', 'MK', 'HU', 'BG', 'AL', 'PL', 'RO', 'XK', 'ZW', 'ZM', 'KM', 'MW', 'LS', 'BW', 'MU', 'SZ', 'RE', 'ZA', 'YT', 'MZ', 'MG', 'AF', 'PK', 'BD', 'TM', 'TJ', 'LK', 'BT', 'IN', 'MV', 'IO', 'NP', 'MM', 'UZ', 'KZ', 'KG', 'TF', 'HM', 'CC', 'PW', 'VN', 'TH', 'ID', 'LA', 'TW', 'PH', 'MY', 'CN', 'HK', 'BN', 'MO', 'KH', 'KR', 'JP', 'KP', 'SG', 'CK', 'TL', 'RU', 'MN', 'AU', 'CX', 'MH', 'FM', 'PG', 'SB', 'TV', 'NR', 'VU', 'NC', 'NF', 'NZ', 'FJ', 'LY', 'CM', 'SN', 'CG', 'PT', 'LR', 'CI', 'GH', 'GQ', 'NG', 'BF', 'TG', 'GW', 'MR', 'BJ', 'GA', 'SL', 'ST', 'GI', 'GM', 'GN', 'TD', 'NE', 'ML', 'EH', 'TN', 'ES', 'MA', 'MT', 'DZ', 'FO', 'DK', 'IS', 'GB', 'CH', 'SE', 'NL', 'AT', 'BE', 'DE', 'LU', 'IE', 'MC', 'FR', 'AD', 'LI', 'JE', 'IM', 'GG', 'SK', 'CZ', 'NO', 'VA', 'SM', 'IT', 'SI', 'ME', 'HR', 'BA', 'AO', 'NA', 'SH', 'BV', 'BB', 'CV', 'GY', 'GF', 'SR', 'PM', 'GL', 'PY', 'UY', 'BR', 'FK', 'GS', 'JM', 'DO', 'CU', 'MQ', 'BS', 'BM', 'AI', 'TT', 'KN', 'DM', 'AG', 'LC', 'TC', 'AW', 'VG', 'VC', 'MS', 'MF', 'BL', 'GP', 'GD', 'KY', 'BZ', 'SV', 'GT', 'HN', 'NI', 'CR', 'VE', 'EC', 'CO', 'PA', 'HT', 'AR', 'CL', 'BO', 'PE', 'MX', 'PF', 'PN', 'KI', 'TK', 'TO', 'WF', 'WS', 'NU', 'MP', 'GU', 'PR', 'VI', 'UM', 'AS', 'CA', 'US', 'PS', 'RS', 'AQ', 'SX', 'CW', 'BQ', 'SS','AC','AN','BU','CP','CS','CT','DD','DG','DY','EA','FQ','FX','HV','IC','JT','MI','NH','NQ','NT','PC','PU','PZ','RH','SU','TA','TP','VD','WK','YD','YU','ZR'); CREATE TYPE platform AS ENUM ('web','ios','android'); @@ -485,12 +453,9 @@ $$ CREATE INDEX sessions_metadata8_gin_idx ON public.sessions USING GIN (metadata_8 gin_trgm_ops); CREATE INDEX sessions_metadata9_gin_idx ON public.sessions USING GIN (metadata_9 gin_trgm_ops); CREATE INDEX sessions_metadata10_gin_idx ON public.sessions USING GIN (metadata_10 gin_trgm_ops); - CREATE INDEX sessions_user_os_gin_idx ON public.sessions USING GIN (user_os gin_trgm_ops); - CREATE INDEX sessions_user_browser_gin_idx ON public.sessions USING GIN (user_browser gin_trgm_ops); CREATE INDEX sessions_user_device_gin_idx ON public.sessions USING GIN (user_device gin_trgm_ops); CREATE INDEX sessions_user_id_gin_idx ON public.sessions USING GIN (user_id gin_trgm_ops); CREATE INDEX sessions_user_anonymous_id_gin_idx ON public.sessions USING GIN (user_anonymous_id gin_trgm_ops); - CREATE INDEX sessions_user_country_gin_idx ON public.sessions (project_id, user_country); CREATE INDEX sessions_start_ts_idx ON public.sessions (start_ts) WHERE duration > 0; CREATE INDEX sessions_project_id_idx ON public.sessions (project_id) WHERE duration > 0; CREATE INDEX sessions_session_id_project_id_start_ts_idx ON sessions (session_id, project_id, start_ts) WHERE duration > 0; @@ -532,21 +497,18 @@ $$ ); CREATE INDEX user_favorite_sessions_user_id_session_id_idx ON user_favorite_sessions (user_id, session_id); --- --- assignments.sql --- - create table assigned_sessions + CREATE TABLE assigned_sessions ( session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE, issue_id text NOT NULL, provider oauth_provider NOT NULL, created_by integer NOT NULL, - created_at timestamp default timezone('utc'::text, now()) NOT NULL, - provider_data jsonb default '{}'::jsonb NOT NULL + created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL, + provider_data jsonb DEFAULT '{}'::jsonb NOT NULL ); CREATE INDEX assigned_sessions_session_id_idx ON assigned_sessions (session_id); --- --- events_common.sql --- - CREATE TYPE events_common.custom_level AS ENUM ('info','error'); @@ -576,7 +538,6 @@ $$ ); CREATE INDEX issues_issue_id_timestamp_idx ON events_common.issues (issue_id, timestamp); CREATE INDEX issues_timestamp_idx ON events_common.issues (timestamp); - CREATE INDEX issues_project_id_issue_id_idx ON public.issues (project_id, issue_id); CREATE TYPE http_method AS ENUM ('GET','HEAD','POST','PUT','DELETE','CONNECT','OPTIONS','TRACE','PATCH'); CREATE TABLE events_common.requests @@ -604,14 +565,11 @@ $$ CREATE INDEX requests_response_body_nn_gin_idx ON events_common.requests USING GIN (response_body gin_trgm_ops) WHERE response_body IS NOT NULL; CREATE INDEX requests_status_code_nn_idx ON events_common.requests (status_code) WHERE status_code IS NOT NULL; CREATE INDEX requests_session_id_status_code_nn_idx ON events_common.requests (session_id, status_code) WHERE status_code IS NOT NULL; - CREATE INDEX requests_host_nn_idx ON events_common.requests (host) WHERE host IS NOT NULL; CREATE INDEX requests_host_nn_gin_idx ON events_common.requests USING GIN (host gin_trgm_ops) WHERE host IS NOT NULL; CREATE INDEX requests_path_nn_idx ON events_common.requests (path) WHERE path IS NOT NULL; CREATE INDEX requests_path_nn_gin_idx ON events_common.requests USING GIN (path gin_trgm_ops) WHERE path IS NOT NULL; - CREATE INDEX requests_query_nn_idx ON events_common.requests (query) WHERE query IS NOT NULL; CREATE INDEX requests_query_nn_gin_idx ON events_common.requests USING GIN (query gin_trgm_ops) WHERE query IS NOT NULL; --- --- events.sql --- CREATE TABLE events.pages ( @@ -641,14 +599,6 @@ $$ CREATE INDEX pages_timestamp_idx ON events.pages (timestamp); CREATE INDEX pages_session_id_timestamp_idx ON events.pages (session_id, timestamp); CREATE INDEX pages_base_referrer_idx ON events.pages (base_referrer); - CREATE INDEX pages_base_referrer_gin_idx2 ON events.pages USING GIN (RIGHT(base_referrer, - length(base_referrer) - (CASE - WHEN base_referrer LIKE 'http://%' - THEN 7 - WHEN base_referrer LIKE 'https://%' - THEN 8 - ELSE 0 END)) - gin_trgm_ops); CREATE INDEX pages_response_time_idx ON events.pages (response_time); CREATE INDEX pages_response_end_idx ON events.pages (response_end); CREATE INDEX pages_path_gin_idx ON events.pages USING GIN (path gin_trgm_ops); @@ -683,6 +633,7 @@ $$ timestamp bigint NOT NULL, label text DEFAULT NULL, url text DEFAULT '' NOT NULL, + path text, selector text DEFAULT '' NOT NULL, PRIMARY KEY (session_id, message_id) ); @@ -692,10 +643,11 @@ $$ CREATE INDEX clicks_timestamp_idx ON events.clicks (timestamp); CREATE INDEX clicks_label_session_id_timestamp_idx ON events.clicks (label, session_id, timestamp); CREATE INDEX clicks_url_idx ON events.clicks (url); - CREATE INDEX clicks_url_gin_idx ON events.clicks USING GIN (url gin_trgm_ops); CREATE INDEX clicks_url_session_id_timestamp_selector_idx ON events.clicks (url, session_id, timestamp, selector); CREATE INDEX clicks_session_id_timestamp_idx ON events.clicks (session_id, timestamp); - + CREATE INDEX clicks_selector_idx ON events.clicks (selector); + CREATE INDEX clicks_path_idx ON events.clicks (path); + CREATE INDEX clicks_path_gin_idx ON events.clicks USING GIN (path gin_trgm_ops); CREATE TABLE events.inputs ( @@ -707,9 +659,7 @@ $$ PRIMARY KEY (session_id, message_id) ); CREATE INDEX inputs_session_id_idx ON events.inputs (session_id); - CREATE INDEX inputs_label_value_idx ON events.inputs (label, value); CREATE INDEX inputs_label_gin_idx ON events.inputs USING GIN (label gin_trgm_ops); - CREATE INDEX inputs_label_idx ON events.inputs (label); CREATE INDEX inputs_timestamp_idx ON events.inputs (timestamp); CREATE INDEX inputs_label_session_id_timestamp_idx ON events.inputs (label, session_id, timestamp); @@ -771,7 +721,6 @@ $$ name text NOT NULL, PRIMARY KEY (session_id, message_id) ); - CREATE INDEX state_actions_name_idx ON events.state_actions (name); CREATE INDEX state_actions_name_gin_idx ON events.state_actions USING GIN (name gin_trgm_ops); CREATE INDEX state_actions_timestamp_idx ON events.state_actions (timestamp); @@ -799,17 +748,12 @@ $$ CREATE INDEX resources_session_id_idx ON events.resources (session_id); CREATE INDEX resources_status_idx ON events.resources (status); CREATE INDEX resources_type_idx ON events.resources (type); - CREATE INDEX resources_duration_durationgt0_idx ON events.resources (duration) WHERE duration > 0; CREATE INDEX resources_url_host_idx ON events.resources (url_host); CREATE INDEX resources_timestamp_idx ON events.resources (timestamp); CREATE INDEX resources_success_idx ON events.resources (success); - CREATE INDEX resources_url_gin_idx ON events.resources USING GIN (url gin_trgm_ops); - CREATE INDEX resources_url_idx ON events.resources (url); CREATE INDEX resources_url_hostpath_gin_idx ON events.resources USING GIN (url_hostpath gin_trgm_ops); - CREATE INDEX resources_url_hostpath_idx ON events.resources (url_hostpath); CREATE INDEX resources_timestamp_type_durationgt0NN_idx ON events.resources (timestamp, type) WHERE duration > 0 AND duration IS NOT NULL; - CREATE INDEX resources_session_id_timestamp_idx ON events.resources (session_id, timestamp); CREATE INDEX resources_session_id_timestamp_type_idx ON events.resources (session_id, timestamp, type); CREATE INDEX resources_timestamp_type_durationgt0NN_noFetch_idx ON events.resources (timestamp, type) WHERE duration > 0 AND duration IS NOT NULL AND type != 'fetch'; CREATE INDEX resources_session_id_timestamp_url_host_fail_idx ON events.resources (session_id, timestamp, url_host) WHERE success = FALSE; @@ -847,8 +791,6 @@ $$ CREATE INDEX performance_avg_used_js_heap_size_gt0_idx ON events.performance (avg_used_js_heap_size) WHERE avg_used_js_heap_size > 0; --- --- autocomplete.sql --- - CREATE TABLE autocomplete ( value text NOT NULL, @@ -887,8 +829,8 @@ $$ project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE, action job_action NOT NULL, reference_id text NOT NULL, - created_at timestamp default timezone('utc'::text, now()) NOT NULL, - updated_at timestamp default timezone('utc'::text, now()) NULL, + created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL, + updated_at timestamp DEFAULT timezone('utc'::text, now()) NULL, start_at timestamp NOT NULL, errors text NULL ); @@ -896,37 +838,29 @@ $$ CREATE INDEX jobs_start_at_idx ON jobs (start_at); CREATE INDEX jobs_project_id_idx ON jobs (project_id); - CREATE TYPE metric_type AS ENUM ('timeseries','table', 'predefined', 'funnel'); - CREATE TYPE metric_view_type AS ENUM ('lineChart','progress','table','pieChart','areaChart','barChart','stackedBarChart','stackedBarLineChart','overview','map'); CREATE TABLE metrics ( metric_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY, - project_id integer NULL REFERENCES projects (project_id) ON DELETE CASCADE, - user_id integer REFERENCES users (user_id) ON DELETE SET NULL, - name text NOT NULL, - is_public boolean NOT NULL DEFAULT FALSE, - active boolean NOT NULL DEFAULT TRUE, - created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), + project_id integer NULL REFERENCES projects (project_id) ON DELETE CASCADE, + user_id integer REFERENCES users (user_id) ON DELETE SET NULL, + name text NOT NULL, + is_public boolean NOT NULL DEFAULT TRUE, + created_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), deleted_at timestamp, - edited_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), - metric_type metric_type NOT NULL DEFAULT 'timeseries', - view_type metric_view_type NOT NULL DEFAULT 'lineChart', - metric_of text NOT NULL DEFAULT 'sessionCount', - metric_value text[] NOT NULL DEFAULT '{}'::text[], + edited_at timestamp NOT NULL DEFAULT timezone('utc'::text, now()), + metric_type text NOT NULL DEFAULT 'timeseries', + view_type text NOT NULL DEFAULT 'lineChart', + metric_of text NOT NULL DEFAULT 'sessionCount', + metric_value text[] NOT NULL DEFAULT '{}'::text[], metric_format text, - category text NULL DEFAULT 'custom', - is_pinned boolean NOT NULL DEFAULT FALSE, - is_predefined boolean NOT NULL DEFAULT FALSE, - is_template boolean NOT NULL DEFAULT FALSE, - predefined_key text NULL DEFAULT NULL, - default_config jsonb NOT NULL DEFAULT '{ + thumbnail text, + is_pinned boolean NOT NULL DEFAULT FALSE, + default_config jsonb NOT NULL DEFAULT '{ "col": 2, "row": 2, "position": 0 }'::jsonb, - CONSTRAINT null_project_id_for_template_only - CHECK ( (metrics.category != 'custom') != (metrics.project_id IS NOT NULL) ), - CONSTRAINT unique_key UNIQUE (predefined_key) + data jsonb NULL ); CREATE INDEX metrics_user_id_is_public_idx ON public.metrics (user_id, is_public); @@ -971,9 +905,9 @@ $$ search_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY, project_id integer NOT NULL REFERENCES projects (project_id) ON DELETE CASCADE, user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, - name text not null, - filter jsonb not null, - created_at timestamp default timezone('utc'::text, now()) not null, + name text NOT NULL, + filter jsonb NOT NULL, + created_at timestamp DEFAULT timezone('utc'::text, now()) NOT NULL, deleted_at timestamp, is_public boolean NOT NULL DEFAULT False ); @@ -1013,7 +947,7 @@ $$ ( note_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY, message text NOT NULL, - created_at timestamp without time zone NOT NULL default (now() at time zone 'utc'), + created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'), user_id integer NULL REFERENCES users (user_id) ON DELETE SET NULL, deleted_at timestamp without time zone NULL DEFAULT NULL, tag text NULL, @@ -1030,244 +964,4 @@ $$ $$ LANGUAGE plpgsql; -INSERT INTO metrics (name, category, default_config, is_predefined, is_template, is_public, predefined_key, metric_type, - view_type) -VALUES ('Captured sessions', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 -}', true, true, true, 'count_sessions', 'predefined', 'overview'), - ('Request Load Time', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_request_load_time', 'predefined', 'overview'), - ('Page Load Time', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_page_load_time', 'predefined', 'overview'), - ('Image Load Time', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_image_load_time', 'predefined', 'overview'), - ('DOM Content Load Start', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_dom_content_load_start', 'predefined', 'overview'), - ('First Meaningful paint', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_first_contentful_pixel', 'predefined', 'overview'), - ('No. of Visited Pages', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_visited_pages', 'predefined', 'overview'), - ('Session Duration', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_session_duration', 'predefined', 'overview'), - ('DOM Build Time', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_pages_dom_buildtime', 'predefined', 'overview'), - ('Pages Response Time', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_pages_response_time', 'predefined', 'overview'), - ('Response Time', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_response_time', 'predefined', 'overview'), - ('First Paint', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_first_paint', 'predefined', 'overview'), - ('DOM Content Loaded', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_dom_content_loaded', 'predefined', 'overview'), - ('Time Till First byte', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_till_first_byte', 'predefined', 'overview'), - ('Time To Interactive', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_time_to_interactive', 'predefined', 'overview'), - ('Captured requests', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'count_requests', 'predefined', 'overview'), - ('Time To Render', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_time_to_render', 'predefined', 'overview'), - ('Memory Consumption', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_used_js_heap_size', 'predefined', 'overview'), - ('CPU Load', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_cpu', 'predefined', 'overview'), - ('Frame rate', 'web vitals', '{ - "col": 1, - "row": 1, - "position": 0 - }', true, true, true, 'avg_fps', 'predefined', 'overview'), - - ('Sessions Affected by JS Errors', 'errors', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'impacted_sessions_by_js_errors', 'predefined', 'barChart'), - ('Top Domains with 4xx Fetch Errors', 'errors', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'domains_errors_4xx', 'predefined', 'lineChart'), - ('Top Domains with 5xx Fetch Errors', 'errors', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'domains_errors_5xx', 'predefined', 'lineChart'), - ('Errors per Domain', 'errors', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'errors_per_domains', 'predefined', 'table'), - ('Fetch Calls with Errors', 'errors', '{ - "col": 4, - "row": 2, - "position": 0 - }', true, true, true, 'calls_errors', 'predefined', 'table'), - ('Errors by Type', 'errors', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'errors_per_type', 'predefined', 'barChart'), - ('Errors by Origin', 'errors', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'resources_by_party', 'predefined', 'stackedBarChart'), - - ('Speed Index by Location', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'speed_location', 'predefined', 'map'), - ('Slowest Domains', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'slowest_domains', 'predefined', 'table'), - ('Sessions per Browser', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'sessions_per_browser', 'predefined', 'table'), - ('Time To Render', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'time_to_render', 'predefined', 'areaChart'), - ('Sessions Impacted by Slow Pages', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'impacted_sessions_by_slow_pages', 'predefined', 'areaChart'), - ('Memory Consumption', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'memory_consumption', 'predefined', 'areaChart'), - ('CPU Load', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'cpu', 'predefined', 'areaChart'), - ('Frame Rate', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'fps', 'predefined', 'areaChart'), - ('Crashes', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'crashes', 'predefined', 'areaChart'), - ('Resources Loaded vs Visually Complete', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'resources_vs_visually_complete', 'predefined', 'areaChart'), - ('DOM Build Time', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'pages_dom_buildtime', 'predefined', 'areaChart'), - ('Pages Response Time', 'performance', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'pages_response_time', 'predefined', 'areaChart'), - ('Pages Response Time Distribution', 'performance', '{ - "col": 4, - "row": 2, - "position": 0 - }', true, true, true, 'pages_response_time_distribution', 'predefined', 'barChart'), - - ('Missing Resources', 'resources', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'missing_resources', 'predefined', 'table'), - ('Slowest Resources', 'resources', '{ - "col": 4, - "row": 2, - "position": 0 - }', true, true, true, 'slowest_resources', 'predefined', 'table'), - ('Resources Fetch Time', 'resources', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'resources_loading_time', 'predefined', 'table'), - ('Resource Loaded vs Response End', 'resources', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'resource_type_vs_response_end', 'predefined', 'stackedBarLineChart'), - ('Breakdown of Loaded Resources', 'resources', '{ - "col": 2, - "row": 2, - "position": 0 - }', true, true, true, 'resources_count_by_type', 'predefined', 'stackedBarChart') -ON CONFLICT (predefined_key) DO UPDATE - SET name=excluded.name, - category=excluded.category, - default_config=excluded.default_config, - is_predefined=excluded.is_predefined, - is_template=excluded.is_template, - is_public=excluded.is_public, - metric_type=excluded.metric_type, - view_type=excluded.view_type; - COMMIT; diff --git a/sourcemap-reader/Dockerfile b/sourcemap-reader/Dockerfile index 493317ba2..9f2b257db 100644 --- a/sourcemap-reader/Dockerfile +++ b/sourcemap-reader/Dockerfile @@ -1,5 +1,7 @@ FROM node:18-alpine LABEL Maintainer="KRAIEM Taha Yassine<tahayk2@gmail.com>" +ARG GIT_SHA +LABEL GIT_SHA=$GIT_SHA RUN apk add --no-cache tini ARG envarg @@ -8,6 +10,7 @@ ENV SOURCE_MAP_VERSION=0.7.4 \ LISTEN_PORT=9000 \ MAPPING_WASM=/work/mappings.wasm \ PRIVATE_ENDPOINTS=true \ + GIT_SHA=$GIT_SHA \ ENTERPRISE_BUILD=${envarg} ADD https://unpkg.com/source-map@${SOURCE_MAP_VERSION}/lib/mappings.wasm ${MAPPING_WASM} diff --git a/sourcemap-reader/build.sh b/sourcemap-reader/build.sh index 9767512f3..fbe8762e2 100644 --- a/sourcemap-reader/build.sh +++ b/sourcemap-reader/build.sh @@ -10,7 +10,8 @@ set -e image_name="sourcemaps-reader" -git_sha1=${IMAGE_TAG:-$(git rev-parse HEAD)} +git_sha=$(git rev-parse --short HEAD) +image_tag=${IMAGE_TAG:-git_sha} envarg="default-foss" tmp_folder_name="${image_name}_${RANDOM}" @@ -33,18 +34,21 @@ function build_api(){ tag="" # Copy enterprise code [[ $1 == "ee" ]] && { - cp -rf ../ee/sourcemap-reader/* ./ + cp -rf ../ee/sourcemap-reader/* ./ || true # We share same codebase for ee/foss envarg="default-ee" tag="ee-" } - docker build -f ./Dockerfile --build-arg envarg=$envarg -t ${DOCKER_REPO:-'local'}/${image_name}:${git_sha1} . + docker build -f ./Dockerfile --build-arg GIT_SHA=$git_sha --build-arg envarg=$envarg -t ${DOCKER_REPO:-'local'}/${image_name}:${image_tag} . cd ../sourcemap-reader rm -rf ../${destination} [[ $PUSH_IMAGE -eq 1 ]] && { - docker push ${DOCKER_REPO:-'local'}/${image_name}:${git_sha1} - docker tag ${DOCKER_REPO:-'local'}/${image_name}:${git_sha1} ${DOCKER_REPO:-'local'}/${image_name}:${tag}latest + docker push ${DOCKER_REPO:-'local'}/${image_name}:${image_tag} + docker tag ${DOCKER_REPO:-'local'}/${image_name}:${image_tag} ${DOCKER_REPO:-'local'}/${image_name}:${tag}latest docker push ${DOCKER_REPO:-'local'}/${image_name}:${tag}latest } + [[ $SIGN_IMAGE -eq 1 ]] && { + cosign sign --key $SIGN_KEY ${DOCKER_REPO:-'local'}/$image_name:${image_tag} + } echo "${image_name} docker build completed" } diff --git a/sourcemap-reader/clean.sh b/sourcemap-reader/clean-dev.sh similarity index 100% rename from sourcemap-reader/clean.sh rename to sourcemap-reader/clean-dev.sh diff --git a/sourcemap-reader/package-lock.json b/sourcemap-reader/package-lock.json index f76942c71..cbaebc3c1 100644 --- a/sourcemap-reader/package-lock.json +++ b/sourcemap-reader/package-lock.json @@ -9,9 +9,8 @@ "version": "1.0.0", "license": "Elastic License 2.0 (ELv2)", "dependencies": { - "aws-sdk": "^2.1172.0", - "express": "^4.18.1", - "request": "^2.88.2", + "aws-sdk": "^2.1314.0", + "express": "^4.18.2", "source-map": "^0.7.4" } }, @@ -27,47 +26,11 @@ "node": ">= 0.6" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "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/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "engines": { - "node": ">=0.8" - } - }, - "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", @@ -80,9 +43,9 @@ } }, "node_modules/aws-sdk": { - "version": "2.1262.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1262.0.tgz", - "integrity": "sha512-XbaK/XUIxwLEBnHANhJ0RTZtiU288lFRj5FllSihQ5Kb0fibKyW8kJFPsY+NzzDezLH5D3WdGbTKb9fycn5TbA==", + "version": "2.1314.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1314.0.tgz", + "integrity": "sha512-2jsfvgtOQ6kRflaicn50ndME4YoIaBhlus/dZCExtWNXeu8ePh+eAtflsYs6aqIiRPKhCBLaqClzahWm7hC0XA==", "dependencies": { "buffer": "4.9.2", "events": "1.1.1", @@ -99,19 +62,6 @@ "node": ">= 10.0.0" } }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -131,14 +81,6 @@ } ] }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -192,22 +134,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" - }, - "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/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -220,9 +146,9 @@ } }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { "node": ">= 0.6" } @@ -240,22 +166,6 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -264,14 +174,6 @@ "ms": "2.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", @@ -289,15 +191,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -373,29 +266,6 @@ "node": ">= 0.10.0" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "engines": [ - "node >=0.6.0" - ] - }, - "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==" - }, - "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==" - }, "node_modules/finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -421,27 +291,6 @@ "is-callable": "^1.1.3" } }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -464,9 +313,9 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -476,14 +325,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -495,27 +336,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -567,20 +387,6 @@ "node": ">= 0.8" } }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -668,21 +474,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" - }, "node_modules/jmespath": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", @@ -691,40 +487,6 @@ "node": ">= 0.6.0" } }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "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==" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" - }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -789,18 +551,10 @@ "node": ">= 0.6" } }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "engines": { - "node": "*" - } - }, "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "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" } @@ -829,11 +583,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -846,18 +595,10 @@ "node": ">= 0.10" } }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "engines": { - "node": ">=6" - } + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" }, "node_modules/qs": { "version": "6.11.0", @@ -904,54 +645,6 @@ "node": ">= 0.8" } }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request/node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/request/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1049,30 +742,6 @@ "node": ">= 8" } }, - "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -1089,34 +758,6 @@ "node": ">=0.6" } }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "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==", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" - }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1137,14 +778,6 @@ "node": ">= 0.8" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/url": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", @@ -1154,11 +787,6 @@ "querystring": "0.2.0" } }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" - }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -1195,19 +823,6 @@ "node": ">= 0.8" } }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "node_modules/which-typed-array": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", diff --git a/sourcemap-reader/package.json b/sourcemap-reader/package.json index bf0a60d44..9d5a2806b 100644 --- a/sourcemap-reader/package.json +++ b/sourcemap-reader/package.json @@ -18,9 +18,8 @@ }, "homepage": "https://github.com/openreplay/openreplay#readme", "dependencies": { - "aws-sdk": "^2.1172.0", - "express": "^4.18.1", - "request": "^2.88.2", + "aws-sdk": "^2.1314.0", + "express": "^4.18.2", "source-map": "^0.7.4" } } diff --git a/sourcemap-reader/servers/sourcemaps-handler.js b/sourcemap-reader/servers/sourcemaps-handler.js index 86c93df7b..a19d4e1c2 100644 --- a/sourcemap-reader/servers/sourcemaps-handler.js +++ b/sourcemap-reader/servers/sourcemaps-handler.js @@ -3,7 +3,7 @@ const fs = require('fs'); const sourceMap = require('source-map'); const AWS = require('aws-sdk'); const URL = require('url'); -const request = require('request'); +const http = require('http'); const wasm = fs.readFileSync(process.env.MAPPING_WASM || '/mappings.wasm'); sourceMap.SourceMapConsumer.initialize({ "lib/mappings.wasm": wasm @@ -83,26 +83,54 @@ module.exports.sourcemapReader = async event => { }; return new Promise(function (resolve, reject) { const getObjectStart = Date.now(); - return request.get(options.URL, (err, response, sourcemap) => { - if (err || response.statusCode !== 200) { + return http.get(options.URL, (response) => { + const {statusCode} = response; + const contentType = response.headers['content-type']; + + let err; + // Any 2xx status code signals a successful response but + // here we're only checking for 200. + if (statusCode !== 200) { + err = new Error('Request Failed.\n' + + `Status Code: ${statusCode}`); + } else if (!/^application\/json/.test(contentType)) { + err = new Error('Invalid content-type.\n' + + `Expected application/json but received ${contentType}`); + } + if (err) { + // Consume response data to free up memory + response.resume(); + console.error("[SR] Getting file from URL failed"); console.error("err:"); - console.error(err); + console.error(err.message); console.error("response:"); - if (err) { - return reject(err); + return reject(err); + } + response.setEncoding('utf8'); + let rawData = ''; + response.on('data', (chunk) => { + rawData += chunk; + }); + response.on('end', () => { + try { + const sourcemap = JSON.parse(rawData); + const getObjectEnd = Date.now(); + options.fileSize = (response.headers['content-length'] / 1024) / 1024; + options.fileSizeUnit = 'Mb'; + options.downloadTime = (getObjectEnd - getObjectStart) / 1000; + options.downloadTimeUnit = 's'; + if (options.fileSize >= 3) { + console.log("[SR] large file:" + JSON.stringify(options)); + } + return parseSourcemap(sourcemap, event, options, resolve, reject); + } catch (e) { + return reject(e); } - return reject(response); - } - const getObjectEnd = Date.now(); - options.fileSize = (response.headers['content-length'] / 1024) / 1024; - options.fileSizeUnit = 'Mb'; - options.downloadTime = (getObjectEnd - getObjectStart) / 1000; - options.downloadTimeUnit = 's'; - if (options.fileSize >= 3) { - console.log("[SR] large file:" + JSON.stringify(options)); - } - return parseSourcemap(sourcemap, event, options, resolve, reject); + }); + + }).on('error', (e) => { + return reject(e); }); }); } else { diff --git a/sourcemap-uploader/cli.js b/sourcemap-uploader/cli.js index 28c167ea3..10977a57a 100755 --- a/sourcemap-uploader/cli.js +++ b/sourcemap-uploader/cli.js @@ -7,48 +7,49 @@ const { version, description } = require('./package.json'); const { uploadFile, uploadDir } = require('./index.js'); const parser = new ArgumentParser({ - version, description, }); -parser.addArgument(['-k', '--api-key'], { +parser.add_argument('-v', '--version', { action: 'version', version }); +parser.add_argument('-k', '--api-key', { help: 'API key', required: true, }); -parser.addArgument(['-p', '-i', '--project-key'], { +parser.add_argument('-p', '-i', '--project-key', { // -i is depricated help: 'Project Key', required: true, }); -parser.addArgument(['-s', '--server'], { +parser.add_argument('-s', '--server', { help: 'OpenReplay API server URL for upload', }); // Should be verbose, but conflicting on npm compilation into bin -parser.addArgument(['-l', '--logs'], { +parser.add_argument('-l', '--logs', { help: 'Log requests information', - action: 'storeTrue', + action: 'store_true', }); -const subparsers = parser.addSubparsers({ +const subparsers = parser.add_subparsers({ title: 'commands', dest: 'command', + required: true, }); -const file = subparsers.addParser('file'); -file.addArgument(['-m', '--sourcemap-file-path'], { +const file = subparsers.add_parser('file'); +file.add_argument('-m', '--sourcemap-file-path', { help: 'Local path to the sourcemap file', required: true, }); -file.addArgument(['-u', '--js-file-url'], { +file.add_argument('-u', '--js-file-url', { help: 'URL to the minified js file', required: true, }); -const dir = subparsers.addParser('dir'); -dir.addArgument(['-m', '--sourcemap-dir-path'], { +const dir = subparsers.add_parser('dir'); +dir.add_argument('-m', '--sourcemap-dir-path', { help: 'Dir with the sourcemap files', required: true, }); -dir.addArgument(['-u', '--js-dir-url'], { +dir.add_argument('-u', '--js-dir-url', { help: 'Base URL where the corresponding dir will be placed', required: true, }); @@ -56,10 +57,10 @@ dir.addArgument(['-u', '--js-dir-url'], { // TODO: exclude in dir const { command, api_key, project_key, server, logs, ...args } = - parser.parseArgs(); + parser.parse_args(); global._VERBOSE = !!logs; - +console.log(command); (command === 'file' ? uploadFile( api_key, @@ -76,7 +77,7 @@ global._VERBOSE = !!logs; server, ) ) - .then((sourceFiles) => + .then((sourceFiles) => console.log('asd') || sourceFiles.length > 0 ? console.log( `Successfully uploaded ${sourceFiles.length} sourcemap file${ diff --git a/sourcemap-uploader/index.js b/sourcemap-uploader/index.js index dabbee434..5874ebd96 100644 --- a/sourcemap-uploader/index.js +++ b/sourcemap-uploader/index.js @@ -21,6 +21,8 @@ module.exports = { server, ) { const sourcemaps = await readDir(sourcemap_dir_path, js_dir_url); + console.log(sourcemaps) + return uploadSourcemaps(api_key, project_key, sourcemaps, server); }, }; diff --git a/sourcemap-uploader/package-lock.json b/sourcemap-uploader/package-lock.json deleted file mode 100644 index 0b38bb293..000000000 --- a/sourcemap-uploader/package-lock.json +++ /dev/null @@ -1,1048 +0,0 @@ -{ - "name": "@openreplay/sourcemap-uploader", - "version": "3.0.6", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "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, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - } - } - }, - "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - } - }, - "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", - "dev": true - }, - "@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" - }, - "@types/glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", - "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", - "requires": { - "@types/events": "*", - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" - }, - "@types/node": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.0.tgz", - "integrity": "sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ==" - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "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 - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "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 - }, - "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, - "requires": { - "color-convert": "^1.9.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "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, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "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, - "requires": { - "color-convert": "^2.0.1" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "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, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "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, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "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, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "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 - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "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 - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "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 - }, - "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - } - }, - "eslint-config-prettier": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", - "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", - "dev": true - }, - "eslint-plugin-prettier": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", - "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "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, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "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 - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "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, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "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 - }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "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 - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "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, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "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, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-promise": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-3.4.0.tgz", - "integrity": "sha512-q08RJ6O+eJn+dVanerAndJwIcumgbDdYiUT7zFQl3Wm1xD6fBKtah7H8ZJChj4wP+8C+QfeVy8xautR7rdmKEw==", - "requires": { - "@types/glob": "*" - } - }, - "globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "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, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "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 - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "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 - }, - "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, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "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 - }, - "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": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "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, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "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 - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "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, - "requires": { - "yallist": "^4.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "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, - "requires": { - "callsites": "^3.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "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 - }, - "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 - }, - "prettier": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", - "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", - "dev": true - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "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 - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "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, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "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 - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "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, - "requires": { - "color-convert": "^2.0.1" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - } - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "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, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "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, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "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 - }, - "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, - "requires": { - "has-flag": "^3.0.0" - } - }, - "table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.6.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", - "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "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, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "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 - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } -} diff --git a/sourcemap-uploader/package.json b/sourcemap-uploader/package.json index ee495605e..5c4efa97c 100644 --- a/sourcemap-uploader/package.json +++ b/sourcemap-uploader/package.json @@ -1,6 +1,6 @@ { "name": "@openreplay/sourcemap-uploader", - "version": "3.0.7", + "version": "3.0.8", "description": "NPM module to upload your JS sourcemaps files to OpenReplay", "bin": "cli.js", "main": "index.js", @@ -13,13 +13,13 @@ ], "license": "MIT", "devDependencies": { - "eslint": "^7.32.0", + "eslint": "^8.28.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "prettier": "^2.4.1" }, "dependencies": { - "argparse": "^1.0.10", - "glob-promise": "^3.4.0" + "argparse": "^2.0.1", + "glob-promise": "^5.0.0" } } diff --git a/sourcemap-uploader/yarn.lock b/sourcemap-uploader/yarn.lock new file mode 100644 index 000000000..a2f7fdeea --- /dev/null +++ b/sourcemap-uploader/yarn.lock @@ -0,0 +1,707 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@eslint/eslintrc@^1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.3.tgz#2b044ab39fdfa75b4688184f9e573ce3c5b0ff95" + integrity sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.4.0" + globals "^13.15.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" + +"@humanwhocodes/config-array@^0.11.6": + version "0.11.7" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.7.tgz#38aec044c6c828f6ed51d5d7ae3d9b9faf6dbb0f" + integrity sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@types/glob@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" + integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/minimatch@*": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" + integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== + +"@types/node@*": + version "18.11.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" + integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.8.0: + version "8.8.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" + integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== + +ajv@^6.10.0, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + 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" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.1, debug@^4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^8.3.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" + integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== + +eslint-plugin-prettier@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" + integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + +eslint@^8.28.0: + version "8.28.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.28.0.tgz#81a680732634677cc890134bcdd9fdfea8e63d6e" + integrity sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ== + dependencies: + "@eslint/eslintrc" "^1.3.3" + "@humanwhocodes/config-array" "^0.11.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.10.0" + 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.1.1" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.3.0" + espree "^9.4.0" + esquery "^1.4.0" + 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.15.0" + grapheme-splitter "^1.0.4" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-sdsl "^4.1.4" + 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.1" + regexpp "^3.2.0" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + +espree@^9.4.0: + version "9.4.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.1.tgz#51d6092615567a2c2cff7833445e37c28c0065bd" + integrity sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg== + dependencies: + acorn "^8.8.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.3.0" + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.7" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" + integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-promise@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/glob-promise/-/glob-promise-5.0.0.tgz#aee3317ee1c211866d0425e1ac2e9089ded09c28" + integrity sha512-YQ7+beqmmQ3yUzybHoxnj7XMImztIdusIFate36/aB1gEWugR1qZI9a2u1A5mt6gYRqYldipZ7NB9s1UEq3vzw== + dependencies: + "@types/glob" "^7.2.0" + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + 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" + +globals@^13.15.0: + version "13.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.18.0.tgz#fb224daeeb2bb7d254cd2c640f003528b8d0c1dc" + integrity sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A== + dependencies: + type-fest "^0.20.2" + +grapheme-splitter@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +ignore@^5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.1.tgz#c2b1f76cb999ede1502f3a226a9310fdfe88d46c" + integrity sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.0, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +js-sdsl@^4.1.4: + version "4.2.0" + resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.2.0.tgz#278e98b7bea589b8baaf048c20aeb19eb7ad09d0" + integrity sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^2.4.1: + version "2.8.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.0.tgz#c7df58393c9ba77d6fba3921ae01faf994fb9dc9" + integrity sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/tests/.gitignore b/tests/.gitignore deleted file mode 100644 index 5bd24dc36..000000000 --- a/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -e2e/node_modules \ No newline at end of file diff --git a/tests/e2e/.gitignore b/tests/e2e/.gitignore deleted file mode 100644 index 84f82b8b3..000000000 --- a/tests/e2e/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -*.log -node_modules/ -.idea -*.DS_Store diff --git a/tests/e2e/app.test.js b/tests/e2e/app.test.js deleted file mode 100644 index 0f1a7eba8..000000000 --- a/tests/e2e/app.test.js +++ /dev/null @@ -1,360 +0,0 @@ -const puppeteer = require('puppeteer') -const appUrlBase = process.env.BASE_URL -const searchEmail = process.env.SEARCH_EMAIL -const person = { - email: process.env.EMAIL, - password: process.env.PASSWORD, - token: process.env.CAPTCHA_TOKEN -}; - -let browser -let page -let client -let project - -beforeAll(async () => { - browser = await puppeteer.launch( - { - headless: false, - slowMo: 10, - defaultViewport: null, - } - ) - page = await browser.newPage() - await page.setUserAgent( 'UA-TEST' ); - await page.setJavaScriptEnabled(true); - await page.setDefaultNavigationTimeout(10000); - await page.setRequestInterception(true); - - await page.evaluate(async () => { - const response = await fetch(appUrlBase + '/API_TESTING/update'); - const text = await response.text(); - return text; - }); - - page.on('request', (req) => { - if (['image', 'font'].indexOf(req.resourceType()) !== -1) { - req.abort(); - } else if (req.url().match(/login/) && req.method() === 'POST') { - var data = { - 'method': 'POST', - 'postData': JSON.stringify(person), - } - req.continue(data); - } else { - req.continue(); - } - }); -}) - -describe('Login', () => { - test('User login', async () => { - await page.goto(appUrlBase + '/login'); - await page.waitForSelector('form'); - - await page.click("input[name=email]"); - await page.type("input[name=email]", person.email); - - await page.click("input[name=password]"); - await page.type("input[name=password]", person.password); - - await page.click("button[type=submit]"); - - const response = await page.waitForResponse(response => response.url().match(/login/) && response.request().method() === 'POST'); - try { - const finalResponse = await response.json(); - client = finalResponse.data.client; - project = client.projects[0]; - } catch (e) { - console.log(e) - } - - expect(response.status()).toBe(200) - }, 9000000) -}); - -describe('Sessions', () => { - test('Search for sessions using email', async () => { - await page.goto(`${appUrlBase}/${project.projectId}/sessions`); - await page.waitForSelector('#search'); - await page.click("#search"); - await page.type("#search", searchEmail); - - await page.waitForSelector('#filter-item'); - await page.click("#filter-item"); - - const response = await page.waitForResponse(response => response.url().match(/search2/) && response.request().method() === 'POST'); - expect(response.status()).toBe(200) - await page.waitForTimeout(2000) - }, 9000000) - - test('Replay a session', async () => { - await page.waitForSelector('#session-item'); - await page.waitForSelector('#play-button'); - await page.click("#play-button"); - - const response = await page.waitForResponse(response => response.url().match(/sessions2/) && response.request().method() === 'GET'); - try { - const finalResponse = await response.json(); - } catch (e) { - console.log(e) - } - - expect(response.status()).toBe(200) - }, 9000000) - - test('Open network tab', async () => { - await page.waitForTimeout(3000) - await page.waitForSelector('#control-button-network:not([disabled])') - await page.click("#control-button-network"); - - await page.waitForSelector('#table-row') - }, 9000000) - - test('Open fetch tab', async () => { - await page.waitForSelector('#control-button-fetch:not([disabled])') - await page.click("#control-button-fetch"); - - await page.waitForSelector('#table-row') - }, 9000000) - - test('Open redux tab', async () => { - await page.waitForSelector('#control-button-redux:not([disabled])') - await page.click("#control-button-redux"); - - await page.waitForSelector('.object-key-val') - }, 9000000) - - test('Open console tab', async () => { - await page.waitForSelector('#control-button-console:not([disabled])') - await page.click("#control-button-console"); - - await page.waitForSelector('div[class^="console_line_"]') - }, 9000000) - - test('Open events tab', async () => { - await page.waitForSelector('#control-button-events:not([disabled])') - await page.click("#control-button-events"); - - await page.waitForSelector('#table-row') - }, 9000000) - - test('Open performance tab', async () => { - await page.waitForSelector('#control-button-performance:not([disabled])') - await page.click("#control-button-performance"); - - await page.waitForSelector('.recharts-surface') - }, 9000000) - - test('Open long tasks tab', async () => { - await page.waitForSelector('#control-button-long:not([disabled])') - await page.click("#control-button-long"); - - await page.waitForSelector('#table-row') - }, 9000000) - - test('Check sessions by metadata', async () => { - await page.waitForSelector('#metadata-button') - await page.click("#metadata-button"); - - await page.waitForSelector('#metadata-item') - await page.click("#metadata-item"); - - const response = await page.waitForResponse(response => response.url().match(/session_search/) && response.request().method() === 'GET'); - expect(response.status()).toBe(200) - - await page.waitForTimeout(5000) - }, 9000000) -}) - - -describe('Errors', () => { - test('Navigate to errors and open an exception to see stacktrace', async () => { - const url = `${appUrlBase}/${project.projectId}/errors`; - await page.goto(url); - await page.waitForSelector('#error-item'); - await page.click("#error-item"); - - const response = await page.waitForResponse(response => response.url().match(/errors/) && response.request().method() === 'GET'); - try { - const finalResponse = await response.json(); - } catch (e) { - console.log(e) - } - - expect(response.status()).toBe(200) - }, 9000000) -}) - -describe('Preferences', () => { - test('Add a user', async () => { - await page.goto(`${appUrlBase}/client/manage-users`); - await page.waitForSelector('#add-button'); - await page.click("#add-button"); - - await page.waitForSelector('form'); - await page.waitForSelector("#name-field"); - await page.click("#name-field"); - await page.type("#name-field", 'Puppeteer User'); - - await page.click("input[name=email]"); - await page.type("input[name=email]", 'puppeteer@openreplay.com'); - - const [button] = await page.$x("//button[contains(., 'Invite')]"); - await button.click(); - - const response = await page.waitForResponse(response => response.url().match(/members/) && response.request().method() === 'PUT'); - expect(response.status()).toBe(200) - }, 9000000) - - test('Delete user', async () => { - await page.goto(`${appUrlBase}/client/manage-users`); - await page.waitForSelector('#user-row') - - const rows = await page.$$('#user-row') - await page.click(`#user-row:nth-child(${rows.length - 1}) #trash`) - await page.waitForSelector('#confirm-button') - await page.click('#confirm-button') - - - const response = await page.waitForResponse(response => response.url().match(/members/) && response.request().method() === 'DELETE'); - expect(response.status()).toBe(200) - }, 9000000) -}) - -describe('Alerts', () => { - test('Create Alert', async () => { - await page.goto(`${appUrlBase}/${project.projectId}/metrics`); - await page.waitForSelector('#menu-manage-alerts') - await page.click('#menu-manage-alerts') - - await page.waitForSelector('#add-button') - - await page.waitForTimeout(1000) - await page.click('#add-button') - - await page.waitForSelector('form'); - await page.waitForSelector("#name-field"); - await page.click("#name-field", { clickCount: 3 }); - await page.type("#name-field", 'Test Alert'); - - await page.waitForSelector("#name-field"); - await page.click("#name-field"); - - await page.waitForSelector("div[name=left]"); - await page.click("div[name=left]"); - await page.waitForSelector('div[name=left] .selected.item') - await page.click('div[name=left] .selected.item') - - await page.waitForSelector(`div[name=operator]`) - await page.click(`div[name=operator]`) - await page.waitForSelector('div[name=operator] .selected.item') - await page.click('div[name=operator] .selected.item') - - await page.click("input[name=right]"); - await page.type("input[name=right]", '3'); - await page.$eval('input[name=right]', e => e.blur()); - - - // const [button] = await page.$x("//button[contains(., 'Create')]"); - // await button.click(); - await page.evaluate(() => { - document.querySelector('#submit-button').click(); - }) - - const response = await page.waitForResponse(response => response.url().match(/alerts/) && response.request().method() === 'PUT'); - expect(response.status()).toBe(200) - }, 9000000) - - - test('Updated Alert', async () => { - await page.goto(`${appUrlBase}/${project.projectId}/metrics`); - await page.waitForSelector('#menu-manage-alerts') - await page.click('#menu-manage-alerts') - - await page.waitForSelector(`#alert-item:nth-child(1)`) - await page.click(`#alert-item:nth-child(1)`) - - await page.waitForSelector('form'); - await page.waitForSelector("#name-field"); - await page.click("#name-field", { clickCount: 3 }); - await page.type("#name-field", 'Test Alert - Update'); - - await page.evaluate(() => { - document.querySelector('#submit-button').click(); - }) - const response = await page.waitForResponse(response => response.url().match(/alerts/) && response.request().method() === 'PUT'); - expect(response.status()).toBe(200) - }, 9000000) - - test('Delete Alert', async () => { - await page.goto(`${appUrlBase}/${project.projectId}/metrics`); - await page.waitForSelector('#menu-manage-alerts') - await page.click('#menu-manage-alerts') - - await page.waitForSelector(`#alert-item:nth-child(1)`) - await page.click(`#alert-item:nth-child(1)`) - - await page.waitForSelector('form'); - await page.waitForSelector('form button#trash-button') - await page.click('form button#trash-button') - - await page.waitForSelector('#confirm-button') - await page.click('#confirm-button') - - const response = await page.waitForResponse(response => response.url().match(/alerts/) && response.request().method() === 'DELETE'); - expect(response.status()).toBe(200) - }, 9000000) -}) - -describe('Metrics', () => { - test('Load Metircs', async () => { - await page.goto(`${appUrlBase}/${project.projectId}/metrics`); - - const response = await page.waitForResponse(response => response.url().match(/dashboard\/overview/) && response.request().method() === 'POST'); - expect(response.status()).toBe(200) - }, 9000000) - - test('Add country filter', async () => { - await page.waitForSelector('#filter-options') - await page.click('#filter-options') - await page.waitForSelector('#filter-dropdown') - await page.waitForSelector('#filter-dropdown > div:nth-child(6)') - await page.click('#filter-dropdown > div:nth-child(6)') - - await page.waitForSelector('div[class^="widgetAutoComplete_searchWrapper_"]') - await page.type('div[class^="widgetAutoComplete_searchWrapper_"]', 'France') - - await page.waitForTimeout(300) - await page.waitForSelector('div[class^="widgetAutoComplete_optionItem_"]:nth-child(1)') - await page.click('div[class^="widgetAutoComplete_optionItem_"]:nth-child(1)') - - const response = await page.waitForResponse(response => response.url().match(/dashboard\/overview/) && response.request().method() === 'POST'); - expect(response.status()).toBe(200) - }, 9000000) - - test('Compare with other country', async () => { - await page.waitForSelector('#compare-button') - await page.click('#compare-button') - - const rows = await page.$$('#filter-options') - rows[1].click() - - await page.waitForTimeout(1000) - await page.waitForSelector('#filter-dropdown') - await page.waitForSelector('#filter-dropdown > div:nth-child(6)') - await page.click('#filter-dropdown > div:nth-child(6)') - await page.waitForSelector('div[class^="widgetAutoComplete_searchWrapper_"]') - await page.type('div[class^="widgetAutoComplete_searchWrapper_"]', 'India') - await page.waitForTimeout(300) - await page.waitForSelector('div[class^="widgetAutoComplete_optionItem_"]:nth-child(1)') - await page.click('div[class^="widgetAutoComplete_optionItem_"]:nth-child(1)') - - const responseTwo = await page.waitForResponse(response => response.url().match(/dashboard\/overview/) && response.request().method() === 'POST'); - expect(responseTwo.status()).toBe(200) - }, 9000000) -}) - -afterAll(() => { - browser.close() -}) diff --git a/tests/e2e/package-lock.json b/tests/e2e/package-lock.json deleted file mode 100644 index f9c929297..000000000 --- a/tests/e2e/package-lock.json +++ /dev/null @@ -1,11135 +0,0 @@ -{ - "name": "e2e", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "e2e", - "version": "1.0.0", - "license": "ISC", - "devDependencies": { - "faker": "^5.5.3", - "jest": "^26.6.3", - "jest-cli": "^26.6.3", - "puppeteer": "^8.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.16.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz", - "integrity": "sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.0.tgz", - "integrity": "sha512-mYZEvshBRHGsIAiyH5PzCFTCfbWfoYbO/jcSdXQSUQu1/pW0xDZAUP7KEc32heqWTAfAHhV9j1vH8Sav7l+JNQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.0", - "@babel/helper-compilation-targets": "^7.16.0", - "@babel/helper-module-transforms": "^7.16.0", - "@babel/helpers": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz", - "integrity": "sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz", - "integrity": "sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.16.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.17.5", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", - "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", - "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", - "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.0.tgz", - "integrity": "sha512-bsjlBFPuWT6IWhl28EdrQ+gTvSvj5tqVP5Xeftp07SEuz5pLnsXZuDkDD3Rfcxy0IsHmbZ+7B2/9SHzxO0T+sQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz", - "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.0.tgz", - "integrity": "sha512-My4cr9ATcaBbmaEa8M0dZNA74cfI6gitvUAskgDtAFmAqyFKDSHQo5YstxPbN+lzHl2D9l/YOEFqb2mtUh4gfA==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.16.0", - "@babel/helper-replace-supers": "^7.16.0", - "@babel/helper-simple-access": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz", - "integrity": "sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.0.tgz", - "integrity": "sha512-TQxuQfSCdoha7cpRNJvfaYxxxzmbxXw/+6cS7V02eeDYyhxderSoMVALvwupA54/pZcOTtVeJ0xccp1nGWladA==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.16.0", - "@babel/helper-optimise-call-expression": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz", - "integrity": "sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", - "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.3.tgz", - "integrity": "sha512-Xn8IhDlBPhvYTvgewPKawhADichOsbkZuzN7qz2BusOM0brChsyXMDJvldWaYMMUNiCQdQzNEioXTp3sC8Nt8w==", - "dev": true, - "dependencies": { - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.3", - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/@babel/highlight/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": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/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/@babel/parser": { - "version": "7.16.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz", - "integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.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-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "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-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-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-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/template": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", - "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz", - "integrity": "sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.0", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-hoist-variables": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/parser": "^7.16.3", - "@babel/types": "^7.16.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", - "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.15.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "dev": true, - "dependencies": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - }, - "bin": { - "watch": "cli.js" - }, - "engines": { - "node": ">=0.1.95" - } - }, - "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/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/@jest/console": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", - "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^26.6.2", - "jest-util": "^26.6.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/core": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", - "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/reporters": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.6.2", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-resolve-dependencies": "^26.6.3", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "jest-watcher": "^26.6.2", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/environment": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", - "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/fake-timers": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", - "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@sinonjs/fake-timers": "^6.0.1", - "@types/node": "*", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/globals": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", - "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", - "dev": true, - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/types": "^26.6.2", - "expect": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/reporters": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", - "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "optionalDependencies": { - "node-notifier": "^8.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", - "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/test-result": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", - "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", - "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", - "dev": true, - "dependencies": { - "@jest/test-result": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/transform": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", - "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^26.6.2", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-util": "^26.6.2", - "micromatch": "^4.0.2", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@types/babel__core": { - "version": "7.1.16", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", - "integrity": "sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", - "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", - "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.14.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", - "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/node": { - "version": "16.11.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.11.tgz", - "integrity": "sha512-KB0sixD67CeecHC33MYn+eYARkqTheIRNuu97y2XMjR7Wu3XibO1vaY6VBV6O/a89SPI81cEUIYT87UqUWlZNw==", - "dev": true - }, - "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/prettier": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.2.tgz", - "integrity": "sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA==", - "dev": true - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", - "dev": true - }, - "node_modules/@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", - "dev": true, - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", - "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/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/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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": "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/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "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/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/babel-jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", - "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", - "dev": true, - "dependencies": { - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/babel__core": "^7.1.7", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.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-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", - "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", - "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/babel-plugin-jest-hoist": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", - "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@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-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", - "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^26.6.2", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.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==", - "dev": true - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.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==", - "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/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.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-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "node_modules/browserslist": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.18.1.tgz", - "integrity": "sha512-8ScCzdpPwR2wQh8IT82CA2VgDwjHyqMovPBZSNH54+tm4Jk2pCuv90gmAdH6J84OCRWi0b4gMe6O6XPXuJnjgQ==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001280", - "electron-to-chromium": "^1.3.896", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "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": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "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": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "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/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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.30001285", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001285.tgz", - "integrity": "sha512-KAOkuUtcQ901MtmvxfKD+ODHH9YVDYnBt+TGYSz2KIfnq22CiArbUxXPN9067gNbgMlnNYRSwho8OPXZPALB9Q==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", - "dev": true, - "dependencies": { - "rsvp": "^4.8.4" - }, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "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/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "node_modules/cjs-module-lexer": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", - "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", - "dev": true - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.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/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", - "dev": true - }, - "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "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.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/devtools-protocol": { - "version": "0.0.854822", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.854822.tgz", - "integrity": "sha512-xd4D8kHQtB0KtWW0c9xBZD5LVtm9chkMOfs/3Yn01RhT/sFIsVtzTtypfKoFfWBaL+7xCYLxjOLkhwPXaX/Kcg==", - "dev": true - }, - "node_modules/diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.11.tgz", - "integrity": "sha512-2OhsaYgsWGhWjx2et8kaUcdktPbBGjKM2X0BReUCKcSCPttEY+hz2zie820JLbttU8jwL92+JJysWwkut3wZgA==", - "dev": true - }, - "node_modules/emittery": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", - "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "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/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==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "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/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-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "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/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/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/exec-sh": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", - "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==", - "dev": true - }, - "node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/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/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/expect": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", - "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/faker": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz", - "integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==", - "dev": true - }, - "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": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "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": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", - "dev": true, - "dependencies": { - "pend": "~1.2.0" - } - }, - "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/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/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "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==", - "dev": true - }, - "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-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-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "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/graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "dev": true - }, - "node_modules/growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true, - "optional": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "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/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "engines": { - "node": ">=8.12.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==", - "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/import-local": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", - "integrity": "sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "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==", - "dev": true - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-core-module": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", - "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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, - "optional": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "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-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "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-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/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "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-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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, - "optional": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.1.tgz", - "integrity": "sha512-q1kvhAXWSsXfMjCdNHNPKZZv94OlspKnoGv+R9RGbnqOOQ0VbNfLFgQDVgi7hHenKsndGq3/o0OBdzDXthWcNw==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", - "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", - "dev": true, - "dependencies": { - "@jest/core": "^26.6.3", - "import-local": "^3.0.2", - "jest-cli": "^26.6.3" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-changed-files": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", - "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "execa": "^4.0.0", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-cli": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", - "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", - "dev": true, - "dependencies": { - "@jest/core": "^26.6.3", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.3", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "prompts": "^2.0.1", - "yargs": "^15.4.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-each": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", - "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", - "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", - "dev": true, - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2", - "jsdom": "^16.4.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-environment-node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", - "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", - "dev": true, - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-haste-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", - "walker": "^1.0.7" - }, - "engines": { - "node": ">= 10.14.2" - }, - "optionalDependencies": { - "fsevents": "^2.1.2" - } - }, - "node_modules/jest-jasmine2": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", - "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^26.6.2", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", - "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", - "dev": true, - "dependencies": { - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-mock": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", - "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", - "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runner": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", - "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.7.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.6.2", - "jest-leak-detector": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "source-map-support": "^0.5.6", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runtime": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", - "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/globals": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^0.6.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^15.4.1" - }, - "bin": { - "jest-runtime": "bin/jest-runtime.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-serializer": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", - "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", - "dev": true, - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-snapshot": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", - "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.0.0", - "chalk": "^4.0.0", - "expect": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-haste-map": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "natural-compare": "^1.4.0", - "pretty-format": "^26.6.2", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", - "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", - "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", - "dev": true, - "dependencies": { - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^26.6.2", - "string-length": "^4.0.1" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "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/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "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-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/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "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/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.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "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/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/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "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/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-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "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/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dev": true, - "dependencies": { - "mime-db": "1.51.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/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": true - }, - "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==", - "dev": true - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "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-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node_modules/node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/node-notifier": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz", - "integrity": "sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg==", - "dev": true, - "optional": true, - "dependencies": { - "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.2", - "shellwords": "^0.1.1", - "uuid": "^8.3.0", - "which": "^2.0.2" - } - }, - "node_modules/node-notifier/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "optional": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", - "dev": true - }, - "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.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "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-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/nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": 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/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "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/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/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/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/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "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/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "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.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "dependencies": { - "node-modules-regexp": "^1.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "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/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "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/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==", - "dev": true - }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "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==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/puppeteer": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-8.0.0.tgz", - "integrity": "sha512-D0RzSWlepeWkxPPdK3xhTcefj8rjah1791GE82Pdjsri49sy11ci/JQsAO8K2NRukqvwEtcI+ImP5F4ZiMvtIQ==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.854822", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" - }, - "engines": { - "node": ">=10.18.1" - } - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "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": { - "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/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/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/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true, - "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": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "dev": true - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "engines": { - "node": ">=0.12" - } - }, - "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/rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "dev": true, - "engines": { - "node": "6.* || >= 7.*" - } - }, - "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==", - "dev": true - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "deprecated": "some dependency vulnerabilities fixed, support for node < 10 dropped, and newer ECMAScript syntax/features added", - "dev": true, - "dependencies": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - }, - "bin": { - "sane": "src/cli.js" - }, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/sane/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/sane/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/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/sane/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/sane/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/sane/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/sane/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/sane/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/sane/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/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/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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/shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true, - "optional": true - }, - "node_modules/signal-exit": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", - "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", - "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/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/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/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/snapdragon/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.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/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "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.11", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", - "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", - "dev": true - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": 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==", - "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/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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/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": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "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/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/supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "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==", - "dev": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "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==", - "dev": 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/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "dev": true - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "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": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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/tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", - "dev": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/unbzip2-stream": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", - "dev": true, - "dependencies": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/union-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "dev": true - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-to-istanbul": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", - "integrity": "sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "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/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true, - "engines": { - "node": ">=10.4" - } - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "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-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "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/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - } - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.0" - } - }, - "@babel/compat-data": { - "version": "7.16.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz", - "integrity": "sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==", - "dev": true - }, - "@babel/core": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.0.tgz", - "integrity": "sha512-mYZEvshBRHGsIAiyH5PzCFTCfbWfoYbO/jcSdXQSUQu1/pW0xDZAUP7KEc32heqWTAfAHhV9j1vH8Sav7l+JNQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.0", - "@babel/helper-compilation-targets": "^7.16.0", - "@babel/helper-module-transforms": "^7.16.0", - "@babel/helpers": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz", - "integrity": "sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/helper-compilation-targets": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz", - "integrity": "sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.16.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.17.5", - "semver": "^6.3.0" - } - }, - "@babel/helper-function-name": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", - "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", - "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", - "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.0.tgz", - "integrity": "sha512-bsjlBFPuWT6IWhl28EdrQ+gTvSvj5tqVP5Xeftp07SEuz5pLnsXZuDkDD3Rfcxy0IsHmbZ+7B2/9SHzxO0T+sQ==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz", - "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-module-transforms": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.0.tgz", - "integrity": "sha512-My4cr9ATcaBbmaEa8M0dZNA74cfI6gitvUAskgDtAFmAqyFKDSHQo5YstxPbN+lzHl2D9l/YOEFqb2mtUh4gfA==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.16.0", - "@babel/helper-replace-supers": "^7.16.0", - "@babel/helper-simple-access": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz", - "integrity": "sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - }, - "@babel/helper-replace-supers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.0.tgz", - "integrity": "sha512-TQxuQfSCdoha7cpRNJvfaYxxxzmbxXw/+6cS7V02eeDYyhxderSoMVALvwupA54/pZcOTtVeJ0xccp1nGWladA==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.16.0", - "@babel/helper-optimise-call-expression": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-simple-access": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz", - "integrity": "sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", - "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", - "dev": true - }, - "@babel/helpers": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.3.tgz", - "integrity": "sha512-Xn8IhDlBPhvYTvgewPKawhADichOsbkZuzN7qz2BusOM0brChsyXMDJvldWaYMMUNiCQdQzNEioXTp3sC8Nt8w==", - "dev": true, - "requires": { - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.3", - "@babel/types": "^7.16.0" - } - }, - "@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "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, - "requires": { - "color-convert": "^1.9.0" - } - }, - "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, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "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, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "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, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/parser": { - "version": "7.16.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz", - "integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==", - "dev": true - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/template": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", - "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0" - } - }, - "@babel/traverse": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz", - "integrity": "sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.0", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-hoist-variables": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/parser": "^7.16.3", - "@babel/types": "^7.16.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", - "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.15.7", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "dev": true, - "requires": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - } - }, - "@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, - "requires": { - "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" - } - }, - "@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 - }, - "@jest/console": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", - "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^26.6.2", - "jest-util": "^26.6.2", - "slash": "^3.0.0" - } - }, - "@jest/core": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", - "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/reporters": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.6.2", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-resolve-dependencies": "^26.6.3", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "jest-watcher": "^26.6.2", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "@jest/environment": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", - "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2" - } - }, - "@jest/fake-timers": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", - "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@sinonjs/fake-timers": "^6.0.1", - "@types/node": "*", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - } - }, - "@jest/globals": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", - "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", - "dev": true, - "requires": { - "@jest/environment": "^26.6.2", - "@jest/types": "^26.6.2", - "expect": "^26.6.2" - } - }, - "@jest/reporters": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", - "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "node-notifier": "^8.0.0", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" - } - }, - "@jest/source-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", - "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", - "dev": true, - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - } - }, - "@jest/test-result": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", - "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", - "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", - "dev": true, - "requires": { - "@jest/test-result": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3" - } - }, - "@jest/transform": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", - "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^26.6.2", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-util": "^26.6.2", - "micromatch": "^4.0.2", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - } - }, - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true - }, - "@types/babel__core": { - "version": "7.1.16", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", - "integrity": "sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", - "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@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, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", - "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/node": { - "version": "16.11.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.11.tgz", - "integrity": "sha512-KB0sixD67CeecHC33MYn+eYARkqTheIRNuu97y2XMjR7Wu3XibO1vaY6VBV6O/a89SPI81cEUIYT87UqUWlZNw==", - "dev": true - }, - "@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 - }, - "@types/prettier": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.2.tgz", - "integrity": "sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", - "dev": true - }, - "@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", - "dev": true, - "optional": true, - "requires": { - "@types/node": "*" - } - }, - "abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "acorn": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", - "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", - "dev": true - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - } - } - }, - "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 - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "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 - }, - "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, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "babel-jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", - "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", - "dev": true, - "requires": { - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/babel__core": "^7.1.7", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - } - }, - "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, - "requires": { - "@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" - }, - "dependencies": { - "istanbul-lib-instrument": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", - "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", - "dev": true, - "requires": { - "@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" - } - } - } - }, - "babel-plugin-jest-hoist": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", - "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@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-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", - "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^26.6.2", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "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, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browserslist": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.18.1.tgz", - "integrity": "sha512-8ScCzdpPwR2wQh8IT82CA2VgDwjHyqMovPBZSNH54+tm4Jk2pCuv90gmAdH6J84OCRWi0b4gMe6O6XPXuJnjgQ==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001280", - "electron-to-chromium": "^1.3.896", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" - } - }, - "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, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true - }, - "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 - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001285", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001285.tgz", - "integrity": "sha512-KAOkuUtcQ901MtmvxfKD+ODHH9YVDYnBt+TGYSz2KIfnq22CiArbUxXPN9067gNbgMlnNYRSwho8OPXZPALB9Q==", - "dev": true - }, - "capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", - "dev": true, - "requires": { - "rsvp": "^4.8.4" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "cjs-module-lexer": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", - "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "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, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - } - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "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 - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "devtools-protocol": { - "version": "0.0.854822", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.854822.tgz", - "integrity": "sha512-xd4D8kHQtB0KtWW0c9xBZD5LVtm9chkMOfs/3Yn01RhT/sFIsVtzTtypfKoFfWBaL+7xCYLxjOLkhwPXaX/Kcg==", - "dev": true - }, - "diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true - }, - "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } - } - }, - "electron-to-chromium": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.11.tgz", - "integrity": "sha512-2OhsaYgsWGhWjx2et8kaUcdktPbBGjKM2X0BReUCKcSCPttEY+hz2zie820JLbttU8jwL92+JJysWwkut3wZgA==", - "dev": true - }, - "emittery": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", - "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", - "dev": true - }, - "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 - }, - "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==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "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, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, - "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 - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "exec-sh": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", - "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==", - "dev": true - }, - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "expect": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", - "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0" - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - } - } - }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - } - }, - "faker": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz", - "integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==", - "dev": true - }, - "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 - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, - "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, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "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, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "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, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "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==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "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 - }, - "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 - }, - "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 - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "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 - }, - "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "dev": true - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true, - "optional": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "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 - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "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 - }, - "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.5" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true - }, - "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, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "import-local": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", - "integrity": "sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", - "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "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, - "optional": true - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "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 - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "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 - }, - "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, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "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 - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "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, - "optional": true, - "requires": { - "is-docker": "^2.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.1.tgz", - "integrity": "sha512-q1kvhAXWSsXfMjCdNHNPKZZv94OlspKnoGv+R9RGbnqOOQ0VbNfLFgQDVgi7hHenKsndGq3/o0OBdzDXthWcNw==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", - "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", - "dev": true, - "requires": { - "@jest/core": "^26.6.3", - "import-local": "^3.0.2", - "jest-cli": "^26.6.3" - } - }, - "jest-changed-files": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", - "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "execa": "^4.0.0", - "throat": "^5.0.0" - } - }, - "jest-cli": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", - "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", - "dev": true, - "requires": { - "@jest/core": "^26.6.3", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.3", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "prompts": "^2.0.1", - "yargs": "^15.4.1" - } - }, - "jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - } - }, - "jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", - "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2" - } - }, - "jest-environment-jsdom": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", - "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", - "dev": true, - "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2", - "jsdom": "^16.4.0" - } - }, - "jest-environment-node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", - "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", - "dev": true, - "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - } - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true - }, - "jest-haste-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.1.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", - "walker": "^1.0.7" - } - }, - "jest-jasmine2": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", - "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", - "dev": true, - "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^26.6.2", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "throat": "^5.0.0" - } - }, - "jest-leak-detector": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", - "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", - "dev": true, - "requires": { - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.2" - } - }, - "jest-mock": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", - "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*" - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", - "dev": true - }, - "jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", - "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.6.2" - } - }, - "jest-runner": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", - "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.7.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.6.2", - "jest-leak-detector": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "source-map-support": "^0.5.6", - "throat": "^5.0.0" - } - }, - "jest-runtime": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", - "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/globals": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^0.6.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^15.4.1" - } - }, - "jest-serializer": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", - "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", - "dev": true, - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - } - }, - "jest-snapshot": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", - "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.0.0", - "chalk": "^4.0.0", - "expect": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-haste-map": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "natural-compare": "^1.4.0", - "pretty-format": "^26.6.2", - "semver": "^7.3.2" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } - }, - "jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "camelcase": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", - "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", - "dev": true - } - } - }, - "jest-watcher": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", - "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", - "dev": true, - "requires": { - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^26.6.2", - "string-length": "^4.0.1" - } - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - } - }, - "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 - }, - "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, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "requires": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "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 - }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "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 - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "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 - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "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 - }, - "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, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "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, - "requires": { - "yallist": "^4.0.0" - } - }, - "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, - "requires": { - "semver": "^6.0.0" - } - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "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 - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true - }, - "mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dev": true, - "requires": { - "mime-db": "1.51.0" - } - }, - "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 - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - } - }, - "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==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "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-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - }, - "dependencies": { - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true - }, - "node-notifier": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz", - "integrity": "sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg==", - "dev": true, - "optional": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.2", - "shellwords": "^0.1.1", - "uuid": "^8.3.0", - "which": "^2.0.2" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "optional": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", - "dev": true - }, - "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, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "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 - }, - "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, - "requires": { - "path-key": "^3.0.0" - } - }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "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, - "requires": { - "p-try": "^2.0.0" - } - }, - "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, - "requires": { - "p-limit": "^2.2.0" - } - }, - "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 - }, - "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, - "requires": { - "@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" - } - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "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 - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "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 - }, - "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 - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true - }, - "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "requires": { - "node-modules-regexp": "^1.0.0" - } - }, - "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, - "requires": { - "find-up": "^4.0.0" - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "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, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "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==", - "dev": true - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "puppeteer": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-8.0.0.tgz", - "integrity": "sha512-D0RzSWlepeWkxPPdK3xhTcefj8rjah1791GE82Pdjsri49sy11ci/JQsAO8K2NRukqvwEtcI+ImP5F4ZiMvtIQ==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.854822", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "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, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "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 - } - } - }, - "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, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "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 - } - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "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 - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "dev": true - }, - "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==", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "dev": true, - "requires": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "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, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - } - } - }, - "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, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "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 - }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", - "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", - "dev": true - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "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 - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "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, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "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 - }, - "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, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", - "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "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==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "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 - } - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "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, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "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, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "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 - }, - "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, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - } - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "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, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "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, - "requires": { - "is-number": "^7.0.0" - } - }, - "tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - } - }, - "tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "unbzip2-stream": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", - "dev": true, - "requires": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - } - } - }, - "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 - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - } - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "optional": true - }, - "v8-to-istanbul": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", - "integrity": "sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "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, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "requires": { - "makeerror": "1.0.12" - } - }, - "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "requires": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", - "dev": true, - "requires": {} - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", - "dev": true, - "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - } - } -} diff --git a/tests/e2e/package.json b/tests/e2e/package.json deleted file mode 100644 index 623408cb9..000000000 --- a/tests/e2e/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "e2e", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "jest" - }, - "author": "", - "license": "ISC", - "devDependencies": { - "faker": "^5.5.3", - "jest": "^26.6.3", - "jest-cli": "^26.6.3", - "puppeteer": "^8.0.0" - } -} diff --git a/third-party.md b/third-party.md index 186ad8817..e0b68d9f6 100644 --- a/third-party.md +++ b/third-party.md @@ -1,4 +1,4 @@ -## Licenses (as of November 04, 2022) +## Licenses (as of January 23, 2023) Below is the list of dependencies used in OpenReplay software. Licenses may change between versions, so please keep this up to date with every new library you use. @@ -49,6 +49,7 @@ Below is the list of dependencies used in OpenReplay software. Licenses may chan | scikit-learn | BSD3 | Python | | sqlalchemy | MIT | Python | | pandas-redshift | MIT | Python | +| confluent-kafka | Apache2 | Python | | amplitude-js | MIT | JavaScript | | classnames | MIT | JavaScript | | codemirror | MIT | JavaScript | @@ -105,12 +106,13 @@ Below is the list of dependencies used in OpenReplay software. Licenses may chan | kafka | Apache2 | Infrastructure | | stern | Apache2 | Infrastructure | | k9s | Apache2 | Infrastructure | -| minio | GPLv3 | Infrastructure | +| minio | [AGPLv3](https://github.com/minio/minio/blob/master/LICENSE) | Infrastructure | | postgreSQL | PostgreSQL License | Infrastructure | -| ansible | GPLv3 | Infrastructure | | k3s | Apache2 | Infrastructure | | nginx | BSD2 | Infrastructure | | clickhouse | Apache2 | Infrastructure | | redis | BSD3 | Infrastructure | | yq | MIT | Infrastructure | | html2canvas | MIT | JavaScript | +| eget | MIT | Infrastructure | + diff --git a/tracker/tracker-assist/CHANGELOG.md b/tracker/tracker-assist/CHANGELOG.md new file mode 100644 index 000000000..f61aad123 --- /dev/null +++ b/tracker/tracker-assist/CHANGELOG.md @@ -0,0 +1,31 @@ +## 5.0.0 + +- fix recording state import + +## 4.1.5 + +- fixed peerjs hack that caused ts compile issues +- - added screen recording feature (EE) license + +## 4.1.4 + +- added peerjs hack `Peer = Peer.default || Peer` to prevent webpack 5 import error + +## 4.1.3 + +- fixed issue with agents reconnecting on new page (setting array without agentinfo) + +## 4.1.2 + +- added agentInfo object to most assist actions callbacks +- added relative path (with port support) for assist connection +- ensure releaseControl is called when session is stopped +- fixed videofeed window sizes +- fixed "black squares" in videofeed when one of the users turn off camera +- fixed html chat snippet layout + +## 4.1.x-4.1.1 + +- mice name tags +- smaller font for cursor +- remote control status for end user diff --git a/tracker/tracker-assist/README.md b/tracker/tracker-assist/README.md index 4897477b3..662e1e084 100644 --- a/tracker/tracker-assist/README.md +++ b/tracker/tracker-assist/README.md @@ -2,6 +2,10 @@ OpenReplay Assist Plugin allows you to support your users by seeing their live screen and instantly hopping on call (WebRTC) with them without requiring any 3rd-party screen sharing software. +## Documentation + +For launch options and available public methods, [refer to the documentation](https://docs.openreplay.com/plugins/assist) + ## Installation ```bash @@ -72,7 +76,7 @@ trackerAssist({ type ConfirmOptions = { text?:string, style?: StyleObject, // style object (i.e {color: 'red', borderRadius: '10px'}) - confirmBtn?: ButtonOptions, + confirmBtn?: ButtonOptions, declineBtn?: ButtonOptions } @@ -82,7 +86,7 @@ type ButtonOptions = HTMLButtonElement | string | { } ``` -- `callConfirm`: Customize the text and/or layout of the call request popup. +- `callConfirm`: Customize the text and/or layout of the call request popup. - `controlConfirm`: Customize the text and/or layout of the remote control request popup. - `config`: Contains any custom ICE/TURN server configuration. Defaults to `{ 'iceServers': [{ 'urls': 'stun:stun.l.google.com:19302' }], 'sdpSemantics': 'unified-plan' }`. - `onAgentConnect: () => (()=>void | void)`: This callback function is fired when someone from OpenReplay UI connects to the current live session. It can return another function. In this case, returned callback will be called when the same agent connection gets closed. diff --git a/tracker/tracker-assist/package.json b/tracker/tracker-assist/package.json index 7c6b40138..aaa80429d 100644 --- a/tracker/tracker-assist/package.json +++ b/tracker/tracker-assist/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker-assist", "description": "Tracker plugin for screen assistance through the WebRTC", - "version": "4.1.3", + "version": "5.0.0", "keywords": [ "WebRTC", "assistance", @@ -27,7 +27,7 @@ }, "dependencies": { "csstype": "^3.0.10", - "peerjs": "1.4.6", + "peerjs": "1.4.7", "socket.io-client": "^4.4.1" }, "peerDependencies": { diff --git a/tracker/tracker-assist/src/Assist.ts b/tracker/tracker-assist/src/Assist.ts index a742c4fbd..646360521 100644 --- a/tracker/tracker-assist/src/Assist.ts +++ b/tracker/tracker-assist/src/Assist.ts @@ -12,8 +12,11 @@ import AnnotationCanvas from './AnnotationCanvas.js' import ConfirmWindow from './ConfirmWindow/ConfirmWindow.js' import { callConfirmDefault, } from './ConfirmWindow/defaults.js' import type { Options as ConfirmOptions, } from './ConfirmWindow/defaults.js' +import ScreenRecordingState from './ScreenRecordingState.js' // TODO: fully specified strict check with no-any (everywhere) +// @ts-ignore +const safeCastedPeer = Peer.default || Peer type StartEndCallback = (agentInfo?: Record<string, any>) => ((() => any) | void) @@ -21,10 +24,12 @@ export interface Options { onAgentConnect: StartEndCallback; onCallStart: StartEndCallback; onRemoteControlStart: StartEndCallback; + onRecordingRequest?: (agentInfo: Record<string, any>) => any; session_calling_peer_key: string; session_control_peer_key: string; callConfirm: ConfirmOptions; controlConfirm: ConfirmOptions; + recordingConfirm: ConfirmOptions; // @depricated confirmText?: string; @@ -80,6 +85,7 @@ export default class Assist { onRemoteControlStart: ()=>{}, callConfirm: {}, controlConfirm: {}, // TODO: clear options passing/merging/overriting + recordingConfirm: {}, }, options, ) @@ -144,6 +150,14 @@ export default class Assist { private onStart() { const app = this.app const sessionId = app.getSessionID() + // Common for all incoming call requests + let callUI: CallWindow | null = null + let annot: AnnotationCanvas | null = null + // TODO: incapsulate + let callConfirmWindow: ConfirmWindow | null = null + let callConfirmAnswer: Promise<boolean> | null = null + let callEndCallback: ReturnType<StartEndCallback> | null = null + if (!sessionId) { return app.debug.error('No session ID') } @@ -174,7 +188,7 @@ export default class Assist { if (this.remoteControl){ callUI?.showRemoteControl(this.remoteControl.releaseControl) } - this.agents[id].onControlReleased = this.options.onRemoteControlStart(this.agents[id].agentInfo) + this.agents[id].onControlReleased = this.options.onRemoteControlStart(this.agents[id]?.agentInfo) this.emit('control_granted', id) annot = new AnnotationCanvas() annot.mount() @@ -199,6 +213,14 @@ export default class Assist { }, ) + const onAcceptRecording = () => { + socket.emit('recording_accepted') + } + const onRejectRecording = () => { + socket.emit('recording_rejected') + } + const recordingState = new ScreenRecordingState(this.options.recordingConfirm) + // TODO: check incoming args socket.on('request_control', this.remoteControl.requestControl) socket.on('release_control', this.remoteControl.releaseControl) @@ -213,7 +235,7 @@ export default class Assist { }) socket.on('input', this.remoteControl.input) - let annot: AnnotationCanvas | null = null + socket.on('moveAnnotation', (_, p) => annot && annot.move(p)) // TODO: restrict by id socket.on('startAnnotation', (_, p) => annot && annot.start(p)) socket.on('stopAnnotation', () => annot && annot.stop()) @@ -248,11 +270,13 @@ export default class Assist { this.agents[id]?.onDisconnect?.() delete this.agents[id] + recordingState.stopAgentRecording(id) endAgentCall(id) }) socket.on('NO_AGENT', () => { Object.values(this.agents).forEach(a => a.onDisconnect?.()) this.agents = {} + if (recordingState.isActive) recordingState.stopRecording() }) socket.on('call_end', (id) => { if (!callingAgents.has(id)) { @@ -266,9 +290,22 @@ export default class Assist { callingAgents.set(id, name) updateCallerNames() }) - socket.on('videofeed', (id, feedState) => { + socket.on('videofeed', (_, feedState) => { callUI?.toggleVideoStream(feedState) }) + socket.on('request_recording', (id, agentData) => { + if (!recordingState.isActive) { + this.options.onRecordingRequest?.(JSON.parse(agentData)) + recordingState.requestRecording(id, onAcceptRecording, onRejectRecording) + } else { + this.emit('recording_busy') + } + }) + socket.on('stop_recording', (id) => { + if (recordingState.isActive) { + recordingState.stopAgentRecording(id) + } + }) const callingAgents: Map<string, string> = new Map() // !! uses socket.io ID // TODO: merge peerId & socket.io id (simplest way - send peerId with the name) @@ -295,20 +332,18 @@ export default class Assist { if (this.options.config) { peerOptions['config'] = this.options.config } - const peer = this.peer = new Peer(peerID, peerOptions) + + const peer = new safeCastedPeer(peerID, peerOptions) as Peer + this.peer = peer // @ts-ignore (peerjs typing) peer.on('error', e => app.debug.warn('Peer error: ', e.type, e)) peer.on('disconnected', () => peer.reconnect()) - // Common for all incoming call requests - let callUI: CallWindow | null = null function updateCallerNames() { callUI?.setAssistentName(callingAgents) } - // TODO: incapsulate - let callConfirmWindow: ConfirmWindow | null = null - let callConfirmAnswer: Promise<boolean> | null = null + const closeCallConfirmWindow = () => { if (callConfirmWindow) { callConfirmWindow.remove() @@ -329,7 +364,7 @@ export default class Assist { return answer }) } - let callEndCallback: ReturnType<StartEndCallback> | null = null + const handleCallEnd = () => { // Completle stop and clear all calls // Streams Object.values(calls).forEach(call => call.close()) @@ -362,7 +397,7 @@ export default class Assist { const updateVideoFeed = ({ enabled, }) => this.emit('videofeed', { streamId: this.peer?.id, enabled, }) peer.on('call', (call) => { - app.debug.log('Incoming call: ', call) + app.debug.log('Incoming call from', call.peer) let confirmAnswer: Promise<boolean> const callingPeerIds = JSON.parse(sessionStorage.getItem(this.options.session_calling_peer_key) || '[]') if (callingPeerIds.includes(call.peer) || this.callingState === CallingState.True) { diff --git a/tracker/tracker-assist/src/CallWindow.ts b/tracker/tracker-assist/src/CallWindow.ts index d561a2ba7..aff1517f3 100644 --- a/tracker/tracker-assist/src/CallWindow.ts +++ b/tracker/tracker-assist/src/CallWindow.ts @@ -17,8 +17,6 @@ export default class CallWindow { private remoteControlContainer: HTMLElement | null = null private remoteControlEndBtn: HTMLElement | null = null private controlsContainer: HTMLElement | null = null - private remoteVideoOn = false - private localVideoOn = false private onToggleVideo: (args: any) => void private tsInterval: ReturnType<typeof setInterval> private remoteVideo: MediaStreamTrack @@ -177,7 +175,6 @@ export default class CallWindow { this.load .then(() => { if (this.videoContainer) { - this.remoteVideoOn = enable if (enable) { this.videoContainer.classList.add('remote') } else { @@ -235,7 +232,6 @@ export default class CallWindow { if (!this.videoBtn || !this.videoContainer) { return } - this.localVideoOn = enabled if (enabled) { this.videoContainer.classList.add('local') this.videoBtn.classList.remove('off') diff --git a/tracker/tracker-assist/src/ConfirmWindow/defaults.ts b/tracker/tracker-assist/src/ConfirmWindow/defaults.ts index d6d5430c4..55f82d19f 100644 --- a/tracker/tracker-assist/src/ConfirmWindow/defaults.ts +++ b/tracker/tracker-assist/src/ConfirmWindow/defaults.ts @@ -5,6 +5,7 @@ import type { ButtonOptions, ConfirmWindowOptions, } from './ConfirmWindow.js' const TEXT_GRANT_REMORTE_ACCESS = 'Grant Remote Control' const TEXT_REJECT = 'Reject' const TEXT_ANSWER_CALL = `${acceptCall}   Answer` +const TEXT_ACCEPT_RECORDING = 'Allow Recording' export type Options = string | Partial<ConfirmWindowOptions>; @@ -40,3 +41,11 @@ export const controlConfirmDefault = (opts: Options) => TEXT_REJECT, 'Agent requested remote control. Allow?' ) + +export const recordRequestDefault = (opts: Options) => + confirmDefault( + opts, + TEXT_ACCEPT_RECORDING, + TEXT_REJECT, + 'Agent requested to record activity in this browser tab.' + ) diff --git a/tracker/tracker-assist/src/ScreenRecordingState.ts b/tracker/tracker-assist/src/ScreenRecordingState.ts new file mode 100644 index 000000000..a962e85ac --- /dev/null +++ b/tracker/tracker-assist/src/ScreenRecordingState.ts @@ -0,0 +1,114 @@ +import ConfirmWindow from './ConfirmWindow/ConfirmWindow.js' +import { recordRequestDefault, } from './ConfirmWindow/defaults.js' +import type { Options as ConfirmOptions, } from './ConfirmWindow/defaults.js' + +export enum RecordingState { + Off, + Requested, + Recording, +} + +const borderStyles = { + height: '100vh', + width: '100vw', + border: '2px dashed red', + left: 0, + top: 0, + position: 'fixed', + pointerEvents: 'none', +} + +const buttonStyles = { + cursor: 'pointer', + color: 'white', + position: 'fixed', + bottom: '0', + left: 'calc(50vw - 60px)', + 'font-weight': 500, + padding: '2px 4px', + background: '#394EFF', + 'border-top-right-radius': '3px', + 'border-top-left-radius': '3px', + 'text-align': 'center', +} + +export default class ScreenRecordingState { + private status = RecordingState.Off + private recordingAgent: string + private overlayAdded = false + private uiComponents: [HTMLDivElement] + + constructor(private readonly confirmOptions: ConfirmOptions) { } + + public get isActive() { + return this.status !== RecordingState.Off + } + + private confirm: ConfirmWindow | null = null + + public requestRecording = ( + id: string, + onAccept: () => void, + onDeny: () => void, + ) => { + if (this.isActive) return + this.status = RecordingState.Requested + + this.confirm = new ConfirmWindow(recordRequestDefault(this.confirmOptions)) + this.confirm + .mount() + .then((allowed) => { + if (allowed) { + this.acceptRecording() + onAccept() + + this.recordingAgent = id + } else { + this.rejectRecording() + onDeny() + } + }) + .then(() => { + this.confirm?.remove() + }) + .catch((e) => { + this.confirm?.remove() + console.error(e) + }) + } + + private readonly acceptRecording = () => { + if (!this.overlayAdded) { + const borderWindow = window.document.createElement('div') + Object.assign(borderWindow.style, borderStyles) + borderWindow.className = 'or-recording-border' + borderWindow.setAttribute('data-openreplay-obscured', '') + borderWindow.setAttribute('data-openreplay-hidden', '') + borderWindow.setAttribute('data-openreplay-ignore', '') + window.document.body.appendChild(borderWindow) + + this.overlayAdded = true + + this.uiComponents = [borderWindow,] + } + this.status = RecordingState.Recording + } + + public readonly stopAgentRecording = (id) => { + if (id === this.recordingAgent) { + this.rejectRecording() + } + } + + public readonly stopRecording = () => { + this.rejectRecording() + } + + private readonly rejectRecording = () => { + this.confirm?.remove() + this.status = RecordingState.Off + + this.overlayAdded = false + this.uiComponents.forEach((el) => el.parentElement?.removeChild(el)) + } +} diff --git a/tracker/tracker-axios/package.json b/tracker/tracker-axios/package.json index 6712fb2c2..318e2ab2d 100644 --- a/tracker/tracker-axios/package.json +++ b/tracker/tracker-axios/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker-axios", "description": "Tracker plugin for axios requests recording", - "version": "3.6.1", + "version": "3.6.2", "keywords": [ "axios", "logging", diff --git a/tracker/tracker-axios/src/index.ts b/tracker/tracker-axios/src/index.ts index 2b2049818..840fd0626 100644 --- a/tracker/tracker-axios/src/index.ts +++ b/tracker/tracker-axios/src/index.ts @@ -52,9 +52,14 @@ export default function(opts: Partial<Options> = {}) { opts, ); return (app: App | null) => { - if (app === null) { + if (app === null || + // @ts-ignore - a catch for the developers who apply a plugin several times + options.instance.__openreplayAxiosInstalled__ + ) { return; } + // @ts-ignore + options.instance.__openreplayAxiosInstalled__ = true const ihOpt = options.ignoreHeaders const isHIgnoring = Array.isArray(ihOpt) diff --git a/tracker/tracker-fetch/package.json b/tracker/tracker-fetch/package.json index 2c10d5ed0..1488ae819 100644 --- a/tracker/tracker-fetch/package.json +++ b/tracker/tracker-fetch/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker-fetch", "description": "Tracker plugin for fetch requests recording ", - "version": "3.6.1", + "version": "3.6.2", "keywords": [ "fetch", "logging", diff --git a/tracker/tracker-fetch/src/index.ts b/tracker/tracker-fetch/src/index.ts index 94aa573b2..10b75b7ea 100644 --- a/tracker/tracker-fetch/src/index.ts +++ b/tracker/tracker-fetch/src/index.ts @@ -37,7 +37,7 @@ export default function(opts: Partial<Options> = {}): (app: App | null) => Windo if (typeof window === 'undefined') { // not in browser (SSR) return () => opts.fetch || null - } + } const options: Options = Object.assign( { @@ -53,9 +53,14 @@ export default function(opts: Partial<Options> = {}): (app: App | null) => Windo } return (app: App | null) => { - if (app === null) { + if (app === null || + // @ts-ignore - a catch for the developers who apply a plugin several times + app.__fetchInstalled__ + ) { return options.fetch } + // @ts-ignore + app.__fetchInstalled__ = true const ihOpt = options.ignoreHeaders const isHIgnoring = Array.isArray(ihOpt) diff --git a/tracker/tracker-testing-playground/.gitignore b/tracker/tracker-testing-playground/.gitignore new file mode 100644 index 000000000..c82625dd6 --- /dev/null +++ b/tracker/tracker-testing-playground/.gitignore @@ -0,0 +1,4 @@ +.env +build +node_modules +!public \ No newline at end of file diff --git a/tracker/tracker-testing-playground/README.md b/tracker/tracker-testing-playground/README.md new file mode 100644 index 000000000..d8ef9057f --- /dev/null +++ b/tracker/tracker-testing-playground/README.md @@ -0,0 +1 @@ +# Create React App diff --git a/tracker/tracker-testing-playground/package.json b/tracker/tracker-testing-playground/package.json new file mode 100644 index 000000000..ccc98d80d --- /dev/null +++ b/tracker/tracker-testing-playground/package.json @@ -0,0 +1,51 @@ +{ + "name": "with-create-react-app", + "version": "0.1.0", + "private": true, + "dependencies": { + "@openreplay/tracker": "file:../../../openreplay/tracker/tracker", + "@openreplay/tracker-axios": "3.6.1", + "@openreplay/tracker-fetch": "3.6.1", + "@openreplay/tracker-redux": "3.5.1", + "@openreplay/tracker-zustand": "1.0.2", + "@tanstack/react-table": "^8.2.6", + "@types/node": "^12.0.0", + "@types/react": "^17.0.0", + "@types/react-dom": "^17.0.0", + "@types/uuid": "^8.3.3", + "axios": "^0.27.2", + "react": "^17.0.2", + "react-bootstrap": "^2.5.0", + "react-dom": "^17.0.2", + "react-scripts": "4.0.3", + "react-table": "^7.8.0", + "redux": "^4.2.0", + "typescript": "^4.1.2", + "uuid": "^8.3.2", + "zustand": "^4.1.1" + }, + "scripts": { + "start": "HOST=0.0.0.0 react-scripts start", + "build": "NODE_OPTIONS=--openssl-legacy-provider react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/tracker/tracker-testing-playground/public/favicon.ico b/tracker/tracker-testing-playground/public/favicon.ico new file mode 100644 index 000000000..a11777cc4 Binary files /dev/null and b/tracker/tracker-testing-playground/public/favicon.ico differ diff --git a/tracker/tracker-testing-playground/public/index.html b/tracker/tracker-testing-playground/public/index.html new file mode 100644 index 000000000..aa069f27c --- /dev/null +++ b/tracker/tracker-testing-playground/public/index.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <meta name="theme-color" content="#000000" /> + <meta + name="description" + content="Web site created using create-react-app" + /> + <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> + <!-- + manifest.json provides metadata used when your web app is installed on a + user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ + --> + <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> + <!-- + Notice the use of %PUBLIC_URL% in the tags above. + It will be replaced with the URL of the `public` folder during the build. + Only files inside the `public` folder can be referenced from the HTML. + + Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will + work correctly both with client-side routing and a non-root public URL. + Learn how to configure a non-root public URL by running `npm run build`. + --> + <title>React App + + + +
+ + + diff --git a/tracker/tracker-testing-playground/public/logo192.png b/tracker/tracker-testing-playground/public/logo192.png new file mode 100644 index 000000000..fc44b0a37 Binary files /dev/null and b/tracker/tracker-testing-playground/public/logo192.png differ diff --git a/tracker/tracker-testing-playground/public/logo512.png b/tracker/tracker-testing-playground/public/logo512.png new file mode 100644 index 000000000..a4e47a654 Binary files /dev/null and b/tracker/tracker-testing-playground/public/logo512.png differ diff --git a/tracker/tracker-testing-playground/public/manifest.json b/tracker/tracker-testing-playground/public/manifest.json new file mode 100644 index 000000000..080d6c77a --- /dev/null +++ b/tracker/tracker-testing-playground/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/tracker/tracker-testing-playground/public/robots.txt b/tracker/tracker-testing-playground/public/robots.txt new file mode 100644 index 000000000..e9e57dc4d --- /dev/null +++ b/tracker/tracker-testing-playground/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/tracker/tracker-testing-playground/src/App.css b/tracker/tracker-testing-playground/src/App.css new file mode 100644 index 000000000..b63393703 --- /dev/null +++ b/tracker/tracker-testing-playground/src/App.css @@ -0,0 +1,37 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40px; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #282c34; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/tracker/tracker-testing-playground/src/App.tsx b/tracker/tracker-testing-playground/src/App.tsx new file mode 100644 index 000000000..f5d7b9fa7 --- /dev/null +++ b/tracker/tracker-testing-playground/src/App.tsx @@ -0,0 +1,455 @@ +import React from "react"; +import Tracker from "@openreplay/tracker"; +import axios from "axios"; +import create from "zustand"; + +import { userId, getTracker, store } from "./tracker"; + +import logo from "./logo.svg"; +import "./App.css"; + +import { + ColumnDef, + flexRender, + getCoreRowModel, + useReactTable, +} from "@tanstack/react-table"; + +const trackerEx = getTracker(); + +function App() { + const [view, setView] = React.useState("main"); + const [tracker, setTracker] = React.useState(); + const [counter, setCounter] = React.useState(store.getState().value); + const [data, setData] = React.useState(() => [...defaultData]); + const [shouldRerender, setShould] = React.useState(false); + const [sRen, setRen] = React.useState(true); + const [sUrl, setURL] = React.useState(""); + const rerender = React.useReducer(() => ({}), {})[1]; + const [input, setInput] = React.useState(""); + store.subscribe(() => setCounter(store.getState().value)); + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + const chasd = () => { + data.forEach((i) => (i.age = Math.floor(Math.random() * 100))); + console.log(data[0]); + setData(data); + + rerender(); + }; + + React.useEffect(() => { + trackerEx.start().then((session) => { + console.log(session); + const url = trackerEx.getSessionURL(); + setURL(url || ""); + setTracker(trackerEx); + }); + }, []); + + React.useEffect(() => { + const id = setInterval(() => shouldRerender && rerender(), 5000); + return () => clearInterval(id); + }, [rerender, shouldRerender]); + + if (!sRen) + return ( +
+ + test +
+ ); + + const testAPI = () => { + fetch("https://pokeapi.co/api/v2/pokemon/ditto") + .then((r) => r.json()) + .then((p) => console.log(p)); + }; + + const testAPIError = () => { + fetch("https://pokeapi.co/api/v2/poakemon/ditto") + .then((r) => r.json()) + .then((p) => console.log(p)); + }; + + const incrementRedux = () => { + store.dispatch({ type: "counter/incremented" }); + }; + const redux2 = () => { + store.dispatch({ type: "counter/test" }); + }; + const redux3 = () => { + store.dispatch({ type: "counter/test2" }); + }; + const redux4 = () => { + store.dispatch({ type: "counter/test3" }); + }; + + const customEvent = () => { + tracker?.event("test", "event"); + }; + + const customError = () => { + tracker?.handleError(new Error(), { testing: "stuff", taha: "is cool" }); + }; + + const testJSError = () => { + throw new Error("Im the error"); + }; + + const axiosInst = axios.create(); + + const addAxios = () => { + console.log("hull"); + }; + const testAxiosApi = () => { + axiosInst("https://pokeapi.co/api/v2/pokemon/ditto").then((p) => + console.log(p) + ); + }; + return ( + <> + + + + + + + + + test link +
+ + + + +
+ + + {view} view +
+ {view === "main" ? ( +
+ {/* */} + logo + Your userId is [{userId}] + + session url:{" "} + + {sUrl} + + + setInput(e.target.value)} + type="text" + /> +
should not be seen here
+ +
+ obscured +
+
+ masked deprecated +
+ +
+ ) : ( +
+
+ + {table + .getHeaderGroups() + .map( + (headerGroup: { + id: React.Key | null | undefined; + headers: any[]; + }) => ( + + {headerGroup.headers.map( + (header: { + id: React.Key | null | undefined; + isPlaceholder: any; + column: { columnDef: { header: any } }; + getContext: () => any; + }) => ( + + ) + )} + + ) + )} + + + {table + .getRowModel() + .rows.map( + (row: { + id: React.Key | null | undefined; + getVisibleCells: () => any[]; + }) => ( + + {row + .getVisibleCells() + .map( + (cell: { + id: React.Key | null | undefined; + column: { columnDef: { cell: any } }; + getContext: () => any; + }) => ( + + ) + )} + + ) + )} + + + {table + .getFooterGroups() + .map( + (footerGroup: { + id: React.Key | null | undefined; + headers: any[]; + }) => ( + + {footerGroup.headers.map( + (header: { + id: React.Key | null | undefined; + isPlaceholder: any; + column: { columnDef: { footer: any } }; + getContext: () => any; + }) => ( + + ) + )} + + ) + )} + +
+ {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} +
+ {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} +
+ {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.footer, + header.getContext() + )} +
+ + + {table + .getHeaderGroups() + .map( + (headerGroup: { + id: React.Key | null | undefined; + headers: any[]; + }) => ( + + {headerGroup.headers.map( + (header: { + id: React.Key | null | undefined; + isPlaceholder: any; + column: { columnDef: { header: any } }; + getContext: () => any; + }) => ( + + ) + )} + + ) + )} + + + {table + .getRowModel() + .rows.map( + (row: { + id: React.Key | null | undefined; + getVisibleCells: () => any[]; + }) => ( + + {row + .getVisibleCells() + .map( + (cell: { + id: React.Key | null | undefined; + column: { columnDef: { cell: any } }; + getContext: () => any; + }) => ( + + ) + )} + + ) + )} + + + {table + .getFooterGroups() + .map( + (footerGroup: { + id: React.Key | null | undefined; + headers: any[]; + }) => ( + + {footerGroup.headers.map( + (header: { + id: React.Key | null | undefined; + isPlaceholder: any; + column: { columnDef: { footer: any } }; + getContext: () => any; + }) => ( + + ) + )} + + ) + )} + +
+ {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} +
+ {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} +
+ {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.footer, + header.getContext() + )} +
+
+
+ )} +
+ + ); +} + +export default App; + +type Person = { + firstName: string; + lastName: string; + age: number; + visits: number; + status: string; + progress: number; +}; + +const defaultData: Person[] = [ + { + firstName: "tanner", + lastName: "linsley", + age: Math.floor(Math.random() * 100), + visits: 100, + status: "In Relationship", + progress: 50, + }, + { + firstName: "tandy", + lastName: "miller", + age: 40, + visits: 40, + status: "Single", + progress: 80, + }, + { + firstName: "joe", + lastName: "dirte", + age: 45, + visits: 20, + status: "Complicated", + progress: 10, + }, +]; + +const testArr = [...defaultData]; + +for (let i = 0; i < 30; i++) { + defaultData.push(...testArr); +} + +const columns: ColumnDef[] = [ + { + accessorKey: "firstName", + cell: (info: { getValue: () => any }) => info.getValue(), + footer: (info: { column: { id: any } }) => info.column.id, + }, + { + accessorFn: (row: { lastName: any }) => row.lastName, + id: "lastName", + cell: (info: { + getValue: () => + | boolean + | React.ReactChild + | React.ReactFragment + | React.ReactPortal + | null + | undefined; + }) => {info.getValue()}, + header: () => Last Name, + footer: (info: { column: { id: any } }) => info.column.id, + }, + { + accessorKey: "age", + header: () => "Age", + footer: (info: { column: { id: any } }) => info.column.id, + }, + { + accessorKey: "visits", + header: () => Visits, + footer: (info: { column: { id: any } }) => info.column.id, + }, + { + accessorKey: "status", + header: "Status", + footer: (info: { column: { id: any } }) => info.column.id, + }, + { + accessorKey: "progress", + header: "Profile Progress", + footer: (info: { column: { id: any } }) => info.column.id, + }, +]; diff --git a/tracker/tracker-testing-playground/src/index.css b/tracker/tracker-testing-playground/src/index.css new file mode 100644 index 000000000..ec2585e8c --- /dev/null +++ b/tracker/tracker-testing-playground/src/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/tracker/tracker-testing-playground/src/index.tsx b/tracker/tracker-testing-playground/src/index.tsx new file mode 100644 index 000000000..29ab5a5be --- /dev/null +++ b/tracker/tracker-testing-playground/src/index.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import "./index.css"; +import App from "./App"; +// import reportWebVitals from "./reportWebVitals"; + +ReactDOM.render( + + + , + document.getElementById("root") +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +// reportWebVitals(); \ No newline at end of file diff --git a/tracker/tracker-testing-playground/src/logo.svg b/tracker/tracker-testing-playground/src/logo.svg new file mode 100644 index 000000000..9dfc1c058 --- /dev/null +++ b/tracker/tracker-testing-playground/src/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tracker/tracker-testing-playground/src/react-app-env.d.ts b/tracker/tracker-testing-playground/src/react-app-env.d.ts new file mode 100644 index 000000000..2e12e99d2 --- /dev/null +++ b/tracker/tracker-testing-playground/src/react-app-env.d.ts @@ -0,0 +1,7 @@ +/// + +declare namespace NodeJS { + interface ProcessEnv { + readonly REACT_APP_OPEN_REPLAY_KEY: string; + } +} diff --git a/tracker/tracker-testing-playground/src/setupTests.ts b/tracker/tracker-testing-playground/src/setupTests.ts new file mode 100644 index 000000000..1dd407a63 --- /dev/null +++ b/tracker/tracker-testing-playground/src/setupTests.ts @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import "@testing-library/jest-dom"; diff --git a/tracker/tracker-testing-playground/src/store.js b/tracker/tracker-testing-playground/src/store.js new file mode 100644 index 000000000..fde0b2dfa --- /dev/null +++ b/tracker/tracker-testing-playground/src/store.js @@ -0,0 +1,23 @@ +const initialState = { + value: 0, + test: { key: 'test' }, + deleteMe: 'a', + arr: [], +} + +export function counterReducer(state = initialState, action) { + switch (action.type) { + case 'counter/incremented': + return { ...state, value: state.value + 1 } + case 'counter/decremented': + return { ...state, value: state.value - 1 } + case 'counter/test': + return { ...state, test: { key: 'value1' }, deleteMe: 'asasd'} + case 'couter/test2': + return { ...state, test: { key: 'value2' }, deleteMe: null } + case 'couter/test3': + return { ...state, test: { key: null }, deleteMe: 'aaaaa', arr: [2,2,23,3] } + default: + return state + } +} diff --git a/tracker/tracker-testing-playground/src/tracker.ts b/tracker/tracker-testing-playground/src/tracker.ts new file mode 100644 index 000000000..33ab677d0 --- /dev/null +++ b/tracker/tracker-testing-playground/src/tracker.ts @@ -0,0 +1,44 @@ +import Tracker +, { SanitizeLevel } +from "@openreplay/tracker"; +import { createStore, applyMiddleware } from 'redux' +import { counterReducer } from "./store"; +import { v4 } from "uuid"; +import trackerRedux from '@openreplay/tracker-redux'; + +export const userId = v4(); + +localStorage.removeItem("__openreplay_uuid"); +localStorage.removeItem("__openreplay_token"); + +const tracker = new Tracker({ + __DISABLE_SECURE_MODE: true, + projectKey: process.env.REACT_APP_KEY!, + ingestPoint: process.env.REACT_APP_INGEST, + // @ts-ignore + network: { capturePayload: true }, + verbose: true, + __debug__: true, + onStart: () => { + tracker.setUserID('Testing_bot'); + tracker.setMetadata('test', 'cypress') + }, + + domSanitizer: (node) => { + return node.className ? node.className.includes?.('testhide') + ? SanitizeLevel.Hidden : node.className.includes?.('testobscure') + ? SanitizeLevel.Obscured : SanitizeLevel.Plain : 0 + } +}) + +const openReplayMiddleware = tracker.use(trackerRedux()) + +export const store = createStore( + // @ts-ignore + counterReducer, + applyMiddleware(openReplayMiddleware) +) + +export const getTracker = () => { + return tracker +} diff --git a/tracker/tracker-testing-playground/tsconfig.json b/tracker/tracker-testing-playground/tsconfig.json new file mode 100644 index 000000000..a273b0cfc --- /dev/null +++ b/tracker/tracker-testing-playground/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": [ + "src" + ] +} diff --git a/tracker/tracker-testing-playground/yarn-error.log b/tracker/tracker-testing-playground/yarn-error.log new file mode 100644 index 000000000..f9b639b6f --- /dev/null +++ b/tracker/tracker-testing-playground/yarn-error.log @@ -0,0 +1,11691 @@ +Arguments: + /Users/nikitamelnikov/.nvm/versions/node/v17.9.0/bin/node /opt/homebrew/Cellar/yarn/1.22.19/libexec/bin/yarn.js upgrade @openreplay/tracker-assist + +PATH: + /Users/nikitamelnikov/.nvm/versions/node/v17.9.0/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin:/Users/nikitamelnikov/.nvm/versions/node/v17.9.0/bin:/opt/homebrew/bin:/opt/homebrew/sbin + +Yarn version: + 1.22.19 + +Node version: + 17.9.0 + +Platform: + darwin arm64 + +Trace: + Error: ENOENT: no such file or directory, lstat '/Users/nikitamelnikov/Library/Caches/Yarn/v6/npm-ansi-styles-3.2.1-integrity/node_modules/ansi-styles' + +npm manifest: + { + "name": "with-create-react-app", + "version": "0.1.0", + "private": true, + "dependencies": { + "@openreplay/tracker": "file:../../../openreplay/tracker/tracker", + "@openreplay/tracker-assist": "file:../../../openreplay/tracker/tracker-assist", + "@openreplay/tracker-axios": "3.6.1", + "@openreplay/tracker-fetch": "3.6.1", + "@openreplay/tracker-redux": "3.5.1", + "@openreplay/tracker-zustand": "1.0.2", + "@tanstack/react-table": "^8.2.6", + "@types/node": "^12.0.0", + "@types/react": "^17.0.0", + "@types/react-dom": "^17.0.0", + "@types/uuid": "^8.3.3", + "axios": "^0.27.2", + "react": "^17.0.2", + "react-bootstrap": "^2.5.0", + "react-dom": "^17.0.2", + "react-scripts": "4.0.3", + "react-table": "^7.8.0", + "redux": "^4.2.0", + "typescript": "^4.1.2", + "uuid": "^8.3.2", + "web-vitals": "^1.0.1", + "zustand": "^4.1.1" + }, + "scripts": { + "start": "HOST=0.0.0.0 NODE_OPTIONS=--openssl-legacy-provider react-scripts start", + "build": "NODE_OPTIONS=--openssl-legacy-provider react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } + } + +yarn manifest: + No manifest + +Lockfile: + # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. + # yarn lockfile v1 + + + "@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + dependencies: + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" + + "@babel/code-frame@7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== + dependencies: + "@babel/highlight" "^7.10.4" + + "@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.5.5": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + + "@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.0.tgz" + integrity sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw== + + "@babel/core@7.12.3": + version "7.12.3" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz" + integrity sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.12.1" + "@babel/helper-module-transforms" "^7.12.1" + "@babel/helpers" "^7.12.1" + "@babel/parser" "^7.12.3" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.12.1" + "@babel/types" "^7.12.1" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + + "@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.7.5", "@babel/core@^7.8.4": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.19.0.tgz" + integrity sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.0" + "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-module-transforms" "^7.19.0" + "@babel/helpers" "^7.19.0" + "@babel/parser" "^7.19.0" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + + "@babel/generator@^7.12.1", "@babel/generator@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz" + integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg== + dependencies: + "@babel/types" "^7.19.0" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + + "@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== + dependencies: + "@babel/types" "^7.18.6" + + "@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz" + integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.18.6" + "@babel/types" "^7.18.9" + + "@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz" + integrity sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA== + dependencies: + "@babel/compat-data" "^7.19.0" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.20.2" + semver "^6.3.0" + + "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz" + integrity sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" + + "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz" + integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + regexpu-core "^5.1.0" + + "@babel/helper-define-polyfill-provider@^0.3.2": + version "0.3.2" + resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz" + integrity sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg== + dependencies: + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + + "@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== + + "@babel/helper-explode-assignable-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz" + integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== + dependencies: + "@babel/types" "^7.18.6" + + "@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" + + "@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + + "@babel/helper-member-expression-to-functions@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz" + integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== + dependencies: + "@babel/types" "^7.18.9" + + "@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + + "@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz" + integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.18.6" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" + + "@babel/helper-optimise-call-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz" + integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== + dependencies: + "@babel/types" "^7.18.6" + + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz" + integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== + + "@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz" + integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-wrap-function" "^7.18.9" + "@babel/types" "^7.18.9" + + "@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz" + integrity sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/traverse" "^7.18.9" + "@babel/types" "^7.18.9" + + "@babel/helper-simple-access@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz" + integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== + dependencies: + "@babel/types" "^7.18.6" + + "@babel/helper-skip-transparent-expression-wrappers@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz" + integrity sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw== + dependencies: + "@babel/types" "^7.18.9" + + "@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + + "@babel/helper-string-parser@^7.18.10": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz" + integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== + + "@babel/helper-validator-identifier@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz" + integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + + "@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + + "@babel/helper-wrap-function@^7.18.9": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz" + integrity sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg== + dependencies: + "@babel/helper-function-name" "^7.19.0" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" + + "@babel/helpers@^7.12.1", "@babel/helpers@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz" + integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg== + dependencies: + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" + + "@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + + "@babel/parser@^7.1.0", "@babel/parser@^7.12.3", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.0", "@babel/parser@^7.7.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz" + integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw== + + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": + version "7.18.6" + 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.18.6.tgz" + integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz" + integrity sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + + "@babel/plugin-proposal-async-generator-functions@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.0.tgz" + integrity sha512-nhEByMUTx3uZueJ/QkJuSlCfN4FGg+xy+vRsfGQGzSauq5ks2Deid2+05Q3KhfaUjvec1IGhw/Zm3cFm8JigTQ== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-remap-async-to-generator" "^7.18.9" + "@babel/plugin-syntax-async-generators" "^7.8.4" + + "@babel/plugin-proposal-class-properties@^7.16.0", "@babel/plugin-proposal-class-properties@^7.18.6": + 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== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-proposal-class-static-block@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz" + integrity sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + + "@babel/plugin-proposal-decorators@^7.16.4": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.19.0.tgz" + integrity sha512-Bo5nOSjiJccjv00+BrDkmfeBLBi2B0qe8ygj24KdL8VdwtZz+710NCwehF+x/Ng+0mkHx5za2eAofmvVFLF4Fg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/plugin-syntax-decorators" "^7.19.0" + + "@babel/plugin-proposal-dynamic-import@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz" + integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + + "@babel/plugin-proposal-export-namespace-from@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz" + integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + + "@babel/plugin-proposal-json-strings@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz" + integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-json-strings" "^7.8.3" + + "@babel/plugin-proposal-logical-assignment-operators@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz" + integrity sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + + "@babel/plugin-proposal-nullish-coalescing-operator@^7.16.0", "@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz" + integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + + "@babel/plugin-proposal-numeric-separator@^7.16.0", "@babel/plugin-proposal-numeric-separator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz" + integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + + "@babel/plugin-proposal-object-rest-spread@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz" + integrity sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q== + dependencies: + "@babel/compat-data" "^7.18.8" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.18.8" + + "@babel/plugin-proposal-optional-catch-binding@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz" + integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + + "@babel/plugin-proposal-optional-chaining@^7.16.0", "@babel/plugin-proposal-optional-chaining@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz" + integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + + "@babel/plugin-proposal-private-methods@^7.16.0", "@babel/plugin-proposal-private-methods@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz" + integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-proposal-private-property-in-object@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz" + integrity sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + + "@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz" + integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-syntax-async-generators@^7.8.4": + 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== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + + "@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + + "@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": + 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== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + + "@babel/plugin-syntax-class-static-block@^7.14.5": + 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== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + + "@babel/plugin-syntax-decorators@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.19.0.tgz" + integrity sha512-xaBZUEDntt4faL1yN8oIFlhfXeQAWJW7CLKYsHTUqriCUbj8xOra8bfxxKGi/UwExPFBuPdH4XfHc9rGQhrVkQ== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + + "@babel/plugin-syntax-dynamic-import@^7.8.3": + 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== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + + "@babel/plugin-syntax-export-namespace-from@^7.8.3": + 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== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + + "@babel/plugin-syntax-flow@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz" + integrity sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-syntax-import-assertions@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz" + integrity sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-syntax-import-meta@^7.8.3": + 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== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + + "@babel/plugin-syntax-json-strings@^7.8.3": + 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== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + + "@babel/plugin-syntax-jsx@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz" + integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + 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== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + + "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + 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== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + + "@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": + 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== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + + "@babel/plugin-syntax-object-rest-spread@^7.8.3": + 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== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + + "@babel/plugin-syntax-optional-catch-binding@^7.8.3": + 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== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + + "@babel/plugin-syntax-optional-chaining@^7.8.3": + 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== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + + "@babel/plugin-syntax-private-property-in-object@^7.14.5": + 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== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + + "@babel/plugin-syntax-top-level-await@^7.14.5", "@babel/plugin-syntax-top-level-await@^7.8.3": + 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== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + + "@babel/plugin-syntax-typescript@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz" + integrity sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-transform-arrow-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz" + integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-transform-async-to-generator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz" + integrity sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag== + dependencies: + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-remap-async-to-generator" "^7.18.6" + + "@babel/plugin-transform-block-scoped-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz" + integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-transform-block-scoping@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz" + integrity sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + + "@babel/plugin-transform-classes@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz" + integrity sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" + globals "^11.1.0" + + "@babel/plugin-transform-computed-properties@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz" + integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + + "@babel/plugin-transform-destructuring@^7.18.13": + version "7.18.13" + resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz" + integrity sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + + "@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz" + integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-transform-duplicate-keys@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz" + integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + + "@babel/plugin-transform-exponentiation-operator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz" + integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-transform-flow-strip-types@^7.16.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.19.0.tgz" + integrity sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-flow" "^7.18.6" + + "@babel/plugin-transform-for-of@^7.18.8": + version "7.18.8" + resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz" + integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-transform-function-name@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz" + integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== + dependencies: + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + + "@babel/plugin-transform-literals@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz" + integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + + "@babel/plugin-transform-member-expression-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz" + integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-transform-modules-amd@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz" + integrity sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg== + dependencies: + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + babel-plugin-dynamic-import-node "^2.3.3" + + "@babel/plugin-transform-modules-commonjs@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz" + integrity sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q== + dependencies: + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + babel-plugin-dynamic-import-node "^2.3.3" + + "@babel/plugin-transform-modules-systemjs@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.0.tgz" + integrity sha512-x9aiR0WXAWmOWsqcsnrzGR+ieaTMVyGyffPVA7F8cXAGt/UxefYv6uSHZLkAFChN5M5Iy1+wjE+xJuPt22H39A== + dependencies: + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-module-transforms" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-validator-identifier" "^7.18.6" + babel-plugin-dynamic-import-node "^2.3.3" + + "@babel/plugin-transform-modules-umd@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz" + integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== + dependencies: + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-transform-named-capturing-groups-regex@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.0.tgz" + integrity sha512-HDSuqOQzkU//kfGdiHBt71/hkDTApw4U/cMVgKgX7PqfB3LOaK+2GtCEsBu1dL9CkswDm0Gwehht1dCr421ULQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" + + "@babel/plugin-transform-new-target@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz" + integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-transform-object-super@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz" + integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.6" + + "@babel/plugin-transform-parameters@^7.18.8": + version "7.18.8" + resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz" + integrity sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-transform-property-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz" + integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-transform-react-constant-elements@^7.12.1": + version "7.18.12" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.18.12.tgz" + integrity sha512-Q99U9/ttiu+LMnRU8psd23HhvwXmKWDQIpocm0JKaICcZHnw+mdQbHm6xnSy7dOl8I5PELakYtNBubNQlBXbZw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + + "@babel/plugin-transform-react-display-name@^7.16.0", "@babel/plugin-transform-react-display-name@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz" + integrity sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-transform-react-jsx-development@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz" + integrity sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.18.6" + + "@babel/plugin-transform-react-jsx@^7.18.6": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz" + integrity sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-jsx" "^7.18.6" + "@babel/types" "^7.19.0" + + "@babel/plugin-transform-react-pure-annotations@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz" + integrity sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-transform-regenerator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz" + integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + regenerator-transform "^0.15.0" + + "@babel/plugin-transform-reserved-words@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz" + integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-transform-runtime@^7.16.4": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz" + integrity sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ== + dependencies: + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.9" + babel-plugin-polyfill-corejs2 "^0.3.2" + babel-plugin-polyfill-corejs3 "^0.5.3" + babel-plugin-polyfill-regenerator "^0.4.0" + semver "^6.3.0" + + "@babel/plugin-transform-shorthand-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz" + integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-transform-spread@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz" + integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + + "@babel/plugin-transform-sticky-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz" + integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/plugin-transform-template-literals@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz" + integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + + "@babel/plugin-transform-typeof-symbol@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz" + integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + + "@babel/plugin-transform-typescript@^7.18.6": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.0.tgz" + integrity sha512-DOOIywxPpkQHXijXv+s9MDAyZcLp12oYRl3CMWZ6u7TjSoCBq/KqHR/nNFR3+i2xqheZxoF0H2XyL7B6xeSRuA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-typescript" "^7.18.6" + + "@babel/plugin-transform-unicode-escapes@^7.18.10": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz" + integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + + "@babel/plugin-transform-unicode-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz" + integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + + "@babel/preset-env@^7.12.1", "@babel/preset-env@^7.16.4", "@babel/preset-env@^7.8.4": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.0.tgz" + integrity sha512-1YUju1TAFuzjIQqNM9WsF4U6VbD/8t3wEAlw3LFYuuEr+ywqLRcSXxFKz4DCEj+sN94l/XTDiUXYRrsvMpz9WQ== + dependencies: + "@babel/compat-data" "^7.19.0" + "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-async-generator-functions" "^7.19.0" + "@babel/plugin-proposal-class-properties" "^7.18.6" + "@babel/plugin-proposal-class-static-block" "^7.18.6" + "@babel/plugin-proposal-dynamic-import" "^7.18.6" + "@babel/plugin-proposal-export-namespace-from" "^7.18.9" + "@babel/plugin-proposal-json-strings" "^7.18.6" + "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" + "@babel/plugin-proposal-numeric-separator" "^7.18.6" + "@babel/plugin-proposal-object-rest-spread" "^7.18.9" + "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-private-methods" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object" "^7.18.6" + "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" + "@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.18.6" + "@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-transform-arrow-functions" "^7.18.6" + "@babel/plugin-transform-async-to-generator" "^7.18.6" + "@babel/plugin-transform-block-scoped-functions" "^7.18.6" + "@babel/plugin-transform-block-scoping" "^7.18.9" + "@babel/plugin-transform-classes" "^7.19.0" + "@babel/plugin-transform-computed-properties" "^7.18.9" + "@babel/plugin-transform-destructuring" "^7.18.13" + "@babel/plugin-transform-dotall-regex" "^7.18.6" + "@babel/plugin-transform-duplicate-keys" "^7.18.9" + "@babel/plugin-transform-exponentiation-operator" "^7.18.6" + "@babel/plugin-transform-for-of" "^7.18.8" + "@babel/plugin-transform-function-name" "^7.18.9" + "@babel/plugin-transform-literals" "^7.18.9" + "@babel/plugin-transform-member-expression-literals" "^7.18.6" + "@babel/plugin-transform-modules-amd" "^7.18.6" + "@babel/plugin-transform-modules-commonjs" "^7.18.6" + "@babel/plugin-transform-modules-systemjs" "^7.19.0" + "@babel/plugin-transform-modules-umd" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.0" + "@babel/plugin-transform-new-target" "^7.18.6" + "@babel/plugin-transform-object-super" "^7.18.6" + "@babel/plugin-transform-parameters" "^7.18.8" + "@babel/plugin-transform-property-literals" "^7.18.6" + "@babel/plugin-transform-regenerator" "^7.18.6" + "@babel/plugin-transform-reserved-words" "^7.18.6" + "@babel/plugin-transform-shorthand-properties" "^7.18.6" + "@babel/plugin-transform-spread" "^7.19.0" + "@babel/plugin-transform-sticky-regex" "^7.18.6" + "@babel/plugin-transform-template-literals" "^7.18.9" + "@babel/plugin-transform-typeof-symbol" "^7.18.9" + "@babel/plugin-transform-unicode-escapes" "^7.18.10" + "@babel/plugin-transform-unicode-regex" "^7.18.6" + "@babel/preset-modules" "^0.1.5" + "@babel/types" "^7.19.0" + babel-plugin-polyfill-corejs2 "^0.3.2" + babel-plugin-polyfill-corejs3 "^0.5.3" + babel-plugin-polyfill-regenerator "^0.4.0" + core-js-compat "^3.22.1" + semver "^6.3.0" + + "@babel/preset-modules@^0.1.5": + version "0.1.5" + resolved "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz" + integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + + "@babel/preset-react@^7.12.5", "@babel/preset-react@^7.16.0": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz" + integrity sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-transform-react-display-name" "^7.18.6" + "@babel/plugin-transform-react-jsx" "^7.18.6" + "@babel/plugin-transform-react-jsx-development" "^7.18.6" + "@babel/plugin-transform-react-pure-annotations" "^7.18.6" + + "@babel/preset-typescript@^7.16.0": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz" + integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-transform-typescript" "^7.18.6" + + "@babel/runtime-corejs3@^7.10.2": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.19.0.tgz" + integrity sha512-JyXXoCu1N8GLuKc2ii8y5RGma5FMpFeO2nAQIe0Yzrbq+rQnN+sFj47auLblR5ka6aHNGPDgv8G/iI2Grb0ldQ== + dependencies: + core-js-pure "^3.20.2" + regenerator-runtime "^0.13.4" + + "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz" + integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== + dependencies: + regenerator-runtime "^0.13.4" + + "@babel/template@^7.10.4", "@babel/template@^7.18.10", "@babel/template@^7.3.3": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz" + integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.10" + "@babel/types" "^7.18.10" + + "@babel/traverse@^7.1.0", "@babel/traverse@^7.12.1", "@babel/traverse@^7.18.9", "@babel/traverse@^7.19.0", "@babel/traverse@^7.7.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.0.tgz" + integrity sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.0" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.19.0" + "@babel/types" "^7.19.0" + debug "^4.1.0" + globals "^11.1.0" + + "@babel/types@^7.0.0", "@babel/types@^7.12.1", "@babel/types@^7.12.6", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz" + integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== + dependencies: + "@babel/helper-string-parser" "^7.18.10" + "@babel/helper-validator-identifier" "^7.18.6" + to-fast-properties "^2.0.0" + + "@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + + "@cnakazawa/watch@^1.0.3": + version "1.0.4" + resolved "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz" + integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== + dependencies: + exec-sh "^0.3.2" + minimist "^1.2.0" + + "@csstools/convert-colors@^1.4.0": + version "1.4.0" + resolved "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz" + integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== + + "@csstools/normalize.css@^10.1.0": + version "10.1.0" + resolved "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz" + integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== + + "@eslint/eslintrc@^0.4.3": + version "0.4.3" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + + "@gar/promisify@^1.0.1": + version "1.1.3" + resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== + + "@hapi/address@2.x.x": + version "2.1.4" + resolved "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz" + integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ== + + "@hapi/bourne@1.x.x": + version "1.3.2" + resolved "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz" + integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA== + + "@hapi/hoek@8.x.x", "@hapi/hoek@^8.3.0": + version "8.5.1" + resolved "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz" + integrity sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow== + + "@hapi/joi@^15.1.0": + version "15.1.1" + resolved "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz" + integrity sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ== + dependencies: + "@hapi/address" "2.x.x" + "@hapi/bourne" "1.x.x" + "@hapi/hoek" "8.x.x" + "@hapi/topo" "3.x.x" + + "@hapi/topo@3.x.x": + version "3.1.6" + resolved "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz" + integrity sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ== + dependencies: + "@hapi/hoek" "^8.3.0" + + "@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== + dependencies: + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" + + "@humanwhocodes/object-schema@^1.2.0": + version "1.2.1" + resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + + "@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + 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" + + "@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + + "@jest/console@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz" + integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^26.6.2" + jest-util "^26.6.2" + slash "^3.0.0" + + "@jest/core@^26.6.0", "@jest/core@^26.6.3": + version "26.6.3" + resolved "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz" + integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== + dependencies: + "@jest/console" "^26.6.2" + "@jest/reporters" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-changed-files "^26.6.2" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-resolve-dependencies "^26.6.3" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + jest-watcher "^26.6.2" + micromatch "^4.0.2" + p-each-series "^2.1.0" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + + "@jest/environment@^26.6.0", "@jest/environment@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz" + integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== + dependencies: + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + + "@jest/fake-timers@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz" + integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== + dependencies: + "@jest/types" "^26.6.2" + "@sinonjs/fake-timers" "^6.0.1" + "@types/node" "*" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-util "^26.6.2" + + "@jest/globals@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz" + integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/types" "^26.6.2" + expect "^26.6.2" + + "@jest/reporters@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz" + integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + graceful-fs "^4.2.4" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^4.0.3" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + jest-haste-map "^26.6.2" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^4.0.1" + terminal-link "^2.0.0" + v8-to-istanbul "^7.0.0" + optionalDependencies: + node-notifier "^8.0.0" + + "@jest/source-map@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz" + integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.4" + source-map "^0.6.0" + + "@jest/test-result@^26.6.0", "@jest/test-result@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz" + integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== + dependencies: + "@jest/console" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + + "@jest/test-sequencer@^26.6.3": + version "26.6.3" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz" + integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== + dependencies: + "@jest/test-result" "^26.6.2" + graceful-fs "^4.2.4" + jest-haste-map "^26.6.2" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + + "@jest/transform@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz" + integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^26.6.2" + babel-plugin-istanbul "^6.0.0" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.4" + jest-haste-map "^26.6.2" + jest-regex-util "^26.0.0" + jest-util "^26.6.2" + micromatch "^4.0.2" + pirates "^4.0.1" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + + "@jest/types@^26.6.0", "@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + + "@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + + "@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + + "@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + + "@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + + "@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + + "@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + + "@jridgewell/trace-mapping@^0.3.9": + version "0.3.15" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz" + integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + + "@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + + "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + + "@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + + "@npmcli/fs@^1.0.0": + version "1.1.1" + resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz" + integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + + "@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + + "@openreplay/tracker-assist@file:../../../openreplay/tracker/tracker-assist": + version "4.1.1" + dependencies: + csstype "^3.0.10" + peerjs "1.4.6" + socket.io-client "^4.4.1" + + "@openreplay/tracker-axios@3.6.1": + version "3.6.1" + resolved "https://registry.npmjs.org/@openreplay/tracker-axios/-/tracker-axios-3.6.1.tgz" + integrity sha512-YZOZ37iAN+OC8hkj4Ur668uBPXPKLUznDHiVKfBYo3tLKWea6d0BelYELCQWCInw9nSBabwk7/EX4iwduQS7cQ== + + "@openreplay/tracker-fetch@3.6.1": + version "3.6.1" + resolved "https://registry.npmjs.org/@openreplay/tracker-fetch/-/tracker-fetch-3.6.1.tgz" + integrity sha512-l+SUogc+hhUgEbpi65leskesWBjZWHKNSkoGQ9ix43HaPsUmT6nrw6+xkFNPRo1ZkqFbG2Hd+Otqs31TU+g19Q== + + "@openreplay/tracker-redux@3.5.1": + version "3.5.1" + resolved "https://registry.npmjs.org/@openreplay/tracker-redux/-/tracker-redux-3.5.1.tgz" + integrity sha512-ZaYOp1zpkoHKvALHj+nOr5CjX75p5N6bHNZD8lPx85o7cJ195DAvOxNKNVUk0VW8OaavkSzfvOWc31Pfc2JQoA== + + "@openreplay/tracker-zustand@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@openreplay/tracker-zustand/-/tracker-zustand-1.0.2.tgz#715e5ffef8eb4e3af1b7c68837272b1a4d3c2aeb" + integrity sha512-m30UoV4HEPAHIiEmz3fmeCPxt4snPksliCi8oBWsxBR0givthe+mCDXJHExFmeVBpC73mA8KeWmb2G4nBlA2uA== + + "@openreplay/tracker@file:../../../openreplay/tracker/tracker": + version "4.1.5" + dependencies: + error-stack-parser "^2.0.6" + + "@pmmmwh/react-refresh-webpack-plugin@0.4.3": + version "0.4.3" + resolved "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.3.tgz" + integrity sha512-br5Qwvh8D2OQqSXpd1g/xqXKnK0r+Jz6qVKBbWmpUcrbGOxUrf39V5oZ1876084CGn18uMdR5uvPqBv9UqtBjQ== + dependencies: + ansi-html "^0.0.7" + error-stack-parser "^2.0.6" + html-entities "^1.2.1" + native-url "^0.2.6" + schema-utils "^2.6.5" + source-map "^0.7.3" + + "@popperjs/core@^2.11.5": + version "2.11.6" + resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz" + integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== + + "@react-aria/ssr@^3.2.0": + version "3.3.0" + resolved "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.3.0.tgz" + integrity sha512-yNqUDuOVZIUGP81R87BJVi/ZUZp/nYOBXbPsRe7oltJOfErQZD+UezMpw4vM2KRz18cURffvmC8tJ6JTeyDtaQ== + dependencies: + "@babel/runtime" "^7.6.2" + + "@restart/hooks@^0.4.6", "@restart/hooks@^0.4.7": + version "0.4.7" + resolved "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.7.tgz" + integrity sha512-ZbjlEHcG+FQtpDPHd7i4FzNNvJf2enAwZfJbpM8CW7BhmOAbsHpZe3tsHwfQUrBuyrxWqPYp2x5UMnilWcY22A== + dependencies: + dequal "^2.0.2" + + "@restart/ui@^1.3.1": + version "1.4.0" + resolved "https://registry.npmjs.org/@restart/ui/-/ui-1.4.0.tgz" + integrity sha512-5dDj5uDzUgK1iijWPRg6AnxjkHM04XhTQDJirM1h/8tIc7KyLtF9YyjcCpNEn259hPMXswpkfXKNgiag0skPFg== + dependencies: + "@babel/runtime" "^7.18.3" + "@popperjs/core" "^2.11.5" + "@react-aria/ssr" "^3.2.0" + "@restart/hooks" "^0.4.7" + "@types/warning" "^3.0.0" + dequal "^2.0.2" + dom-helpers "^5.2.0" + uncontrollable "^7.2.1" + warning "^4.0.3" + + "@rollup/plugin-node-resolve@^7.1.1": + version "7.1.3" + resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz" + integrity sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q== + dependencies: + "@rollup/pluginutils" "^3.0.8" + "@types/resolve" "0.0.8" + builtin-modules "^3.1.0" + is-module "^1.0.0" + resolve "^1.14.2" + + "@rollup/plugin-replace@^2.3.1": + version "2.4.2" + resolved "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz" + integrity sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg== + dependencies: + "@rollup/pluginutils" "^3.1.0" + magic-string "^0.25.7" + + "@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": + version "3.1.0" + resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + + "@sinonjs/commons@^1.7.0": + version "1.8.3" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + + "@sinonjs/fake-timers@^6.0.1": + version "6.0.1" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz" + integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== + dependencies: + "@sinonjs/commons" "^1.7.0" + + "@socket.io/component-emitter@~3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" + integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== + + "@surma/rollup-plugin-off-main-thread@^1.1.1": + version "1.4.2" + resolved "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-1.4.2.tgz" + integrity sha512-yBMPqmd1yEJo/280PAMkychuaALyQ9Lkb5q1ck3mjJrFuEobIfhnQ4J3mbvBoISmR3SWMWV+cGB/I0lCQee79A== + dependencies: + ejs "^2.6.1" + magic-string "^0.25.0" + + "@svgr/babel-plugin-add-jsx-attribute@^5.4.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz" + integrity sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg== + + "@svgr/babel-plugin-remove-jsx-attribute@^5.4.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz" + integrity sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg== + + "@svgr/babel-plugin-remove-jsx-empty-expression@^5.0.1": + version "5.0.1" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz" + integrity sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA== + + "@svgr/babel-plugin-replace-jsx-attribute-value@^5.0.1": + version "5.0.1" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz" + integrity sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ== + + "@svgr/babel-plugin-svg-dynamic-title@^5.4.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz" + integrity sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg== + + "@svgr/babel-plugin-svg-em-dimensions@^5.4.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz" + integrity sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw== + + "@svgr/babel-plugin-transform-react-native-svg@^5.4.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz" + integrity sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q== + + "@svgr/babel-plugin-transform-svg-component@^5.5.0": + version "5.5.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz" + integrity sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ== + + "@svgr/babel-preset@^5.5.0": + version "5.5.0" + resolved "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz" + integrity sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "^5.4.0" + "@svgr/babel-plugin-remove-jsx-attribute" "^5.4.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "^5.0.1" + "@svgr/babel-plugin-replace-jsx-attribute-value" "^5.0.1" + "@svgr/babel-plugin-svg-dynamic-title" "^5.4.0" + "@svgr/babel-plugin-svg-em-dimensions" "^5.4.0" + "@svgr/babel-plugin-transform-react-native-svg" "^5.4.0" + "@svgr/babel-plugin-transform-svg-component" "^5.5.0" + + "@svgr/core@^5.5.0": + version "5.5.0" + resolved "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz" + integrity sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ== + dependencies: + "@svgr/plugin-jsx" "^5.5.0" + camelcase "^6.2.0" + cosmiconfig "^7.0.0" + + "@svgr/hast-util-to-babel-ast@^5.5.0": + version "5.5.0" + resolved "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz" + integrity sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ== + dependencies: + "@babel/types" "^7.12.6" + + "@svgr/plugin-jsx@^5.5.0": + version "5.5.0" + resolved "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz" + integrity sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA== + dependencies: + "@babel/core" "^7.12.3" + "@svgr/babel-preset" "^5.5.0" + "@svgr/hast-util-to-babel-ast" "^5.5.0" + svg-parser "^2.0.2" + + "@svgr/plugin-svgo@^5.5.0": + version "5.5.0" + resolved "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz" + integrity sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ== + dependencies: + cosmiconfig "^7.0.0" + deepmerge "^4.2.2" + svgo "^1.2.2" + + "@svgr/webpack@5.5.0": + version "5.5.0" + resolved "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz" + integrity sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g== + dependencies: + "@babel/core" "^7.12.3" + "@babel/plugin-transform-react-constant-elements" "^7.12.1" + "@babel/preset-env" "^7.12.1" + "@babel/preset-react" "^7.12.5" + "@svgr/core" "^5.5.0" + "@svgr/plugin-jsx" "^5.5.0" + "@svgr/plugin-svgo" "^5.5.0" + loader-utils "^2.0.0" + + "@swc/helpers@^0.3.13": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.3.17.tgz#7c1b91f43c77e2bba99492162a498d465ef253d5" + integrity sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q== + dependencies: + tslib "^2.4.0" + + "@tanstack/react-table@^8.2.6": + version "8.5.13" + resolved "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.5.13.tgz" + integrity sha512-k52HsnonKwDZMCIy59HfehTnkJYlqkqyvC+BAV0DyWdN9WmSbVq+Kf+luJVENQnXcnEX47dBHiHAjqZu592ZLQ== + dependencies: + "@tanstack/table-core" "8.5.13" + + "@tanstack/table-core@8.5.13": + version "8.5.13" + resolved "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.5.13.tgz" + integrity sha512-kvDRjC7LrLNNNgUMfsBz3nHOwbBLFdosWQ16dfbD9D/OJrvFHRXoUIPzF0MNqqSPghaj7/18fnwXHGypvzoh9Q== + + "@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + + "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": + version "7.1.19" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz" + integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + + "@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== + dependencies: + "@babel/types" "^7.0.0" + + "@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== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + + "@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": + version "7.18.1" + resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz" + integrity sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA== + dependencies: + "@babel/types" "^7.3.0" + + "@types/eslint@^7.29.0": + version "7.29.0" + resolved "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz" + integrity sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + + "@types/estree@*": + version "1.0.0" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz" + integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== + + "@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz" + integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== + + "@types/glob@^7.1.1": + version "7.2.0" + resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz" + integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + + "@types/graceful-fs@^4.1.2": + version "4.1.5" + resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + dependencies: + "@types/node" "*" + + "@types/html-minifier-terser@^5.0.0": + version "5.1.2" + resolved "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz" + integrity sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w== + + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.4" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + + "@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + + "@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + + "@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8": + version "7.0.11" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + + "@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + + "@types/minimatch@*": + version "5.1.2" + resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz" + integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== + + "@types/node@*", "@types/node@^12.0.0": + version "12.20.55" + resolved "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz" + integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== + + "@types/normalize-package-data@^2.4.0": + version "2.4.1" + resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz" + integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== + + "@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + + "@types/prettier@^2.0.0": + version "2.7.0" + resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz" + integrity sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A== + + "@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== + + "@types/q@^1.5.1": + version "1.5.5" + resolved "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz" + integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ== + + "@types/react-dom@^17.0.0": + version "17.0.17" + resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.17.tgz" + integrity sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg== + dependencies: + "@types/react" "^17" + + "@types/react-transition-group@^4.4.4": + version "4.4.5" + resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz" + integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA== + dependencies: + "@types/react" "*" + + "@types/react@*", "@types/react@>=16.9.11", "@types/react@^17", "@types/react@^17.0.0": + version "17.0.49" + resolved "https://registry.npmjs.org/@types/react/-/react-17.0.49.tgz" + integrity sha512-CCBPMZaPhcKkYUTqFs/hOWqKjPxhTEmnZWjlHHgIMop67DsXywf9B5Os9Hz8KSacjNOgIdnZVJamwl232uxoPg== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + + "@types/resolve@0.0.8": + version "0.0.8" + resolved "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz" + integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== + dependencies: + "@types/node" "*" + + "@types/scheduler@*": + version "0.16.2" + resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + + "@types/source-list-map@*": + version "0.1.2" + resolved "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz" + integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== + + "@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + + "@types/tapable@^1", "@types/tapable@^1.0.5": + version "1.0.8" + resolved "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz" + integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ== + + "@types/uglify-js@*": + version "3.17.0" + resolved "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.0.tgz" + integrity sha512-3HO6rm0y+/cqvOyA8xcYLweF0TKXlAxmQASjbOi49Co51A1N4nR4bEwBgRoD9kNM+rqFGArjKr654SLp2CoGmQ== + dependencies: + source-map "^0.6.1" + + "@types/uuid@^8.3.3": + version "8.3.4" + resolved "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + + "@types/warning@^3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz" + integrity sha512-t/Tvs5qR47OLOr+4E9ckN8AmP2Tf16gWq+/qA4iUGS/OOyHVO8wv2vjJuX8SNOUTJyWb+2t7wJm6cXILFnOROA== + + "@types/webpack-sources@*": + version "3.2.0" + resolved "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.0.tgz" + integrity sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg== + dependencies: + "@types/node" "*" + "@types/source-list-map" "*" + source-map "^0.7.3" + + "@types/webpack@^4.41.8": + version "4.41.32" + resolved "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz" + integrity sha512-cb+0ioil/7oz5//7tZUSwbrSAN/NWHrQylz5cW8G0dWTcF/g+/dSdMlKVZspBYuMAN1+WnwHrkxiRrLcwd0Heg== + dependencies: + "@types/node" "*" + "@types/tapable" "^1" + "@types/uglify-js" "*" + "@types/webpack-sources" "*" + anymatch "^3.0.0" + source-map "^0.6.0" + + "@types/yargs-parser@*": + version "21.0.0" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + + "@types/yargs@^15.0.0": + version "15.0.14" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz" + integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ== + dependencies: + "@types/yargs-parser" "*" + + "@typescript-eslint/eslint-plugin@^4.5.0": + version "4.33.0" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz" + integrity sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg== + dependencies: + "@typescript-eslint/experimental-utils" "4.33.0" + "@typescript-eslint/scope-manager" "4.33.0" + debug "^4.3.1" + functional-red-black-tree "^1.0.1" + ignore "^5.1.8" + regexpp "^3.1.0" + semver "^7.3.5" + tsutils "^3.21.0" + + "@typescript-eslint/experimental-utils@4.33.0", "@typescript-eslint/experimental-utils@^4.0.1": + version "4.33.0" + resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz" + integrity sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q== + dependencies: + "@types/json-schema" "^7.0.7" + "@typescript-eslint/scope-manager" "4.33.0" + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/typescript-estree" "4.33.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + + "@typescript-eslint/experimental-utils@^3.10.1": + version "3.10.1" + resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz" + integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/typescript-estree" "3.10.1" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + + "@typescript-eslint/parser@^4.5.0": + version "4.33.0" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz" + integrity sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA== + dependencies: + "@typescript-eslint/scope-manager" "4.33.0" + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/typescript-estree" "4.33.0" + debug "^4.3.1" + + "@typescript-eslint/scope-manager@4.33.0": + version "4.33.0" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz" + integrity sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ== + dependencies: + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/visitor-keys" "4.33.0" + + "@typescript-eslint/types@3.10.1": + version "3.10.1" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz" + integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ== + + "@typescript-eslint/types@4.33.0": + version "4.33.0" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz" + integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== + + "@typescript-eslint/typescript-estree@3.10.1": + version "3.10.1" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz" + integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w== + dependencies: + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/visitor-keys" "3.10.1" + debug "^4.1.1" + glob "^7.1.6" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + + "@typescript-eslint/typescript-estree@4.33.0": + version "4.33.0" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz" + integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA== + dependencies: + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/visitor-keys" "4.33.0" + debug "^4.3.1" + globby "^11.0.3" + is-glob "^4.0.1" + semver "^7.3.5" + tsutils "^3.21.0" + + "@typescript-eslint/visitor-keys@3.10.1": + version "3.10.1" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz" + integrity sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ== + dependencies: + eslint-visitor-keys "^1.1.0" + + "@typescript-eslint/visitor-keys@4.33.0": + version "4.33.0" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz" + integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg== + dependencies: + "@typescript-eslint/types" "4.33.0" + eslint-visitor-keys "^2.0.0" + + "@webassemblyjs/ast@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz" + integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== + dependencies: + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + + "@webassemblyjs/floating-point-hex-parser@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz" + integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== + + "@webassemblyjs/helper-api-error@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz" + integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== + + "@webassemblyjs/helper-buffer@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz" + integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== + + "@webassemblyjs/helper-code-frame@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz" + integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== + dependencies: + "@webassemblyjs/wast-printer" "1.9.0" + + "@webassemblyjs/helper-fsm@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz" + integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== + + "@webassemblyjs/helper-module-context@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz" + integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== + dependencies: + "@webassemblyjs/ast" "1.9.0" + + "@webassemblyjs/helper-wasm-bytecode@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz" + integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== + + "@webassemblyjs/helper-wasm-section@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz" + integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + + "@webassemblyjs/ieee754@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz" + integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + + "@webassemblyjs/leb128@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz" + integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== + dependencies: + "@xtuc/long" "4.2.2" + + "@webassemblyjs/utf8@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz" + integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== + + "@webassemblyjs/wasm-edit@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz" + integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/helper-wasm-section" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-opt" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + "@webassemblyjs/wast-printer" "1.9.0" + + "@webassemblyjs/wasm-gen@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz" + integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + + "@webassemblyjs/wasm-opt@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz" + integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + + "@webassemblyjs/wasm-parser@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz" + integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + + "@webassemblyjs/wast-parser@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz" + integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/floating-point-hex-parser" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-code-frame" "1.9.0" + "@webassemblyjs/helper-fsm" "1.9.0" + "@xtuc/long" "4.2.2" + + "@webassemblyjs/wast-printer@1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz" + integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + "@xtuc/long" "4.2.2" + + "@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + + "@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + + abab@^2.0.3, abab@^2.0.5: + version "2.0.6" + resolved "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + + accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: + 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== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + + acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + + acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + + acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + + acorn@^6.4.1: + version "6.4.2" + resolved "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz" + integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== + + acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + + acorn@^8.2.4, acorn@^8.5.0: + version "8.8.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + + address@1.1.2, address@^1.0.1: + version "1.1.2" + resolved "https://registry.npmjs.org/address/-/address-1.1.2.tgz" + integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA== + + adjust-sourcemap-loader@3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz" + integrity sha512-YBrGyT2/uVQ/c6Rr+t6ZJXniY03YtHGMJQYal368burRGYKqhx9qGTWqcBU5s1CwYY9E/ri63RYyG1IacMZtqw== + dependencies: + loader-utils "^2.0.0" + regex-parser "^2.2.11" + + agent-base@6: + version "6.0.2" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + + aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + + ajv-errors@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz" + integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== + + ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + + ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + 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" + + ajv@^8.0.1: + version "8.11.0" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz" + integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + + alphanum-sort@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz" + integrity sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ== + + ansi-colors@^3.0.0: + version "3.2.4" + resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz" + integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== + + ansi-colors@^4.1.1: + version "4.1.3" + resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + + ansi-escapes@^4.2.1, ansi-escapes@^4.3.1: + version "4.3.2" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + + ansi-html@0.0.7, ansi-html@^0.0.7: + version "0.0.7" + resolved "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz" + integrity sha512-JoAxEa1DfP9m2xfB/y2r/aKcwXNlltr4+0QSBC4TrLfcxyvepX2Pv0t/xpgGV5bGsDzCYV8SzjWgyCW0T9yYbA== + + ansi-regex@^2.0.0: + 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== + + ansi-regex@^4.1.0: + version "4.1.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== + + ansi-regex@^5.0.0, ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + + ansi-styles@^3.2.0, ansi-styles@^3.2.1: + 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== + dependencies: + color-convert "^1.9.0" + + ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + + anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + + anymatch@^3.0.0, anymatch@^3.0.3, anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + + aproba@^1.1.1: + version "1.2.0" + resolved "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + + argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + + aria-query@^4.2.2: + version "4.2.2" + resolved "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz" + integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== + dependencies: + "@babel/runtime" "^7.10.2" + "@babel/runtime-corejs3" "^7.10.2" + + arity-n@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz" + integrity sha512-fExL2kFDC1Q2DUOx3whE/9KoN66IzkY4b4zUHUBFM1ojEYjZZYDcUW3bek/ufGionX9giIKDC5redH2IlGqcQQ== + + arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz" + integrity sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA== + + arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + + arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz" + integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== + + array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + + array-flatten@^2.1.0: + version "2.1.2" + resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz" + integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== + + array-includes@^3.1.4, array-includes@^3.1.5: + version "3.1.5" + resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz" + integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + get-intrinsic "^1.1.1" + is-string "^1.0.7" + + array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz" + integrity sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng== + dependencies: + array-uniq "^1.0.1" + + array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + + array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz" + integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== + + array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz" + integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== + + array.prototype.flat@^1.2.5: + version "1.3.0" + resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz" + integrity sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.2" + es-shim-unscopables "^1.0.0" + + array.prototype.flatmap@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz" + integrity sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.2" + es-shim-unscopables "^1.0.0" + + array.prototype.reduce@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz" + integrity sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.2" + es-array-method-boxes-properly "^1.0.0" + is-string "^1.0.7" + + arrify@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz" + integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== + + asap@~2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + + asn1.js@^5.2.0: + version "5.4.1" + resolved "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" + + assert@^1.1.1: + version "1.5.0" + resolved "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz" + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== + dependencies: + object-assign "^4.1.1" + util "0.10.3" + + assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz" + integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== + + ast-types-flow@^0.0.7: + version "0.0.7" + resolved "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz" + integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag== + + astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + + async-each@^1.0.1: + version "1.0.3" + resolved "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + + async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + + async@^2.6.4: + version "2.6.4" + resolved "https://registry.npmjs.org/async/-/async-2.6.4.tgz" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== + dependencies: + lodash "^4.17.14" + + asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + + at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + + atob@^2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + + autoprefixer@^9.6.1: + version "9.8.8" + resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.8.tgz" + integrity sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA== + dependencies: + browserslist "^4.12.0" + caniuse-lite "^1.0.30001109" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + picocolors "^0.2.1" + postcss "^7.0.32" + postcss-value-parser "^4.1.0" + + axe-core@^4.4.3: + version "4.4.3" + resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.4.3.tgz" + integrity sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w== + + axios@^0.27.2: + version "0.27.2" + resolved "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== + dependencies: + follow-redirects "^1.14.9" + form-data "^4.0.0" + + axobject-query@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz" + integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== + + babel-eslint@^10.1.0: + version "10.1.0" + resolved "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz" + integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" + eslint-visitor-keys "^1.0.0" + resolve "^1.12.0" + + babel-extract-comments@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/babel-extract-comments/-/babel-extract-comments-1.0.0.tgz" + integrity sha512-qWWzi4TlddohA91bFwgt6zO/J0X+io7Qp184Fw0m2JYRSTZnJbFR8+07KmzudHCZgOiKRCrjhylwv9Xd8gfhVQ== + dependencies: + babylon "^6.18.0" + + babel-jest@^26.6.0, babel-jest@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz" + integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== + dependencies: + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/babel__core" "^7.1.7" + babel-plugin-istanbul "^6.0.0" + babel-preset-jest "^26.6.2" + chalk "^4.0.0" + graceful-fs "^4.2.4" + slash "^3.0.0" + + babel-loader@8.1.0: + version "8.1.0" + resolved "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz" + integrity sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw== + dependencies: + find-cache-dir "^2.1.0" + loader-utils "^1.4.0" + mkdirp "^0.5.3" + pify "^4.0.1" + schema-utils "^2.6.5" + + babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== + dependencies: + object.assign "^4.1.0" + + babel-plugin-istanbul@^6.0.0: + 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== + 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" + + babel-plugin-jest-hoist@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz" + integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" + "@types/babel__traverse" "^7.0.6" + + babel-plugin-macros@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + + babel-plugin-named-asset-import@^0.3.7: + version "0.3.8" + resolved "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz" + integrity sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q== + + babel-plugin-polyfill-corejs2@^0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz" + integrity sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q== + dependencies: + "@babel/compat-data" "^7.17.7" + "@babel/helper-define-polyfill-provider" "^0.3.2" + semver "^6.1.1" + + babel-plugin-polyfill-corejs3@^0.5.3: + version "0.5.3" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz" + integrity sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.2" + core-js-compat "^3.21.0" + + babel-plugin-polyfill-regenerator@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.0.tgz" + integrity sha512-RW1cnryiADFeHmfLS+WW/G431p1PsW5qdRdz0SDRi7TKcUgc7Oh/uXkT7MZ/+tGsT1BkczEAmD5XjUyJ5SWDTw== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.2" + + babel-plugin-syntax-object-rest-spread@^6.8.0: + version "6.13.0" + resolved "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz" + integrity sha512-C4Aq+GaAj83pRQ0EFgTvw5YO6T3Qz2KGrNRwIj9mSoNHVvdZY4KO2uA6HNtNXCw993iSZnckY1aLW8nOi8i4+w== + + babel-plugin-transform-object-rest-spread@^6.26.0: + version "6.26.0" + resolved "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz" + integrity sha512-ocgA9VJvyxwt+qJB0ncxV8kb/CjfTcECUY4tQ5VT7nP6Aohzobm8CDFaQ5FHdvZQzLmf0sgDxB8iRXZXxwZcyA== + dependencies: + babel-plugin-syntax-object-rest-spread "^6.8.0" + babel-runtime "^6.26.0" + + babel-plugin-transform-react-remove-prop-types@^0.4.24: + version "0.4.24" + resolved "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz" + integrity sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA== + + babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@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-top-level-await" "^7.8.3" + + babel-preset-jest@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz" + integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== + dependencies: + babel-plugin-jest-hoist "^26.6.2" + babel-preset-current-node-syntax "^1.0.0" + + babel-preset-react-app@^10.0.0: + version "10.0.1" + resolved "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz" + integrity sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg== + dependencies: + "@babel/core" "^7.16.0" + "@babel/plugin-proposal-class-properties" "^7.16.0" + "@babel/plugin-proposal-decorators" "^7.16.4" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.16.0" + "@babel/plugin-proposal-numeric-separator" "^7.16.0" + "@babel/plugin-proposal-optional-chaining" "^7.16.0" + "@babel/plugin-proposal-private-methods" "^7.16.0" + "@babel/plugin-transform-flow-strip-types" "^7.16.0" + "@babel/plugin-transform-react-display-name" "^7.16.0" + "@babel/plugin-transform-runtime" "^7.16.4" + "@babel/preset-env" "^7.16.4" + "@babel/preset-react" "^7.16.0" + "@babel/preset-typescript" "^7.16.0" + "@babel/runtime" "^7.16.3" + babel-plugin-macros "^3.1.0" + babel-plugin-transform-react-remove-prop-types "^0.4.24" + + babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz" + integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g== + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + + babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz" + integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== + + balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + + base64-js@^1.0.2: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + + base@^0.11.1: + version "0.11.2" + resolved "https://registry.npmjs.org/base/-/base-0.11.2.tgz" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + + batch@0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz" + integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== + + bfj@^7.0.2: + version "7.0.2" + resolved "https://registry.npmjs.org/bfj/-/bfj-7.0.2.tgz" + integrity sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw== + dependencies: + bluebird "^3.5.5" + check-types "^11.1.1" + hoopy "^0.1.4" + tryer "^1.0.1" + + big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + + binary-extensions@^1.0.0: + version "1.13.1" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== + + binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + + bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + + bluebird@^3.5.5: + version "3.7.2" + resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + + bn.js@^5.0.0, bn.js@^5.1.1: + version "5.2.1" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + + body-parser@1.20.0: + version "1.20.0" + resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz" + integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== + 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.10.3" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" + + bonjour@^3.5.0: + version "3.5.0" + resolved "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz" + integrity sha512-RaVTblr+OnEli0r/ud8InrU7D+G0y6aJhlxaLa6Pwty4+xoxboF1BsUI45tujvRpbj9dQVoglChqonGAsjEBYg== + dependencies: + array-flatten "^2.1.0" + deep-equal "^1.0.1" + dns-equal "^1.0.0" + dns-txt "^2.0.2" + multicast-dns "^6.0.1" + multicast-dns-service-types "^1.1.0" + + boolbase@^1.0.0, boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + + brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + + braces@^2.3.1, braces@^2.3.2: + version "2.3.2" + resolved "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + + braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + + brorand@^1.0.1, brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + + browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + + browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.2.0" + resolved "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + + browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + + browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + + browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: + version "4.1.0" + resolved "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz" + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== + dependencies: + bn.js "^5.0.0" + randombytes "^2.0.1" + + browserify-sign@^4.0.0: + version "4.2.1" + resolved "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz" + integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== + dependencies: + bn.js "^5.1.1" + browserify-rsa "^4.0.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + elliptic "^6.5.3" + inherits "^2.0.4" + parse-asn1 "^5.1.5" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + + browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== + dependencies: + pako "~1.0.5" + + browserslist@4.14.2: + version "4.14.2" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.14.2.tgz" + integrity sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw== + dependencies: + caniuse-lite "^1.0.30001125" + electron-to-chromium "^1.3.564" + escalade "^3.0.2" + node-releases "^1.1.61" + + browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.20.2, browserslist@^4.21.3, browserslist@^4.6.2, browserslist@^4.6.4: + version "4.21.3" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz" + integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== + dependencies: + caniuse-lite "^1.0.30001370" + electron-to-chromium "^1.4.202" + node-releases "^2.0.6" + update-browserslist-db "^1.0.5" + + bser@2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + + buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + + buffer-indexof@^1.0.0: + version "1.1.1" + resolved "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz" + integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== + + buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz" + integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== + + buffer@^4.3.0: + version "4.9.2" + resolved "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + + builtin-modules@^3.1.0: + version "3.3.0" + resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + + builtin-status-codes@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz" + integrity sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ== + + bytes@3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz" + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + + bytes@3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + + cacache@^12.0.2: + version "12.0.4" + resolved "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz" + integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== + dependencies: + bluebird "^3.5.5" + chownr "^1.1.1" + figgy-pudding "^3.5.1" + glob "^7.1.4" + graceful-fs "^4.1.15" + infer-owner "^1.0.3" + lru-cache "^5.1.1" + mississippi "^3.0.0" + mkdirp "^0.5.1" + move-concurrently "^1.0.1" + promise-inflight "^1.0.1" + rimraf "^2.6.3" + ssri "^6.0.1" + unique-filename "^1.1.1" + y18n "^4.0.0" + + cacache@^15.0.5: + version "15.3.0" + resolved "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== + dependencies: + "@npmcli/fs" "^1.0.0" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + + cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + + call-bind@^1.0.0, call-bind@^1.0.2: + 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" + + caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz" + integrity sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ== + dependencies: + callsites "^2.0.0" + + caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz" + integrity sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A== + dependencies: + caller-callsite "^2.0.0" + + callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz" + integrity sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ== + + callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + + camel-case@^4.1.1: + version "4.1.2" + resolved "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== + dependencies: + pascal-case "^3.1.2" + tslib "^2.0.3" + + camelcase@5.3.1, camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + + camelcase@^6.0.0, camelcase@^6.1.0, camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + + caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + + caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001370: + version "1.0.30001397" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001397.tgz" + integrity sha512-SW9N2TbCdLf0eiNDRrrQXx2sOkaakNZbCjgNpPyMJJbiOrU5QzMIrXOVMRM1myBXTD5iTkdrtU/EguCrBocHlA== + + capture-exit@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz" + integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== + dependencies: + rsvp "^4.8.4" + + case-sensitive-paths-webpack-plugin@2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.3.0.tgz" + integrity sha512-/4YgnZS8y1UXXmC02xD5rRrBEu6T5ub+mQHLNRj0fzTRbgdBYhsNo2V5EqwgqrExjxsjtF/OpAKAMkKsxbD5XQ== + + chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + + chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + + char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + + check-types@^11.1.1: + version "11.1.2" + resolved "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz" + integrity sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ== + + chokidar@^2.1.8: + version "2.1.8" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.1" + braces "^2.3.2" + glob-parent "^3.1.0" + inherits "^2.0.3" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^3.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.2.1" + upath "^1.1.1" + optionalDependencies: + fsevents "^1.2.7" + + chokidar@^3.4.1: + version "3.5.3" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + 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" + optionalDependencies: + fsevents "~2.3.2" + + chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + + chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + + chrome-trace-event@^1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + + ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + + cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + + cjs-module-lexer@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz" + integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== + + class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + + classnames@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz" + integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== + + clean-css@^4.2.3: + version "4.2.4" + resolved "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz" + integrity sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A== + dependencies: + source-map "~0.6.0" + + clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + + cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + + cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + + co@^4.6.0: + version "4.6.0" + resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + + coa@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz" + integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== + dependencies: + "@types/q" "^1.5.1" + chalk "^2.4.1" + q "^1.1.2" + + collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + + collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz" + integrity sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw== + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + + color-convert@^1.9.0, color-convert@^1.9.3: + version "1.9.3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + + color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + + color-name@1.1.3, color-name@^1.0.0: + version "1.1.3" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + + color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + + color-string@^1.6.0: + version "1.9.1" + resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + + color@^3.0.0: + version "3.2.1" + resolved "https://registry.npmjs.org/color/-/color-3.2.1.tgz" + integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== + dependencies: + color-convert "^1.9.3" + color-string "^1.6.0" + + combined-stream@^1.0.8: + 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" + + commander@^2.20.0: + 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== + + commander@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + + common-tags@^1.8.0: + version "1.8.2" + resolved "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz" + integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== + + commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + + component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + + compose-function@3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/compose-function/-/compose-function-3.0.3.tgz" + integrity sha512-xzhzTJ5eC+gmIzvZq+C3kCJHsp9os6tJkrigDRZclyGtOKINbZtE8n1Tzmeh32jW+BUDPbvZpibwvJHBLGMVwg== + dependencies: + arity-n "^1.0.4" + + compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + + compression@^1.7.4: + version "1.7.4" + resolved "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + 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" + + concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + + concat-stream@^1.5.0: + version "1.6.2" + resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + + confusing-browser-globals@^1.0.10: + version "1.0.11" + resolved "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz" + integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== + + connect-history-api-fallback@^1.6.0: + version "1.6.0" + resolved "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz" + integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== + + console-browserify@^1.1.0: + version "1.2.0" + resolved "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz" + integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== + + constants-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz" + integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ== + + content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + + content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + + convert-source-map@1.7.0: + version "1.7.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + + convert-source-map@^0.3.3: + version "0.3.5" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz" + integrity sha512-+4nRk0k3oEpwUB7/CalD7xE2z4VmtEnnq0GO2IPTkrooTrAhEsWvuLF5iWP1dXrwluki/azwXV1ve7gtYuPldg== + + convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + + cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + + cookie@0.5.0: + version "0.5.0" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + + copy-concurrently@^1.0.0: + version "1.0.5" + resolved "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz" + integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== + dependencies: + aproba "^1.1.1" + fs-write-stream-atomic "^1.0.8" + iferr "^0.1.5" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.0" + + copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz" + integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== + + core-js-compat@^3.21.0, core-js-compat@^3.22.1: + version "3.25.1" + resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.25.1.tgz" + integrity sha512-pOHS7O0i8Qt4zlPW/eIFjwp+NrTPx+wTL0ctgI2fHn31sZOq89rDsmtc/A2vAX7r6shl+bmVI+678He46jgBlw== + dependencies: + browserslist "^4.21.3" + + core-js-pure@^3.20.2: + version "3.25.1" + resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.25.1.tgz" + integrity sha512-7Fr74bliUDdeJCBMxkkIuQ4xfxn/SwrVg+HkJUAoNEXVqYLv55l6Af0dJ5Lq2YBUW9yKqSkLXaS5SYPK6MGa/A== + + core-js@^2.4.0: + version "2.6.12" + resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz" + integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== + + core-js@^3.6.5: + version "3.25.1" + resolved "https://registry.npmjs.org/core-js/-/core-js-3.25.1.tgz" + integrity sha512-sr0FY4lnO1hkQ4gLDr24K0DGnweGO1QwSj5BpfQjpSJPdqWalja4cTps29Y/PJVG/P7FYlPDkH3hO+Tr0CvDgQ== + + core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + + cosmiconfig@^5.0.0: + version "5.2.1" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz" + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== + dependencies: + import-fresh "^2.0.0" + is-directory "^0.3.1" + js-yaml "^3.13.1" + parse-json "^4.0.0" + + cosmiconfig@^7.0.0: + version "7.0.1" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + 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" + + create-ecdh@^4.0.0: + version "4.0.4" + resolved "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz" + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== + dependencies: + bn.js "^4.1.0" + elliptic "^6.5.3" + + create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + + create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + + cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + + cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + + crypto-browserify@^3.11.0: + version "3.12.0" + resolved "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + + crypto-random-string@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz" + integrity sha512-GsVpkFPlycH7/fRR7Dhcmnoii54gV1nz7y4CWyeFS14N+JVBBhY+r8amRHE4BwSYal7BPTDp8isvAlCxyFt3Hg== + + css-blank-pseudo@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz" + integrity sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w== + dependencies: + postcss "^7.0.5" + + css-color-names@0.0.4, css-color-names@^0.0.4: + version "0.0.4" + resolved "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz" + integrity sha512-zj5D7X1U2h2zsXOAM8EyUREBnnts6H+Jm+d1M2DbiQQcUtnqgQsMrdo8JW9R80YFUmIdBZeMu5wvYM7hcgWP/Q== + + css-declaration-sorter@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz" + integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== + dependencies: + postcss "^7.0.1" + timsort "^0.3.0" + + css-has-pseudo@^0.10.0: + version "0.10.0" + resolved "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz" + integrity sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ== + dependencies: + postcss "^7.0.6" + postcss-selector-parser "^5.0.0-rc.4" + + css-loader@4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/css-loader/-/css-loader-4.3.0.tgz" + integrity sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg== + dependencies: + camelcase "^6.0.0" + cssesc "^3.0.0" + icss-utils "^4.1.1" + loader-utils "^2.0.0" + postcss "^7.0.32" + postcss-modules-extract-imports "^2.0.0" + postcss-modules-local-by-default "^3.0.3" + postcss-modules-scope "^2.2.0" + postcss-modules-values "^3.0.0" + postcss-value-parser "^4.1.0" + schema-utils "^2.7.1" + semver "^7.3.2" + + css-prefers-color-scheme@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz" + integrity sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg== + dependencies: + postcss "^7.0.5" + + css-select-base-adapter@^0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz" + integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== + + css-select@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz" + integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== + dependencies: + boolbase "^1.0.0" + css-what "^3.2.1" + domutils "^1.7.0" + nth-check "^1.0.2" + + css-select@^4.1.3: + version "4.3.0" + resolved "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz" + integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== + dependencies: + boolbase "^1.0.0" + css-what "^6.0.1" + domhandler "^4.3.1" + domutils "^2.8.0" + nth-check "^2.0.1" + + css-tree@1.0.0-alpha.37: + version "1.0.0-alpha.37" + resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz" + integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== + dependencies: + mdn-data "2.0.4" + source-map "^0.6.1" + + css-tree@^1.1.2: + version "1.1.3" + resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + + css-what@^3.2.1: + version "3.4.2" + resolved "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz" + integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== + + css-what@^6.0.1: + version "6.1.0" + resolved "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + + css@^2.0.0: + version "2.2.4" + resolved "https://registry.npmjs.org/css/-/css-2.2.4.tgz" + integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== + dependencies: + inherits "^2.0.3" + source-map "^0.6.1" + source-map-resolve "^0.5.2" + urix "^0.1.0" + + cssdb@^4.4.0: + version "4.4.0" + resolved "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz" + integrity sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ== + + cssesc@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz" + integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg== + + cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + + cssnano-preset-default@^4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz" + integrity sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ== + dependencies: + css-declaration-sorter "^4.0.1" + cssnano-util-raw-cache "^4.0.1" + postcss "^7.0.0" + postcss-calc "^7.0.1" + postcss-colormin "^4.0.3" + postcss-convert-values "^4.0.1" + postcss-discard-comments "^4.0.2" + postcss-discard-duplicates "^4.0.2" + postcss-discard-empty "^4.0.1" + postcss-discard-overridden "^4.0.1" + postcss-merge-longhand "^4.0.11" + postcss-merge-rules "^4.0.3" + postcss-minify-font-values "^4.0.2" + postcss-minify-gradients "^4.0.2" + postcss-minify-params "^4.0.2" + postcss-minify-selectors "^4.0.2" + postcss-normalize-charset "^4.0.1" + postcss-normalize-display-values "^4.0.2" + postcss-normalize-positions "^4.0.2" + postcss-normalize-repeat-style "^4.0.2" + postcss-normalize-string "^4.0.2" + postcss-normalize-timing-functions "^4.0.2" + postcss-normalize-unicode "^4.0.1" + postcss-normalize-url "^4.0.1" + postcss-normalize-whitespace "^4.0.2" + postcss-ordered-values "^4.1.2" + postcss-reduce-initial "^4.0.3" + postcss-reduce-transforms "^4.0.2" + postcss-svgo "^4.0.3" + postcss-unique-selectors "^4.0.1" + + cssnano-util-get-arguments@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz" + integrity sha512-6RIcwmV3/cBMG8Aj5gucQRsJb4vv4I4rn6YjPbVWd5+Pn/fuG+YseGvXGk00XLkoZkaj31QOD7vMUpNPC4FIuw== + + cssnano-util-get-match@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz" + integrity sha512-JPMZ1TSMRUPVIqEalIBNoBtAYbi8okvcFns4O0YIhcdGebeYZK7dMyHJiQ6GqNBA9kE0Hym4Aqym5rPdsV/4Cw== + + cssnano-util-raw-cache@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz" + integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== + dependencies: + postcss "^7.0.0" + + cssnano-util-same-parent@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz" + integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== + + cssnano@^4.1.10: + version "4.1.11" + resolved "https://registry.npmjs.org/cssnano/-/cssnano-4.1.11.tgz" + integrity sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g== + dependencies: + cosmiconfig "^5.0.0" + cssnano-preset-default "^4.0.8" + is-resolvable "^1.0.0" + postcss "^7.0.0" + + csso@^4.0.2: + version "4.2.0" + resolved "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== + dependencies: + css-tree "^1.1.2" + + cssom@^0.4.4: + version "0.4.4" + resolved "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== + + cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + + cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + + csstype@^3.0.10: + version "3.1.1" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" + integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== + + csstype@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz" + integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA== + + cyclist@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz" + integrity sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A== + + d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/d/-/d-1.0.1.tgz" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + + damerau-levenshtein@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz" + integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== + + data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz" + integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== + dependencies: + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + + debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + + debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@~4.3.1, debug@~4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + + debug@^3.2.7: + version "3.2.7" + resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + + decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + + decimal.js@^10.2.1: + version "10.4.0" + resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz" + integrity sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg== + + decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz" + integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== + + dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz" + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== + + deep-equal@^1.0.1: + version "1.1.1" + resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz" + integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" + + deep-is@^0.1.3, deep-is@~0.1.3: + 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== + + deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + + default-gateway@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz" + integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA== + dependencies: + execa "^1.0.0" + ip-regex "^2.1.0" + + define-properties@^1.1.2, define-properties@^1.1.3, define-properties@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz" + integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== + dependencies: + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + + define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz" + integrity sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA== + dependencies: + is-descriptor "^0.1.0" + + define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz" + integrity sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA== + dependencies: + is-descriptor "^1.0.0" + + define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + + del@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/del/-/del-4.1.1.tgz" + integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== + dependencies: + "@types/glob" "^7.1.1" + globby "^6.1.0" + is-path-cwd "^2.0.0" + is-path-in-cwd "^2.0.0" + p-map "^2.0.0" + pify "^4.0.1" + rimraf "^2.6.3" + + delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + + depd@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + + depd@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + + dequal@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + + des.js@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + + destroy@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + + detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + + detect-node@^2.0.4: + version "2.1.0" + resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + + detect-port-alt@1.1.6: + version "1.1.6" + resolved "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz" + integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== + dependencies: + address "^1.0.1" + debug "^2.6.0" + + diff-sequences@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz" + integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== + + diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + + dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + + dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz" + integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== + + dns-packet@^1.3.1: + version "1.3.4" + resolved "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz" + integrity sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA== + dependencies: + ip "^1.1.0" + safe-buffer "^5.0.1" + + dns-txt@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz" + integrity sha512-Ix5PrWjphuSoUXV/Zv5gaFHjnaJtb02F2+Si3Ht9dyJ87+Z/lMmy+dpNHtTGraNK958ndXq2i+GLkWsWHcKaBQ== + dependencies: + buffer-indexof "^1.0.0" + + doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + + doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + + dom-converter@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz" + integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== + dependencies: + utila "~0.4" + + dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1: + version "5.2.1" + resolved "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + + dom-serializer@0: + version "0.2.2" + resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + + dom-serializer@^1.0.1: + version "1.4.1" + resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz" + integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + + domain-browser@^1.1.1: + version "1.2.0" + resolved "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz" + integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== + + domelementtype@1: + version "1.3.1" + resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + + domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.3.0" + resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + + domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz" + integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== + dependencies: + webidl-conversions "^5.0.0" + + domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: + version "4.3.1" + resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== + dependencies: + domelementtype "^2.2.0" + + domutils@^1.7.0: + version "1.7.0" + resolved "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + + domutils@^2.5.2, domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + + dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + + dot-prop@^5.2.0: + version "5.3.0" + resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + + dotenv-expand@5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + + dotenv@8.2.0: + version "8.2.0" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + + duplexer@^0.1.1: + version "0.1.2" + resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + + duplexify@^3.4.2, duplexify@^3.6.0: + version "3.7.1" + resolved "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + + ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + + ejs@^2.6.1: + version "2.7.4" + resolved "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz" + integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== + + electron-to-chromium@^1.3.564, electron-to-chromium@^1.4.202: + version "1.4.247" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.247.tgz" + integrity sha512-FLs6R4FQE+1JHM0hh3sfdxnYjKvJpHZyhQDjc2qFq/xFvmmRt/TATNToZhrcGUFzpF2XjeiuozrA8lI0PZmYYw== + + elliptic@^6.5.3: + version "6.5.4" + resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + + emittery@^0.7.1: + version "0.7.2" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz" + integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== + + emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + + emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + + emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + + emojis-list@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz" + integrity sha512-knHEZMgs8BB+MInokmNTg/OyPlAddghe1YBgNwJBc5zsJi/uyIcXoSDsL/W9ymOsBoBGdPIHXYJ9+qKFwRwDng== + + emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + + encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + + end-of-stream@^1.0.0, end-of-stream@^1.1.0: + 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== + dependencies: + once "^1.4.0" + + engine.io-client@~6.2.1: + version "6.2.3" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.2.3.tgz#a8cbdab003162529db85e9de31575097f6d29458" + integrity sha512-aXPtgF1JS3RuuKcpSrBtimSjYvrbhKW9froICH4s0F3XQWLxsKNxqzG39nnvQZQnva4CMvUK63T7shevxRyYHw== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + engine.io-parser "~5.0.3" + ws "~8.2.3" + xmlhttprequest-ssl "~2.0.0" + + engine.io-parser@~5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.4.tgz#0b13f704fa9271b3ec4f33112410d8f3f41d0fc0" + integrity sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg== + + enhanced-resolve@^4.3.0: + version "4.5.0" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz" + integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + + enquirer@^2.3.5: + version "2.3.6" + resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + + entities@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + + errno@^0.1.3, errno@~0.1.7: + version "0.1.8" + resolved "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== + dependencies: + prr "~1.0.1" + + error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + + error-stack-parser@^2.0.6: + version "2.1.4" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz#229cb01cdbfa84440bfa91876285b94680188286" + integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== + dependencies: + stackframe "^1.3.4" + + es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5, es-abstract@^1.20.1: + version "1.20.2" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.2.tgz" + integrity sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + function.prototype.name "^1.1.5" + get-intrinsic "^1.1.2" + get-symbol-description "^1.0.0" + has "^1.0.3" + has-property-descriptors "^1.0.0" + has-symbols "^1.0.3" + internal-slot "^1.0.3" + is-callable "^1.2.4" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-weakref "^1.0.2" + object-inspect "^1.12.2" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.4.3" + string.prototype.trimend "^1.0.5" + string.prototype.trimstart "^1.0.5" + unbox-primitive "^1.0.2" + + es-array-method-boxes-properly@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz" + integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== + + es-shim-unscopables@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz" + integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== + dependencies: + has "^1.0.3" + + es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + + es5-ext@^0.10.35, es5-ext@^0.10.50: + version "0.10.62" + resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz" + integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + next-tick "^1.1.0" + + es6-iterator@2.0.3, es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + + es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + + escalade@^3.0.2, escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + + escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + + escape-string-regexp@2.0.0, escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + + escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + + escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + + escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + + eslint-config-react-app@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz" + integrity sha512-bpoAAC+YRfzq0dsTk+6v9aHm/uqnDwayNAXleMypGl6CpxI9oXXscVHo4fk3eJPIn+rsbtNetB4r/ZIidFIE8A== + dependencies: + confusing-browser-globals "^1.0.10" + + eslint-import-resolver-node@^0.3.6: + version "0.3.6" + resolved "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz" + integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== + dependencies: + debug "^3.2.7" + resolve "^1.20.0" + + eslint-module-utils@^2.7.3: + version "2.7.4" + resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz" + integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== + dependencies: + debug "^3.2.7" + + eslint-plugin-flowtype@^5.2.0: + version "5.10.0" + resolved "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-5.10.0.tgz" + integrity sha512-vcz32f+7TP+kvTUyMXZmCnNujBQZDNmcqPImw8b9PZ+16w1Qdm6ryRuYZYVaG9xRqqmAPr2Cs9FAX5gN+x/bjw== + dependencies: + lodash "^4.17.15" + string-natural-compare "^3.0.1" + + eslint-plugin-import@^2.22.1: + version "2.26.0" + resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz" + integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== + dependencies: + array-includes "^3.1.4" + array.prototype.flat "^1.2.5" + debug "^2.6.9" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.6" + eslint-module-utils "^2.7.3" + has "^1.0.3" + is-core-module "^2.8.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.values "^1.1.5" + resolve "^1.22.0" + tsconfig-paths "^3.14.1" + + eslint-plugin-jest@^24.1.0: + version "24.7.0" + resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.7.0.tgz" + integrity sha512-wUxdF2bAZiYSKBclsUMrYHH6WxiBreNjyDxbRv345TIvPeoCEgPNEn3Sa+ZrSqsf1Dl9SqqSREXMHExlMMu1DA== + dependencies: + "@typescript-eslint/experimental-utils" "^4.0.1" + + eslint-plugin-jsx-a11y@^6.3.1: + version "6.6.1" + resolved "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz" + integrity sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q== + dependencies: + "@babel/runtime" "^7.18.9" + aria-query "^4.2.2" + array-includes "^3.1.5" + ast-types-flow "^0.0.7" + axe-core "^4.4.3" + axobject-query "^2.2.0" + damerau-levenshtein "^1.0.8" + emoji-regex "^9.2.2" + has "^1.0.3" + jsx-ast-utils "^3.3.2" + language-tags "^1.0.5" + minimatch "^3.1.2" + semver "^6.3.0" + + eslint-plugin-react-hooks@^4.2.0: + version "4.6.0" + resolved "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz" + integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== + + eslint-plugin-react@^7.21.5: + version "7.31.8" + resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.8.tgz" + integrity sha512-5lBTZmgQmARLLSYiwI71tiGVTLUuqXantZM6vlSY39OaDSV0M7+32K5DnLkmFrwTe+Ksz0ffuLUC91RUviVZfw== + dependencies: + array-includes "^3.1.5" + array.prototype.flatmap "^1.3.0" + doctrine "^2.1.0" + estraverse "^5.3.0" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.5" + object.fromentries "^2.0.5" + object.hasown "^1.1.1" + object.values "^1.1.5" + prop-types "^15.8.1" + resolve "^2.0.0-next.3" + semver "^6.3.0" + string.prototype.matchall "^4.0.7" + + eslint-plugin-testing-library@^3.9.2: + version "3.10.2" + resolved "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.10.2.tgz" + integrity sha512-WAmOCt7EbF1XM8XfbCKAEzAPnShkNSwcIsAD2jHdsMUT9mZJPjLCG7pMzbcC8kK366NOuGip8HKLDC+Xk4yIdA== + dependencies: + "@typescript-eslint/experimental-utils" "^3.10.1" + + eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + + eslint-scope@^5.0.0, eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + + eslint-utils@^2.0.0, eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + + eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + + eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + + eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + + eslint-webpack-plugin@^2.5.2: + version "2.7.0" + resolved "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-2.7.0.tgz" + integrity sha512-bNaVVUvU4srexGhVcayn/F4pJAz19CWBkKoMx7aSQ4wtTbZQCnG5O9LHCE42mM+JSKOUp7n6vd5CIwzj7lOVGA== + dependencies: + "@types/eslint" "^7.29.0" + arrify "^2.0.1" + jest-worker "^27.5.1" + micromatch "^4.0.5" + normalize-path "^3.0.0" + schema-utils "^3.1.1" + + eslint@^7.11.0: + version "7.32.0" + resolved "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz" + integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== + dependencies: + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.3" + "@humanwhocodes/config-array" "^0.5.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + escape-string-regexp "^4.0.0" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.1.2" + globals "^13.6.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^6.0.9" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + + espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" + + esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + + esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + + esrecurse@^4.1.0, esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + + estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + + estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: + version "5.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + + estree-walker@^0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz" + integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== + + estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + + esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + + etag@~1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + + eventemitter3@^4.0.0, eventemitter3@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + + events@^3.0.0: + version "3.3.0" + resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + + eventsource@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz" + integrity sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA== + + evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + + exec-sh@^0.3.2: + version "0.3.6" + resolved "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz" + integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== + + execa@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + + execa@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + + exit@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + + expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz" + integrity sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA== + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + + expect@^26.6.0, expect@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz" + integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== + dependencies: + "@jest/types" "^26.6.2" + ansi-styles "^4.0.0" + jest-get-type "^26.3.0" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-regex-util "^26.0.0" + + express@^4.17.1: + version "4.18.1" + resolved "https://registry.npmjs.org/express/-/express-4.18.1.tgz" + integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.0" + 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.10.3" + 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" + + ext@^1.1.2: + version "1.7.0" + resolved "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + + extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz" + integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== + dependencies: + is-extendable "^0.1.0" + + extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz" + integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + + extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + 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== + + fast-glob@^3.1.1, fast-glob@^3.2.9: + version "3.2.12" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz" + integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== + 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" + + fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + 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== + + fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + + fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + + faye-websocket@^0.11.3, faye-websocket@^0.11.4: + version "0.11.4" + resolved "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + + fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + + figgy-pudding@^3.5.1: + version "3.5.2" + resolved "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz" + integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== + + file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + + file-loader@6.1.1: + version "6.1.1" + resolved "https://registry.npmjs.org/file-loader/-/file-loader-6.1.1.tgz" + integrity sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + + file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + + filesize@6.1.0: + version "6.1.0" + resolved "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz" + integrity sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg== + + fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz" + integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ== + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + + fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + + finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + 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" + + find-cache-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz" + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== + dependencies: + commondir "^1.0.1" + make-dir "^2.0.0" + pkg-dir "^3.0.0" + + find-cache-dir@^3.3.1: + 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== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + + find-up@4.1.0, find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + + find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + + flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + + flatted@^3.1.0: + version "3.2.7" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz" + integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== + + flatten@^1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz" + integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== + + flush-write-stream@^1.0.0: + version "1.1.1" + resolved "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz" + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== + dependencies: + inherits "^2.0.3" + readable-stream "^2.3.6" + + follow-redirects@^1.0.0, follow-redirects@^1.14.9: + version "1.15.1" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz" + integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== + + for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz" + integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== + + fork-ts-checker-webpack-plugin@4.1.6: + version "4.1.6" + resolved "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz" + integrity sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw== + dependencies: + "@babel/code-frame" "^7.5.5" + chalk "^2.4.1" + micromatch "^3.1.10" + minimatch "^3.0.4" + semver "^5.6.0" + tapable "^1.0.0" + worker-rpc "^0.1.0" + + form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + + form-data@^4.0.0: + 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" + + forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + + fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz" + integrity sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA== + dependencies: + map-cache "^0.2.2" + + fresh@0.5.2: + version "0.5.2" + resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + + from2@^2.1.0: + version "2.3.0" + resolved "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz" + integrity sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g== + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.0" + + fs-extra@^7.0.0: + version "7.0.1" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + + fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + + fs-extra@^9.0.1: + version "9.1.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + + fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + + fs-write-stream-atomic@^1.0.8: + version "1.0.10" + resolved "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz" + integrity sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA== + dependencies: + graceful-fs "^4.1.2" + iferr "^0.1.5" + imurmurhash "^0.1.4" + readable-stream "1 || 2" + + fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + + fsevents@^1.2.7: + version "1.2.13" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz" + integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== + dependencies: + bindings "^1.5.0" + nan "^2.12.1" + + fsevents@^2.1.2, fsevents@^2.1.3, fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + + function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + + function.prototype.name@^1.1.5: + version "1.1.5" + resolved "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz" + integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + functions-have-names "^1.2.2" + + functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz" + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== + + functions-have-names@^1.2.2: + version "1.2.3" + resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + + gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: + 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== + + get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + + get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz" + integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + + get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== + + get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + + get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + + get-stream@^5.0.0: + version "5.2.0" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + + get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + + get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz" + integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== + + glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz" + integrity sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA== + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + + glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + + glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + 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" + + global-modules@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + + global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + + globals@^11.1.0: + version "11.12.0" + resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + + globals@^13.6.0, globals@^13.9.0: + version "13.17.0" + resolved "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz" + integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== + dependencies: + type-fest "^0.20.2" + + globby@11.0.1: + version "11.0.1" + resolved "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz" + integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + + globby@^11.0.3: + version "11.1.0" + resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + 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" + + globby@^6.1.0: + version "6.1.0" + resolved "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz" + integrity sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw== + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: + version "4.2.10" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + + growly@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz" + integrity sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw== + + gzip-size@5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz" + integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== + dependencies: + duplexer "^0.1.1" + pify "^4.0.1" + + handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + + harmony-reflect@^1.4.6: + version "1.6.2" + resolved "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz" + integrity sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g== + + has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + + has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + + has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + + has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + + has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + + has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + + has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz" + integrity sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q== + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + + has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz" + integrity sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw== + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + + has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz" + integrity sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ== + + has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz" + integrity sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ== + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + + has@^1.0.0, has@^1.0.3: + 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" + + hash-base@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== + dependencies: + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + + hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + + he@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + + hex-color-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz" + integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== + + hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + + hoopy@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz" + integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ== + + hosted-git-info@^2.1.4: + 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== + + hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz" + integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + + hsl-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz" + integrity sha512-M5ezZw4LzXbBKMruP+BNANf0k+19hDQMgpzBIYnya//Al+fjNct9Wf3b1WedLqdEs2hKBvxq/jh+DsHJLj0F9A== + + hsla-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz" + integrity sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA== + + html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz" + integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== + dependencies: + whatwg-encoding "^1.0.5" + + html-entities@^1.2.1, html-entities@^1.3.1: + version "1.4.0" + resolved "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz" + integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA== + + html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + + html-minifier-terser@^5.0.1: + version "5.1.1" + resolved "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz" + integrity sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg== + dependencies: + camel-case "^4.1.1" + clean-css "^4.2.3" + commander "^4.1.1" + he "^1.2.0" + param-case "^3.0.3" + relateurl "^0.2.7" + terser "^4.6.3" + + html-webpack-plugin@4.5.0: + version "4.5.0" + resolved "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz" + integrity sha512-MouoXEYSjTzCrjIxWwg8gxL5fE2X2WZJLmBYXlaJhQUH5K/b5OrqmV7T4dB7iu0xkmJ6JlUuV6fFVtnqbPopZw== + dependencies: + "@types/html-minifier-terser" "^5.0.0" + "@types/tapable" "^1.0.5" + "@types/webpack" "^4.41.8" + html-minifier-terser "^5.0.1" + loader-utils "^1.2.3" + lodash "^4.17.15" + pretty-error "^2.1.1" + tapable "^1.1.3" + util.promisify "1.0.0" + + htmlparser2@^6.1.0: + version "6.1.0" + resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.5.2" + entities "^2.0.0" + + http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz" + integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== + + http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + + http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz" + integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + + http-parser-js@>=0.5.1: + version "0.5.8" + resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz" + integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== + + http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + + http-proxy-middleware@0.19.1: + version "0.19.1" + resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz" + integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== + dependencies: + http-proxy "^1.17.0" + is-glob "^4.0.0" + lodash "^4.17.11" + micromatch "^3.1.10" + + http-proxy@^1.17.0: + version "1.18.1" + resolved "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + + https-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz" + integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg== + + https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + + human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + + iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + + icss-utils@^4.0.0, icss-utils@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz" + integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== + dependencies: + postcss "^7.0.14" + + identity-obj-proxy@3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz" + integrity sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA== + dependencies: + harmony-reflect "^1.4.6" + + ieee754@^1.1.4: + version "1.2.1" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + + iferr@^0.1.5: + version "0.1.5" + resolved "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz" + integrity sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA== + + ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + + ignore@^5.1.4, ignore@^5.1.8, ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + + immer@8.0.1: + version "8.0.1" + resolved "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz" + integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA== + + import-cwd@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz" + integrity sha512-Ew5AZzJQFqrOV5BTW3EIoHAnoie1LojZLXKcCQ/yTRyVZosBhK1x1ViYjHGf5pAFOq8ZyChZp6m/fSN7pJyZtg== + dependencies: + import-from "^2.1.0" + + import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz" + integrity sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg== + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + + import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + + import-from@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz" + integrity sha512-0vdnLL2wSGnhlRmzHJAg5JHjt1l2vYhzJ7tNLGbeVg0fse56tpGaH0uzH+r9Slej+BSXXEHvBKDEnVSLLE9/+w== + dependencies: + resolve-from "^3.0.0" + + import-local@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz" + integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== + dependencies: + pkg-dir "^3.0.0" + resolve-cwd "^2.0.0" + + import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + + imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + + indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + + indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz" + integrity sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA== + + infer-owner@^1.0.3, infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + + inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + + inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + + inherits@2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + integrity sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA== + + inherits@2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== + + ini@^1.3.5: + version "1.3.8" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + + internal-ip@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz" + integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg== + dependencies: + default-gateway "^4.2.0" + ipaddr.js "^1.9.0" + + internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + + invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + + ip-regex@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz" + integrity sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw== + + ip@^1.1.0, ip@^1.1.5: + version "1.1.8" + resolved "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz" + integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg== + + ipaddr.js@1.9.1, ipaddr.js@^1.9.0: + 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== + + is-absolute-url@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz" + integrity sha512-vOx7VprsKyllwjSkLV79NIhpyLfr3jAp7VaTCMXOJHu4m0Ew1CZ2fcjASwmV1jI3BWuWHB013M48eyeldk9gYg== + + is-absolute-url@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz" + integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== + + is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz" + integrity sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A== + dependencies: + kind-of "^3.0.2" + + is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + + is-arguments@^1.0.4: + version "1.1.1" + resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + + is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + + is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + + is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + + is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz" + integrity sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q== + dependencies: + binary-extensions "^1.0.0" + + is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + + is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + + is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + + is-callable@^1.1.4, is-callable@^1.2.4: + version "1.2.5" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.5.tgz" + integrity sha512-ZIWRujF6MvYGkEuHMYtFRkL2wAtFw89EHfKlXrkPkjQZZRWeh9L1q3SV13NIfHnqxugjLvAOkEHx9mb1zcMnEw== + + is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + + is-color-stop@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz" + integrity sha512-H1U8Vz0cfXNujrJzEcvvwMDW9Ra+biSYA3ThdQvAnMLJkEHQXn6bWzLkxHtVYJ+Sdbx0b6finn3jZiaVe7MAHA== + dependencies: + css-color-names "^0.0.4" + hex-color-regex "^1.1.0" + hsl-regex "^1.0.0" + hsla-regex "^1.0.0" + rgb-regex "^1.0.1" + rgba-regex "^1.0.0" + + is-core-module@^2.0.0, is-core-module@^2.8.1, is-core-module@^2.9.0: + version "2.10.0" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz" + integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== + dependencies: + has "^1.0.3" + + is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz" + integrity sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg== + dependencies: + kind-of "^3.0.2" + + is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + + is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + + is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + + is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + + is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz" + integrity sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw== + + is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + + is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz" + integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== + + is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + + is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + + is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz" + integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== + + is-fullwidth-code-point@^3.0.0: + 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== + + is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + + is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz" + integrity sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw== + dependencies: + is-extglob "^2.1.0" + + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + + is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz" + integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== + + is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + + is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + + is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz" + integrity sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg== + dependencies: + kind-of "^3.0.2" + + is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + + is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz" + integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== + + is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + + is-path-cwd@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + + is-path-in-cwd@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz" + integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== + dependencies: + is-path-inside "^2.1.0" + + is-path-inside@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz" + integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== + dependencies: + path-is-inside "^1.0.2" + + is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" + integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== + + is-plain-object@^2.0.3, is-plain-object@^2.0.4: + 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== + dependencies: + isobject "^3.0.1" + + is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + + is-regex@^1.0.4, is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + + is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz" + integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA== + + is-resolvable@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz" + integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== + + is-root@2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz" + integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== + + is-shared-array-buffer@^1.0.2: + 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== + dependencies: + call-bind "^1.0.2" + + is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz" + integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== + + is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + + is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + + is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + + is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + + is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + + is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + + is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz" + integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw== + + is-wsl@^2.1.1, is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + + isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + + isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + + isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz" + integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA== + dependencies: + isarray "1.0.0" + + isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + + istanbul-lib-instrument@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz" + integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== + dependencies: + "@babel/core" "^7.7.5" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + + istanbul-lib-instrument@^5.0.4: + version "5.2.0" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz" + integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== + 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" + + istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + + istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + + istanbul-reports@^3.0.2: + version "3.1.5" + resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz" + integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + + jest-changed-files@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz" + integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== + dependencies: + "@jest/types" "^26.6.2" + execa "^4.0.0" + throat "^5.0.0" + + jest-circus@26.6.0: + version "26.6.0" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-26.6.0.tgz" + integrity sha512-L2/Y9szN6FJPWFK8kzWXwfp+FOR7xq0cUL4lIsdbIdwz3Vh6P1nrpcqOleSzr28zOtSHQNV9Z7Tl+KkuK7t5Ng== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^26.6.0" + "@jest/test-result" "^26.6.0" + "@jest/types" "^26.6.0" + "@types/babel__traverse" "^7.0.4" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + expect "^26.6.0" + is-generator-fn "^2.0.0" + jest-each "^26.6.0" + jest-matcher-utils "^26.6.0" + jest-message-util "^26.6.0" + jest-runner "^26.6.0" + jest-runtime "^26.6.0" + jest-snapshot "^26.6.0" + jest-util "^26.6.0" + pretty-format "^26.6.0" + stack-utils "^2.0.2" + throat "^5.0.0" + + jest-cli@^26.6.0: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz" + integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== + dependencies: + "@jest/core" "^26.6.3" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + import-local "^3.0.2" + is-ci "^2.0.0" + jest-config "^26.6.3" + jest-util "^26.6.2" + jest-validate "^26.6.2" + prompts "^2.0.1" + yargs "^15.4.1" + + jest-config@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz" + integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^26.6.3" + "@jest/types" "^26.6.2" + babel-jest "^26.6.3" + chalk "^4.0.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.4" + jest-environment-jsdom "^26.6.2" + jest-environment-node "^26.6.2" + jest-get-type "^26.3.0" + jest-jasmine2 "^26.6.3" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + micromatch "^4.0.2" + pretty-format "^26.6.2" + + jest-diff@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz" + integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== + dependencies: + chalk "^4.0.0" + diff-sequences "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + + jest-docblock@^26.0.0: + version "26.0.0" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz" + integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w== + dependencies: + detect-newline "^3.0.0" + + jest-each@^26.6.0, jest-each@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz" + integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== + dependencies: + "@jest/types" "^26.6.2" + chalk "^4.0.0" + jest-get-type "^26.3.0" + jest-util "^26.6.2" + pretty-format "^26.6.2" + + jest-environment-jsdom@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz" + integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + jest-util "^26.6.2" + jsdom "^16.4.0" + + jest-environment-node@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz" + integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + jest-util "^26.6.2" + + jest-get-type@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz" + integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== + + jest-haste-map@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz" + integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== + dependencies: + "@jest/types" "^26.6.2" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.4" + jest-regex-util "^26.0.0" + jest-serializer "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" + micromatch "^4.0.2" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.1.2" + + jest-jasmine2@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz" + integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + expect "^26.6.2" + is-generator-fn "^2.0.0" + jest-each "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + pretty-format "^26.6.2" + throat "^5.0.0" + + jest-leak-detector@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz" + integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== + dependencies: + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + + jest-matcher-utils@^26.6.0, jest-matcher-utils@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz" + integrity sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw== + dependencies: + chalk "^4.0.0" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + + jest-message-util@^26.6.0, jest-message-util@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz" + integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/types" "^26.6.2" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + micromatch "^4.0.2" + pretty-format "^26.6.2" + slash "^3.0.0" + stack-utils "^2.0.2" + + jest-mock@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz" + integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + + jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + + jest-regex-util@^26.0.0: + version "26.0.0" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz" + integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== + + jest-resolve-dependencies@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz" + integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== + dependencies: + "@jest/types" "^26.6.2" + jest-regex-util "^26.0.0" + jest-snapshot "^26.6.2" + + jest-resolve@26.6.0: + version "26.6.0" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.0.tgz" + integrity sha512-tRAz2bwraHufNp+CCmAD8ciyCpXCs1NQxB5EJAmtCFy6BN81loFEGWKzYu26Y62lAJJe4X4jg36Kf+NsQyiStQ== + dependencies: + "@jest/types" "^26.6.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + jest-pnp-resolver "^1.2.2" + jest-util "^26.6.0" + read-pkg-up "^7.0.1" + resolve "^1.17.0" + slash "^3.0.0" + + jest-resolve@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz" + integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== + dependencies: + "@jest/types" "^26.6.2" + chalk "^4.0.0" + graceful-fs "^4.2.4" + jest-pnp-resolver "^1.2.2" + jest-util "^26.6.2" + read-pkg-up "^7.0.1" + resolve "^1.18.1" + slash "^3.0.0" + + jest-runner@^26.6.0, jest-runner@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz" + integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== + dependencies: + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.7.1" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-config "^26.6.3" + jest-docblock "^26.0.0" + jest-haste-map "^26.6.2" + jest-leak-detector "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + jest-runtime "^26.6.3" + jest-util "^26.6.2" + jest-worker "^26.6.2" + source-map-support "^0.5.6" + throat "^5.0.0" + + jest-runtime@^26.6.0, jest-runtime@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz" + integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== + dependencies: + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/globals" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + cjs-module-lexer "^0.6.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.4" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + slash "^3.0.0" + strip-bom "^4.0.0" + yargs "^15.4.1" + + jest-serializer@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz" + integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.4" + + jest-snapshot@^26.6.0, jest-snapshot@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz" + integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^26.6.2" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.0.0" + chalk "^4.0.0" + expect "^26.6.2" + graceful-fs "^4.2.4" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + jest-haste-map "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + natural-compare "^1.4.0" + pretty-format "^26.6.2" + semver "^7.3.2" + + jest-util@^26.6.0, jest-util@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz" + integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + micromatch "^4.0.2" + + jest-validate@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz" + integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== + dependencies: + "@jest/types" "^26.6.2" + camelcase "^6.0.0" + chalk "^4.0.0" + jest-get-type "^26.3.0" + leven "^3.1.0" + pretty-format "^26.6.2" + + jest-watch-typeahead@0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-0.6.1.tgz" + integrity sha512-ITVnHhj3Jd/QkqQcTqZfRgjfyRhDFM/auzgVo2RKvSwi18YMvh0WvXDJFoFED6c7jd/5jxtu4kSOb9PTu2cPVg== + dependencies: + ansi-escapes "^4.3.1" + chalk "^4.0.0" + jest-regex-util "^26.0.0" + jest-watcher "^26.3.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + + jest-watcher@^26.3.0, jest-watcher@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz" + integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== + dependencies: + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + jest-util "^26.6.2" + string-length "^4.0.1" + + jest-worker@^24.9.0: + version "24.9.0" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz" + integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw== + dependencies: + merge-stream "^2.0.0" + supports-color "^6.1.0" + + jest-worker@^26.5.0, jest-worker@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + + jest-worker@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + + jest@26.6.0: + version "26.6.0" + resolved "https://registry.npmjs.org/jest/-/jest-26.6.0.tgz" + integrity sha512-jxTmrvuecVISvKFFhOkjsWRZV7sFqdSUAd1ajOKY+/QE/aLBVstsJ/dX8GczLzwiT6ZEwwmZqtCUHLHHQVzcfA== + dependencies: + "@jest/core" "^26.6.0" + import-local "^3.0.2" + jest-cli "^26.6.0" + + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + + js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + + jsdom@^16.4.0: + version "16.7.0" + resolved "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz" + integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== + dependencies: + abab "^2.0.5" + acorn "^8.2.4" + acorn-globals "^6.0.0" + cssom "^0.4.4" + cssstyle "^2.3.0" + data-urls "^2.0.0" + decimal.js "^10.2.1" + domexception "^2.0.1" + escodegen "^2.0.0" + form-data "^3.0.0" + html-encoding-sniffer "^2.0.1" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.0" + parse5 "6.0.1" + saxes "^5.0.1" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^2.0.0" + webidl-conversions "^6.1.0" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.5.0" + ws "^7.4.6" + xml-name-validator "^3.0.0" + + jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + + jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== + + json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: + 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== + + json-parse-even-better-errors@^2.3.0: + 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== + + json-schema-traverse@^0.4.1: + 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== + + json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + + json-stable-stringify-without-jsonify@^1.0.1: + 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== + + json5@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + + json5@^2.1.2, json5@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + + jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + + jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + + "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.2: + version "3.3.3" + resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz" + integrity sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw== + dependencies: + array-includes "^3.1.5" + object.assign "^4.1.3" + + killable@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz" + integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== + + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz" + integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== + dependencies: + is-buffer "^1.1.5" + + kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz" + integrity sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw== + dependencies: + is-buffer "^1.1.5" + + kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + + kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + + kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + + klona@^2.0.4: + version "2.0.5" + resolved "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz" + integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== + + language-subtag-registry@~0.3.2: + version "0.3.22" + resolved "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz" + integrity sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w== + + language-tags@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz" + integrity sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ== + dependencies: + language-subtag-registry "~0.3.2" + + last-call-webpack-plugin@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz" + integrity sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w== + dependencies: + lodash "^4.17.5" + webpack-sources "^1.1.0" + + leven@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + + levn@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + + levn@~0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz" + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + + lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + + loader-runner@^2.4.0: + version "2.4.0" + resolved "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz" + integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== + + loader-utils@1.2.3: + version "1.2.3" + resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz" + integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== + dependencies: + big.js "^5.2.2" + emojis-list "^2.0.0" + json5 "^1.0.1" + + loader-utils@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz" + integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + + loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + + loader-utils@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz" + integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + + locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + + locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + + lodash._reinterpolate@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz" + integrity sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA== + + lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + + lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + + lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + + lodash.template@^4.5.0: + version "4.5.0" + resolved "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz" + integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.templatesettings "^4.0.0" + + lodash.templatesettings@^4.0.0: + version "4.2.0" + resolved "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz" + integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== + dependencies: + lodash._reinterpolate "^3.0.0" + + lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz" + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== + + lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz" + integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== + + "lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.7.0: + version "4.17.21" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + + loglevel@^1.6.8: + version "1.8.0" + resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz" + integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA== + + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + + lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + + lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + + lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + + magic-string@^0.25.0, magic-string@^0.25.7: + version "0.25.9" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz" + integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== + dependencies: + sourcemap-codec "^1.4.8" + + make-dir@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + + make-dir@^3.0.0, make-dir@^3.0.2: + 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== + dependencies: + semver "^6.0.0" + + makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + + map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz" + integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== + + map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz" + integrity sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w== + dependencies: + object-visit "^1.0.0" + + md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + + mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + + mdn-data@2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz" + integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== + + media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + + memory-fs@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz" + integrity sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + + memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + + merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + + merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + + merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + + methods@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + + microevent.ts@~0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz" + integrity sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g== + + micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + + micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + + miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + + mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + + mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: + 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" + + mime@1.6.0: + version "1.6.0" + resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + + mime@^2.4.4: + version "2.6.0" + resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + + mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + + mini-css-extract-plugin@0.11.3: + version "0.11.3" + resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz" + integrity sha512-n9BA8LonkOkW1/zn+IbLPQmovsL0wMb9yx75fMJQZf2X1Zoec9yTZtyMePcyu19wPkmFbzZZA6fLTotpFhQsOA== + dependencies: + loader-utils "^1.1.0" + normalize-url "1.9.1" + schema-utils "^1.0.0" + webpack-sources "^1.1.0" + + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + + minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + + minimatch@3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + + minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + + minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + + minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + + minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + + minipass-pipeline@^1.2.2: + version "1.2.4" + resolved "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + + minipass@^3.0.0, minipass@^3.1.1: + version "3.3.4" + resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz" + integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw== + dependencies: + yallist "^4.0.0" + + minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + + mississippi@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz" + integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== + dependencies: + concat-stream "^1.5.0" + duplexify "^3.4.2" + end-of-stream "^1.1.0" + flush-write-stream "^1.0.0" + from2 "^2.1.0" + parallel-transform "^1.1.0" + pump "^3.0.0" + pumpify "^1.3.3" + stream-each "^1.1.0" + through2 "^2.0.0" + + mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + + mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.6, mkdirp@~0.5.1: + version "0.5.6" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + + mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + + move-concurrently@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz" + integrity sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ== + dependencies: + aproba "^1.1.1" + copy-concurrently "^1.0.0" + fs-write-stream-atomic "^1.0.8" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.3" + + ms@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + + ms@2.1.2, ms@^2.1.1: + version "2.1.2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + + ms@2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + + multicast-dns-service-types@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz" + integrity sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ== + + multicast-dns@^6.0.1: + version "6.2.3" + resolved "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz" + integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== + dependencies: + dns-packet "^1.3.1" + thunky "^1.0.2" + + nan@^2.12.1: + version "2.16.0" + resolved "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz" + integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA== + + nanoid@^3.3.4: + version "3.3.4" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== + + nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + + native-url@^0.2.6: + version "0.2.6" + resolved "https://registry.npmjs.org/native-url/-/native-url-0.2.6.tgz" + integrity sha512-k4bDC87WtgrdD362gZz6zoiXQrl40kYlBmpfmSjwRO1VU0V5ccwJTlxuE72F6m3V0vc1xOf6n3UCP9QyerRqmA== + dependencies: + querystring "^0.2.0" + + natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + + negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + + neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + + next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + + nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + + no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + + node-forge@^0.10.0: + version "0.10.0" + resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz" + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== + + node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + + node-libs-browser@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz" + integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== + dependencies: + assert "^1.1.1" + browserify-zlib "^0.2.0" + buffer "^4.3.0" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + crypto-browserify "^3.11.0" + domain-browser "^1.1.1" + events "^3.0.0" + https-browserify "^1.0.0" + os-browserify "^0.3.0" + path-browserify "0.0.1" + process "^0.11.10" + punycode "^1.2.4" + querystring-es3 "^0.2.0" + readable-stream "^2.3.3" + stream-browserify "^2.0.1" + stream-http "^2.7.2" + string_decoder "^1.0.0" + timers-browserify "^2.0.4" + tty-browserify "0.0.0" + url "^0.11.0" + util "^0.11.0" + vm-browserify "^1.0.1" + + node-notifier@^8.0.0: + version "8.0.2" + resolved "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz" + integrity sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg== + dependencies: + growly "^1.3.0" + is-wsl "^2.2.0" + semver "^7.3.2" + shellwords "^0.1.1" + uuid "^8.3.0" + which "^2.0.2" + + node-releases@^1.1.61: + version "1.1.77" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz" + integrity sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ== + + node-releases@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz" + integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== + + normalize-package-data@^2.5.0: + 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== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + + normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz" + integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w== + dependencies: + remove-trailing-separator "^1.0.1" + + normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + + normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + + normalize-url@1.9.1: + version "1.9.1" + resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz" + integrity sha512-A48My/mtCklowHBlI8Fq2jFWK4tX4lJ5E6ytFsSOq1fzpvT0SQSgKhSg7lN5c2uYFOrUAOQp6zhhJnpp1eMloQ== + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + + normalize-url@^3.0.0: + version "3.3.0" + resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz" + integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== + + npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz" + integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== + dependencies: + path-key "^2.0.0" + + npm-run-path@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + + nth-check@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + + nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + + num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz" + integrity sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg== + + nwsapi@^2.2.0: + version "2.2.2" + resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz" + integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw== + + object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + + object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz" + integrity sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ== + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + + object-inspect@^1.12.2, object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + + object-is@^1.0.1: + version "1.1.5" + resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + + object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + + object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz" + integrity sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA== + dependencies: + isobject "^3.0.0" + + object.assign@^4.1.0, object.assign@^4.1.3, object.assign@^4.1.4: + version "4.1.4" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + + object.entries@^1.1.0, object.entries@^1.1.5: + version "1.1.5" + resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz" + integrity sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + + object.fromentries@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz" + integrity sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + + object.getownpropertydescriptors@^2.0.3: + version "2.1.4" + resolved "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz" + integrity sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ== + dependencies: + array.prototype.reduce "^1.0.4" + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.1" + + object.hasown@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz" + integrity sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A== + dependencies: + define-properties "^1.1.4" + es-abstract "^1.19.5" + + object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz" + integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== + dependencies: + isobject "^3.0.1" + + object.values@^1.1.0, object.values@^1.1.5: + version "1.1.5" + resolved "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz" + integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + + obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + + on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + + on-headers@~1.0.2: + 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== + + once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + + onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + + open@^7.0.2: + version "7.4.2" + resolved "https://registry.npmjs.org/open/-/open-7.4.2.tgz" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + + opn@^5.5.0: + version "5.5.0" + resolved "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz" + integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== + dependencies: + is-wsl "^1.1.0" + + optimize-css-assets-webpack-plugin@5.0.4: + version "5.0.4" + resolved "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz" + integrity sha512-wqd6FdI2a5/FdoiCNNkEvLeA//lHHfG24Ln2Xm2qqdIk4aOlsR18jwpyOihqQ8849W3qu2DX8fOYxpvTMj+93A== + dependencies: + cssnano "^4.1.10" + last-call-webpack-plugin "^3.0.0" + + optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + + optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + + os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz" + integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== + + p-each-series@^2.1.0: + version "2.2.0" + resolved "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz" + integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== + + p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + + p-limit@^2.0.0, p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + + p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + + p-locate@^3.0.0: + 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== + dependencies: + p-limit "^2.0.0" + + p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + + p-map@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + + p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + + p-retry@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz" + integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w== + dependencies: + retry "^0.12.0" + + p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + + pako@~1.0.5: + version "1.0.11" + resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + + parallel-transform@^1.1.0: + version "1.2.0" + resolved "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz" + integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== + dependencies: + cyclist "^1.0.1" + inherits "^2.0.3" + readable-stream "^2.1.5" + + param-case@^3.0.3: + version "3.0.4" + resolved "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz" + integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + + parent-module@^1.0.0: + 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== + dependencies: + callsites "^3.0.0" + + parse-asn1@^5.0.0, parse-asn1@^5.1.5: + version "5.1.6" + resolved "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz" + integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== + dependencies: + asn1.js "^5.2.0" + browserify-aes "^1.0.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + + parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz" + integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + + parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + 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" + + parse5@6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + + parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + + pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + + pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz" + integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== + + path-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz" + integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== + + path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz" + integrity sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q== + + path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz" + integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== + + path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + + path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + + path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz" + integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== + + path-key@^2.0.0, path-key@^2.0.1: + 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== + + path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + + path-parse@^1.0.6, path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + + path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + + path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + + pbkdf2@^3.0.3: + version "3.1.2" + resolved "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + + peerjs-js-binarypack@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/peerjs-js-binarypack/-/peerjs-js-binarypack-1.0.1.tgz#80fa2b61c794a6b16d64253700405e476ada29be" + integrity sha512-N6aeia3NhdpV7kiGxJV5xQiZZCVEEVjRz2T2C6UZQiBkHWHzUv/oWA4myQLcwBwO8LUoR1KWW5oStvwVesmfCg== + + peerjs@1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/peerjs/-/peerjs-1.4.6.tgz#781f335f4043fcd686dc19acd3341db0570f7e72" + integrity sha512-0XA105/9yBFGxfpyCjlI1bcBiyPmXHs8+UDvO2j1WGnY+FXilMn35+P/3t8HzKnUnR1SX0PRkDSk8kM17ciNxA== + dependencies: + "@swc/helpers" "^0.3.13" + eventemitter3 "^4.0.7" + peerjs-js-binarypack "1.0.1" + webrtc-adapter "^7.7.1" + + performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + + picocolors@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz" + integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== + + picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + + pify@^2.0.0: + version "2.3.0" + resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + + pify@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + + pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" + integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw== + dependencies: + pinkie "^2.0.0" + + pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" + integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== + + pirates@^4.0.1: + version "4.0.5" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== + + pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + dependencies: + find-up "^3.0.0" + + pkg-dir@^4.1.0, pkg-dir@^4.2.0: + 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== + dependencies: + find-up "^4.0.0" + + pkg-up@3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + + pnp-webpack-plugin@1.6.4: + version "1.6.4" + resolved "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz" + integrity sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg== + dependencies: + ts-pnp "^1.1.6" + + portfinder@^1.0.26: + version "1.0.32" + resolved "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz" + integrity sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg== + dependencies: + async "^2.6.4" + debug "^3.2.7" + mkdirp "^0.5.6" + + posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz" + integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== + + postcss-attribute-case-insensitive@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz" + integrity sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^6.0.2" + + postcss-browser-comments@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-3.0.0.tgz" + integrity sha512-qfVjLfq7HFd2e0HW4s1dvU8X080OZdG46fFbIBFjW7US7YPDcWfRvdElvwMJr2LI6hMmD+7LnH2HcmXTs+uOig== + dependencies: + postcss "^7" + + postcss-calc@^7.0.1: + version "7.0.5" + resolved "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz" + integrity sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg== + dependencies: + postcss "^7.0.27" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.0.2" + + postcss-color-functional-notation@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz" + integrity sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + + postcss-color-gray@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz" + integrity sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.5" + postcss-values-parser "^2.0.0" + + postcss-color-hex-alpha@^5.0.3: + version "5.0.3" + resolved "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz" + integrity sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw== + dependencies: + postcss "^7.0.14" + postcss-values-parser "^2.0.1" + + postcss-color-mod-function@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz" + integrity sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + + postcss-color-rebeccapurple@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz" + integrity sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + + postcss-colormin@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz" + integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== + dependencies: + browserslist "^4.0.0" + color "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + + postcss-convert-values@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz" + integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + + postcss-custom-media@^7.0.8: + version "7.0.8" + resolved "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz" + integrity sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg== + dependencies: + postcss "^7.0.14" + + postcss-custom-properties@^8.0.11: + version "8.0.11" + resolved "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz" + integrity sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA== + dependencies: + postcss "^7.0.17" + postcss-values-parser "^2.0.1" + + postcss-custom-selectors@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz" + integrity sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + + postcss-dir-pseudo-class@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz" + integrity sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + + postcss-discard-comments@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz" + integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== + dependencies: + postcss "^7.0.0" + + postcss-discard-duplicates@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz" + integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== + dependencies: + postcss "^7.0.0" + + postcss-discard-empty@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz" + integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== + dependencies: + postcss "^7.0.0" + + postcss-discard-overridden@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz" + integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== + dependencies: + postcss "^7.0.0" + + postcss-double-position-gradients@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz" + integrity sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA== + dependencies: + postcss "^7.0.5" + postcss-values-parser "^2.0.0" + + postcss-env-function@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz" + integrity sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + + postcss-flexbugs-fixes@4.2.1: + version "4.2.1" + resolved "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz" + integrity sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ== + dependencies: + postcss "^7.0.26" + + postcss-focus-visible@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz" + integrity sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g== + dependencies: + postcss "^7.0.2" + + postcss-focus-within@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz" + integrity sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w== + dependencies: + postcss "^7.0.2" + + postcss-font-variant@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz" + integrity sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA== + dependencies: + postcss "^7.0.2" + + postcss-gap-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz" + integrity sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg== + dependencies: + postcss "^7.0.2" + + postcss-image-set-function@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz" + integrity sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + + postcss-initial@^3.0.0: + version "3.0.4" + resolved "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.4.tgz" + integrity sha512-3RLn6DIpMsK1l5UUy9jxQvoDeUN4gP939tDcKUHD/kM8SGSKbFAnvkpFpj3Bhtz3HGk1jWY5ZNWX6mPta5M9fg== + dependencies: + postcss "^7.0.2" + + postcss-lab-function@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz" + integrity sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + + postcss-load-config@^2.0.0: + version "2.1.2" + resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.2.tgz" + integrity sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw== + dependencies: + cosmiconfig "^5.0.0" + import-cwd "^2.0.0" + + postcss-loader@3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz" + integrity sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA== + dependencies: + loader-utils "^1.1.0" + postcss "^7.0.0" + postcss-load-config "^2.0.0" + schema-utils "^1.0.0" + + postcss-logical@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz" + integrity sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA== + dependencies: + postcss "^7.0.2" + + postcss-media-minmax@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz" + integrity sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw== + dependencies: + postcss "^7.0.2" + + postcss-merge-longhand@^4.0.11: + version "4.0.11" + resolved "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz" + integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== + dependencies: + css-color-names "0.0.4" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + stylehacks "^4.0.0" + + postcss-merge-rules@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz" + integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + cssnano-util-same-parent "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + vendors "^1.0.0" + + postcss-minify-font-values@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz" + integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + + postcss-minify-gradients@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz" + integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + is-color-stop "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + + postcss-minify-params@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz" + integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== + dependencies: + alphanum-sort "^1.0.0" + browserslist "^4.0.0" + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + uniqs "^2.0.0" + + postcss-minify-selectors@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz" + integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== + dependencies: + alphanum-sort "^1.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + + postcss-modules-extract-imports@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz" + integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== + dependencies: + postcss "^7.0.5" + + postcss-modules-local-by-default@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz" + integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== + dependencies: + icss-utils "^4.1.1" + postcss "^7.0.32" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + + postcss-modules-scope@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz" + integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== + dependencies: + postcss "^7.0.6" + postcss-selector-parser "^6.0.0" + + postcss-modules-values@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz" + integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== + dependencies: + icss-utils "^4.0.0" + postcss "^7.0.6" + + postcss-nesting@^7.0.0: + version "7.0.1" + resolved "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz" + integrity sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg== + dependencies: + postcss "^7.0.2" + + postcss-normalize-charset@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz" + integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== + dependencies: + postcss "^7.0.0" + + postcss-normalize-display-values@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz" + integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + + postcss-normalize-positions@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz" + integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== + dependencies: + cssnano-util-get-arguments "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + + postcss-normalize-repeat-style@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz" + integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + + postcss-normalize-string@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz" + integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== + dependencies: + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + + postcss-normalize-timing-functions@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz" + integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + + postcss-normalize-unicode@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz" + integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + + postcss-normalize-url@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz" + integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + + postcss-normalize-whitespace@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz" + integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + + postcss-normalize@8.0.1: + version "8.0.1" + resolved "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-8.0.1.tgz" + integrity sha512-rt9JMS/m9FHIRroDDBGSMsyW1c0fkvOJPy62ggxSHUldJO7B195TqFMqIf+lY5ezpDcYOV4j86aUp3/XbxzCCQ== + dependencies: + "@csstools/normalize.css" "^10.1.0" + browserslist "^4.6.2" + postcss "^7.0.17" + postcss-browser-comments "^3.0.0" + sanitize.css "^10.0.0" + + postcss-ordered-values@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz" + integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== + dependencies: + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + + postcss-overflow-shorthand@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz" + integrity sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g== + dependencies: + postcss "^7.0.2" + + postcss-page-break@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz" + integrity sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ== + dependencies: + postcss "^7.0.2" + + postcss-place@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz" + integrity sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + + postcss-preset-env@6.7.0: + version "6.7.0" + resolved "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz" + integrity sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg== + dependencies: + autoprefixer "^9.6.1" + browserslist "^4.6.4" + caniuse-lite "^1.0.30000981" + css-blank-pseudo "^0.1.4" + css-has-pseudo "^0.10.0" + css-prefers-color-scheme "^3.1.1" + cssdb "^4.4.0" + postcss "^7.0.17" + postcss-attribute-case-insensitive "^4.0.1" + postcss-color-functional-notation "^2.0.1" + postcss-color-gray "^5.0.0" + postcss-color-hex-alpha "^5.0.3" + postcss-color-mod-function "^3.0.3" + postcss-color-rebeccapurple "^4.0.1" + postcss-custom-media "^7.0.8" + postcss-custom-properties "^8.0.11" + postcss-custom-selectors "^5.1.2" + postcss-dir-pseudo-class "^5.0.0" + postcss-double-position-gradients "^1.0.0" + postcss-env-function "^2.0.2" + postcss-focus-visible "^4.0.0" + postcss-focus-within "^3.0.0" + postcss-font-variant "^4.0.0" + postcss-gap-properties "^2.0.0" + postcss-image-set-function "^3.0.1" + postcss-initial "^3.0.0" + postcss-lab-function "^2.0.1" + postcss-logical "^3.0.0" + postcss-media-minmax "^4.0.0" + postcss-nesting "^7.0.0" + postcss-overflow-shorthand "^2.0.0" + postcss-page-break "^2.0.0" + postcss-place "^4.0.1" + postcss-pseudo-class-any-link "^6.0.0" + postcss-replace-overflow-wrap "^3.0.0" + postcss-selector-matches "^4.0.0" + postcss-selector-not "^4.0.0" + + postcss-pseudo-class-any-link@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz" + integrity sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + + postcss-reduce-initial@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz" + integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + + postcss-reduce-transforms@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz" + integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== + dependencies: + cssnano-util-get-match "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + + postcss-replace-overflow-wrap@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz" + integrity sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw== + dependencies: + postcss "^7.0.2" + + postcss-safe-parser@5.0.2: + version "5.0.2" + resolved "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-5.0.2.tgz" + integrity sha512-jDUfCPJbKOABhwpUKcqCVbbXiloe/QXMcbJ6Iipf3sDIihEzTqRCeMBfRaOHxhBuTYqtASrI1KJWxzztZU4qUQ== + dependencies: + postcss "^8.1.0" + + postcss-selector-matches@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz" + integrity sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww== + dependencies: + balanced-match "^1.0.0" + postcss "^7.0.2" + + postcss-selector-not@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.1.tgz" + integrity sha512-YolvBgInEK5/79C+bdFMyzqTg6pkYqDbzZIST/PDMqa/o3qtXenD05apBG2jLgT0/BQ77d4U2UK12jWpilqMAQ== + dependencies: + balanced-match "^1.0.0" + postcss "^7.0.2" + + postcss-selector-parser@^3.0.0: + version "3.1.2" + resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz" + integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== + dependencies: + dot-prop "^5.2.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + + postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: + version "5.0.0" + resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz" + integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ== + dependencies: + cssesc "^2.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + + postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: + version "6.0.10" + resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz" + integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + + postcss-svgo@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.3.tgz" + integrity sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + svgo "^1.0.0" + + postcss-unique-selectors@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz" + integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== + dependencies: + alphanum-sort "^1.0.0" + postcss "^7.0.0" + uniqs "^2.0.0" + + postcss-value-parser@^3.0.0: + version "3.3.1" + resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== + + postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: + version "4.2.0" + resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + + postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz" + integrity sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg== + dependencies: + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + + postcss@7.0.36: + version "7.0.36" + resolved "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz" + integrity sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + + postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: + version "7.0.39" + resolved "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz" + integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== + dependencies: + picocolors "^0.2.1" + source-map "^0.6.1" + + postcss@^8.1.0: + version "8.4.16" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz" + integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + + prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + + prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz" + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== + + prepend-http@^1.0.0: + version "1.0.4" + resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz" + integrity sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg== + + pretty-bytes@^5.3.0: + version "5.6.0" + resolved "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== + + pretty-error@^2.1.1: + version "2.1.2" + resolved "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz" + integrity sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw== + dependencies: + lodash "^4.17.20" + renderkid "^2.0.4" + + pretty-format@^26.6.0, pretty-format@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz" + integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== + dependencies: + "@jest/types" "^26.6.2" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^17.0.1" + + process-nextick-args@~2.0.0: + 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== + + process@^0.11.10: + version "0.11.10" + resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + + progress@^2.0.0: + version "2.0.3" + resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + + promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz" + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== + + promise@^8.1.0: + version "8.2.0" + resolved "https://registry.npmjs.org/promise/-/promise-8.2.0.tgz" + integrity sha512-+CMAlLHqwRYwBMXKCP+o8ns7DN+xHDUiI+0nArsiJ9y+kJVPLFxEaSw6Ha9s9H0tftxg2Yzl25wqj9G7m5wLZg== + dependencies: + asap "~2.0.6" + + prompts@2.4.0, prompts@^2.0.1: + version "2.4.0" + resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz" + integrity sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + + prop-types-extra@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz" + integrity sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew== + dependencies: + react-is "^16.3.2" + warning "^4.0.0" + + prop-types@^15.6.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + + proxy-addr@~2.0.7: + 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== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + + prr@~1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz" + integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== + + psl@^1.1.33: + version "1.9.0" + resolved "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + + public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + + pump@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + + pump@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + + pumpify@^1.3.3: + version "1.5.1" + resolved "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== + dependencies: + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" + + punycode@1.3.2: + version "1.3.2" + resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" + integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== + + punycode@^1.2.4: + version "1.4.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" + integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== + + punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + + q@^1.1.2: + version "1.5.1" + resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz" + integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== + + qs@6.10.3: + version "6.10.3" + resolved "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz" + integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== + dependencies: + side-channel "^1.0.4" + + query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz" + integrity sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q== + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + + querystring-es3@^0.2.0: + version "0.2.1" + resolved "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz" + integrity sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA== + + querystring@0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz" + integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== + + querystring@^0.2.0: + version "0.2.1" + resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz" + integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== + + querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + + queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + + raf@^3.4.1: + version "3.4.1" + resolved "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + + randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + + range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + + raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + + react-app-polyfill@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz" + integrity sha512-0sF4ny9v/B7s6aoehwze9vJNWcmCemAUYBVasscVr92+UYiEqDXOxfKjXN685mDaMRNF3WdhHQs76oTODMocFA== + dependencies: + core-js "^3.6.5" + object-assign "^4.1.1" + promise "^8.1.0" + raf "^3.4.1" + regenerator-runtime "^0.13.7" + whatwg-fetch "^3.4.1" + + react-bootstrap@^2.5.0: + version "2.5.0" + resolved "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.5.0.tgz" + integrity sha512-j/aLR+okzbYk61TM3eDOU1NqOqnUdwyVrF+ojoCRUxPdzc2R0xXvqyRsjSoyRoCo7n82Fs/LWjPCin/QJNdwvA== + dependencies: + "@babel/runtime" "^7.17.2" + "@restart/hooks" "^0.4.6" + "@restart/ui" "^1.3.1" + "@types/react-transition-group" "^4.4.4" + classnames "^2.3.1" + dom-helpers "^5.2.1" + invariant "^2.2.4" + prop-types "^15.8.1" + prop-types-extra "^1.1.0" + react-transition-group "^4.4.2" + uncontrollable "^7.2.1" + warning "^4.0.3" + + react-dev-utils@^11.0.3: + version "11.0.4" + resolved "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz" + integrity sha512-dx0LvIGHcOPtKbeiSUM4jqpBl3TcY7CDjZdfOIcKeznE7BWr9dg0iPG90G5yfVQ+p/rGNMXdbfStvzQZEVEi4A== + dependencies: + "@babel/code-frame" "7.10.4" + address "1.1.2" + browserslist "4.14.2" + chalk "2.4.2" + cross-spawn "7.0.3" + detect-port-alt "1.1.6" + escape-string-regexp "2.0.0" + filesize "6.1.0" + find-up "4.1.0" + fork-ts-checker-webpack-plugin "4.1.6" + global-modules "2.0.0" + globby "11.0.1" + gzip-size "5.1.1" + immer "8.0.1" + is-root "2.1.0" + loader-utils "2.0.0" + open "^7.0.2" + pkg-up "3.1.0" + prompts "2.4.0" + react-error-overlay "^6.0.9" + recursive-readdir "2.2.2" + shell-quote "1.7.2" + strip-ansi "6.0.0" + text-table "0.2.0" + + react-dom@^17.0.2: + version "17.0.2" + resolved "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scheduler "^0.20.2" + + react-error-overlay@^6.0.9: + version "6.0.11" + resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz" + integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== + + react-is@^16.13.1, react-is@^16.3.2: + version "16.13.1" + resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + + react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + + react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + + react-refresh@^0.8.3: + version "0.8.3" + resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz" + integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg== + + react-scripts@4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/react-scripts/-/react-scripts-4.0.3.tgz" + integrity sha512-S5eO4vjUzUisvkIPB7jVsKtuH2HhWcASREYWHAQ1FP5HyCv3xgn+wpILAEWkmy+A+tTNbSZClhxjT3qz6g4L1A== + dependencies: + "@babel/core" "7.12.3" + "@pmmmwh/react-refresh-webpack-plugin" "0.4.3" + "@svgr/webpack" "5.5.0" + "@typescript-eslint/eslint-plugin" "^4.5.0" + "@typescript-eslint/parser" "^4.5.0" + babel-eslint "^10.1.0" + babel-jest "^26.6.0" + babel-loader "8.1.0" + babel-plugin-named-asset-import "^0.3.7" + babel-preset-react-app "^10.0.0" + bfj "^7.0.2" + camelcase "^6.1.0" + case-sensitive-paths-webpack-plugin "2.3.0" + css-loader "4.3.0" + dotenv "8.2.0" + dotenv-expand "5.1.0" + eslint "^7.11.0" + eslint-config-react-app "^6.0.0" + eslint-plugin-flowtype "^5.2.0" + eslint-plugin-import "^2.22.1" + eslint-plugin-jest "^24.1.0" + eslint-plugin-jsx-a11y "^6.3.1" + eslint-plugin-react "^7.21.5" + eslint-plugin-react-hooks "^4.2.0" + eslint-plugin-testing-library "^3.9.2" + eslint-webpack-plugin "^2.5.2" + file-loader "6.1.1" + fs-extra "^9.0.1" + html-webpack-plugin "4.5.0" + identity-obj-proxy "3.0.0" + jest "26.6.0" + jest-circus "26.6.0" + jest-resolve "26.6.0" + jest-watch-typeahead "0.6.1" + mini-css-extract-plugin "0.11.3" + optimize-css-assets-webpack-plugin "5.0.4" + pnp-webpack-plugin "1.6.4" + postcss-flexbugs-fixes "4.2.1" + postcss-loader "3.0.0" + postcss-normalize "8.0.1" + postcss-preset-env "6.7.0" + postcss-safe-parser "5.0.2" + prompts "2.4.0" + react-app-polyfill "^2.0.0" + react-dev-utils "^11.0.3" + react-refresh "^0.8.3" + resolve "1.18.1" + resolve-url-loader "^3.1.2" + sass-loader "^10.0.5" + semver "7.3.2" + style-loader "1.3.0" + terser-webpack-plugin "4.2.3" + ts-pnp "1.2.0" + url-loader "4.1.1" + webpack "4.44.2" + webpack-dev-server "3.11.1" + webpack-manifest-plugin "2.2.0" + workbox-webpack-plugin "5.1.4" + optionalDependencies: + fsevents "^2.1.3" + + react-table@^7.8.0: + version "7.8.0" + resolved "https://registry.npmjs.org/react-table/-/react-table-7.8.0.tgz" + integrity sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA== + + react-transition-group@^4.4.2: + version "4.4.5" + resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + + react@^17.0.2: + version "17.0.2" + resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + + read-pkg-up@^7.0.1: + 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== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + + read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + + "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + 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" + + readable-stream@^3.0.6, readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + + readdirp@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + + readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + + recursive-readdir@2.2.2: + version "2.2.2" + resolved "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz" + integrity sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg== + dependencies: + minimatch "3.0.4" + + redux@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz" + integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA== + dependencies: + "@babel/runtime" "^7.9.2" + + regenerate-unicode-properties@^10.0.1: + version "10.0.1" + resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz" + integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== + dependencies: + regenerate "^1.4.2" + + regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + + regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + + regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: + version "0.13.9" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + + regenerator-transform@^0.15.0: + version "0.15.0" + resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz" + integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg== + dependencies: + "@babel/runtime" "^7.8.4" + + regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + + regex-parser@^2.2.11: + version "2.2.11" + resolved "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz" + integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q== + + regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3: + version "1.4.3" + resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz" + integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + functions-have-names "^1.2.2" + + regexpp@^3.1.0: + version "3.2.0" + resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + + regexpu-core@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz" + integrity sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.0.1" + regjsgen "^0.6.0" + regjsparser "^0.8.2" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.0.0" + + regjsgen@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz" + integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== + + regjsparser@^0.8.2: + version "0.8.4" + resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz" + integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== + dependencies: + jsesc "~0.5.0" + + relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz" + integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== + + remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz" + integrity sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw== + + renderkid@^2.0.4: + version "2.0.7" + resolved "https://registry.npmjs.org/renderkid/-/renderkid-2.0.7.tgz" + integrity sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ== + dependencies: + css-select "^4.1.3" + dom-converter "^0.2.0" + htmlparser2 "^6.1.0" + lodash "^4.17.21" + strip-ansi "^3.0.1" + + repeat-element@^1.1.2: + version "1.1.4" + resolved "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz" + integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== + + repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" + integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== + + require-directory@^2.1.1: + 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== + + require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + + require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + + requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + + resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz" + integrity sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg== + dependencies: + resolve-from "^3.0.0" + + resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + + resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz" + integrity sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw== + + resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + + resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + + resolve-url-loader@^3.1.2: + version "3.1.4" + resolved "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.1.4.tgz" + integrity sha512-D3sQ04o0eeQEySLrcz4DsX3saHfsr8/N6tfhblxgZKXxMT2Louargg12oGNfoTRLV09GXhVUe5/qgA5vdgNigg== + dependencies: + adjust-sourcemap-loader "3.0.0" + camelcase "5.3.1" + compose-function "3.0.3" + convert-source-map "1.7.0" + es6-iterator "2.0.3" + loader-utils "1.2.3" + postcss "7.0.36" + rework "1.0.1" + rework-visit "1.0.0" + source-map "0.6.1" + + resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz" + integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== + + resolve@1.18.1, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.3.2: + version "1.18.1" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz" + integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== + dependencies: + is-core-module "^2.0.0" + path-parse "^1.0.6" + + resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0: + version "1.22.1" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + + resolve@^2.0.0-next.3: + version "2.0.0-next.4" + resolved "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz" + integrity sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + + ret@~0.1.10: + version "0.1.15" + resolved "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + + retry@^0.12.0: + version "0.12.0" + resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + + reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + + rework-visit@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/rework-visit/-/rework-visit-1.0.0.tgz" + integrity sha512-W6V2fix7nCLUYX1v6eGPrBOZlc03/faqzP4sUxMAJMBMOPYhfV/RyLegTufn5gJKaOITyi+gvf0LXDZ9NzkHnQ== + + rework@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/rework/-/rework-1.0.1.tgz" + integrity sha512-eEjL8FdkdsxApd0yWVZgBGzfCQiT8yqSc2H1p4jpZpQdtz7ohETiDMoje5PlM8I9WgkqkreVxFUKYOiJdVWDXw== + dependencies: + convert-source-map "^0.3.3" + css "^2.0.0" + + rgb-regex@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz" + integrity sha512-gDK5mkALDFER2YLqH6imYvK6g02gpNGM4ILDZ472EwWfXZnC2ZEpoB2ECXTyOVUKuk/bPJZMzwQPBYICzP+D3w== + + rgba-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz" + integrity sha512-zgn5OjNQXLUTdq8m17KdaicF6w89TZs8ZU8y0AYENIU6wG8GG6LLm0yLSiPY8DmaYmHdgRW8rnApjoT0fQRfMg== + + rimraf@^2.5.4, rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + + rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + + ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + + rollup-plugin-babel@^4.3.3: + version "4.4.0" + resolved "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.4.0.tgz" + integrity sha512-Lek/TYp1+7g7I+uMfJnnSJ7YWoD58ajo6Oarhlex7lvUce+RCKRuGRSgztDO3/MF/PuGKmUL5iTHKf208UNszw== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + rollup-pluginutils "^2.8.1" + + rollup-plugin-terser@^5.3.1: + version "5.3.1" + resolved "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz" + integrity sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w== + dependencies: + "@babel/code-frame" "^7.5.5" + jest-worker "^24.9.0" + rollup-pluginutils "^2.8.2" + serialize-javascript "^4.0.0" + terser "^4.6.2" + + rollup-pluginutils@^2.8.1, rollup-pluginutils@^2.8.2: + version "2.8.2" + resolved "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz" + integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== + dependencies: + estree-walker "^0.6.1" + + rollup@^1.31.1: + version "1.32.1" + resolved "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz" + integrity sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A== + dependencies: + "@types/estree" "*" + "@types/node" "*" + acorn "^7.1.0" + + rsvp@^4.8.4: + version "4.8.5" + resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz" + integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== + + rtcpeerconnection-shim@^1.2.15: + version "1.2.15" + resolved "https://registry.yarnpkg.com/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz#e7cc189a81b435324c4949aa3dfb51888684b243" + integrity sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw== + dependencies: + sdp "^2.6.0" + + run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + + run-queue@^1.0.0, run-queue@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz" + integrity sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg== + dependencies: + aproba "^1.1.1" + + safe-buffer@5.1.2, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + + safe-buffer@5.2.1, safe-buffer@^5.2.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + + safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz" + integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== + dependencies: + ret "~0.1.10" + + "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0: + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + + sane@^4.0.3: + version "4.1.0" + resolved "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz" + integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^2.0.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + + sanitize.css@^10.0.0: + version "10.0.0" + resolved "https://registry.npmjs.org/sanitize.css/-/sanitize.css-10.0.0.tgz" + integrity sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg== + + sass-loader@^10.0.5: + version "10.3.1" + resolved "https://registry.npmjs.org/sass-loader/-/sass-loader-10.3.1.tgz" + integrity sha512-y2aBdtYkbqorVavkC3fcJIUDGIegzDWPn3/LAFhsf3G+MzPKTJx37sROf5pXtUeggSVbNbmfj8TgRaSLMelXRA== + dependencies: + klona "^2.0.4" + loader-utils "^2.0.0" + neo-async "^2.6.2" + schema-utils "^3.0.0" + semver "^7.3.2" + + sax@~1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + + saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + + scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + + schema-utils@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz" + integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== + dependencies: + ajv "^6.1.0" + ajv-errors "^1.0.0" + ajv-keywords "^3.1.0" + + schema-utils@^2.6.5, schema-utils@^2.7.0, schema-utils@^2.7.1: + version "2.7.1" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== + dependencies: + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" + + schema-utils@^3.0.0, schema-utils@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz" + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + + sdp@^2.12.0, sdp@^2.6.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/sdp/-/sdp-2.12.0.tgz#338a106af7560c86e4523f858349680350d53b22" + integrity sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw== + + select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz" + integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== + + selfsigned@^1.10.8: + version "1.10.14" + resolved "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.14.tgz" + integrity sha512-lkjaiAye+wBZDCBsu5BGi0XiLRxeUlsGod5ZP924CRSEoGuZAw/f7y9RKu28rwTfiHVhdavhB0qH0INV6P1lEA== + dependencies: + node-forge "^0.10.0" + + "semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: + version "5.7.1" + resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + + semver@7.3.2, semver@^7.2.1, semver@^7.3.2: + version "7.3.2" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + + semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + + semver@^7.3.5: + version "7.3.7" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + + send@0.18.0: + version "0.18.0" + resolved "https://registry.npmjs.org/send/-/send-0.18.0.tgz" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + 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" + + serialize-javascript@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz" + integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== + dependencies: + randombytes "^2.1.0" + + serialize-javascript@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" + + serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz" + integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + + serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + + set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + + set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + + setimmediate@^1.0.4: + version "1.0.5" + resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + + setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + + setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + + sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + + shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz" + integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== + dependencies: + shebang-regex "^1.0.0" + + shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + + shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz" + integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== + + shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + + shell-quote@1.7.2: + version "1.7.2" + resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz" + integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== + + shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz" + integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== + + side-channel@^1.0.4: + 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" + + signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.7" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + + simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + + sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + + slash@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + + slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + + snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + + snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + + snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + + socket.io-client@^4.4.1: + version "4.5.2" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.5.2.tgz#9481518c560388c980c88b01e3cf62f367f04c96" + integrity sha512-naqYfFu7CLDiQ1B7AlLhRXKX3gdeaIMfgigwavDzgJoIUYulc1qHH5+2XflTsXTPY7BlPH5rppJyUjhjrKQKLg== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.2.1" + socket.io-parser "~4.2.0" + + socket.io-parser@~4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.1.tgz#01c96efa11ded938dcb21cbe590c26af5eff65e5" + integrity sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + + sockjs-client@^1.5.0: + version "1.6.1" + resolved "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.1.tgz" + integrity sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw== + dependencies: + debug "^3.2.7" + eventsource "^2.0.2" + faye-websocket "^0.11.4" + inherits "^2.0.4" + url-parse "^1.5.10" + + sockjs@^0.3.21: + version "0.3.24" + resolved "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== + dependencies: + faye-websocket "^0.11.3" + uuid "^8.3.2" + websocket-driver "^0.7.4" + + sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz" + integrity sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg== + dependencies: + is-plain-obj "^1.0.0" + + source-list-map@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== + + source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + + source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: + version "0.5.3" + resolved "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + + source-map-support@^0.5.6, source-map-support@~0.5.12, source-map-support@~0.5.20: + 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== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + + source-map-url@^0.4.0: + version "0.4.1" + resolved "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz" + integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== + + source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + + source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + + source-map@^0.7.3: + version "0.7.4" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + + sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + + spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + + spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + + spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + + spdx-license-ids@^3.0.0: + version "3.0.12" + resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz" + integrity sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA== + + spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + + spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + + split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + + sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + + ssri@^6.0.1: + version "6.0.2" + resolved "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz" + integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q== + dependencies: + figgy-pudding "^3.5.1" + + ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + + stable@^0.1.8: + version "0.1.8" + resolved "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + + stack-utils@^2.0.2: + version "2.0.5" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz" + integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== + dependencies: + escape-string-regexp "^2.0.0" + + stackframe@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" + integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== + + static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz" + integrity sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g== + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + + statuses@2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + + "statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + + stream-browserify@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz" + integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== + dependencies: + inherits "~2.0.1" + readable-stream "^2.0.2" + + stream-each@^1.1.0: + version "1.2.3" + resolved "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz" + integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== + dependencies: + end-of-stream "^1.1.0" + stream-shift "^1.0.0" + + stream-http@^2.7.2: + version "2.8.3" + resolved "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz" + integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.1" + readable-stream "^2.3.6" + to-arraybuffer "^1.0.0" + xtend "^4.0.0" + + stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + + strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz" + integrity sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ== + + string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + + string-natural-compare@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz" + integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== + + string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + + string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + + string.prototype.matchall@^4.0.7: + version "4.0.7" + resolved "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz" + integrity sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + get-intrinsic "^1.1.1" + has-symbols "^1.0.3" + internal-slot "^1.0.3" + regexp.prototype.flags "^1.4.1" + side-channel "^1.0.4" + + string.prototype.trimend@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz" + integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + + string.prototype.trimstart@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz" + integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + + string_decoder@^1.0.0, string_decoder@^1.1.1: + 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== + dependencies: + safe-buffer "~5.2.0" + + string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + + stringify-object@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== + dependencies: + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.0.0" + + strip-ansi@6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + + strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== + dependencies: + ansi-regex "^2.0.0" + + strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + + strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + + strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + + strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + + strip-comments@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/strip-comments/-/strip-comments-1.0.2.tgz" + integrity sha512-kL97alc47hoyIQSV165tTt9rG5dn4w1dNnBhOQ3bOU1Nc1hel09jnXANaHJ7vzHLd4Ju8kseDGzlev96pghLFw== + dependencies: + babel-extract-comments "^1.0.0" + babel-plugin-transform-object-rest-spread "^6.26.0" + + strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz" + integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== + + strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + + strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + 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== + + style-loader@1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/style-loader/-/style-loader-1.3.0.tgz" + integrity sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q== + dependencies: + loader-utils "^2.0.0" + schema-utils "^2.7.0" + + stylehacks@^4.0.0: + version "4.0.3" + resolved "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz" + integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + + supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + + supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + + supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + + supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + + supports-hyperlinks@^2.0.0: + version "2.3.0" + resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz" + integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + + supports-preserve-symlinks-flag@^1.0.0: + 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== + + svg-parser@^2.0.2: + version "2.0.4" + resolved "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz" + integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== + + svgo@^1.0.0, svgo@^1.2.2: + version "1.3.2" + resolved "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz" + integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== + dependencies: + chalk "^2.4.1" + coa "^2.0.2" + css-select "^2.0.0" + css-select-base-adapter "^0.1.1" + css-tree "1.0.0-alpha.37" + csso "^4.0.2" + js-yaml "^3.13.1" + mkdirp "~0.5.1" + object.values "^1.1.0" + sax "~1.2.4" + stable "^0.1.8" + unquote "~1.1.1" + util.promisify "~1.0.0" + + symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + + table@^6.0.9: + version "6.8.0" + resolved "https://registry.npmjs.org/table/-/table-6.8.0.tgz" + integrity sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + + tapable@^1.0.0, tapable@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + + tar@^6.0.2: + version "6.1.11" + resolved "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + + temp-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz" + integrity sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== + + tempy@^0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/tempy/-/tempy-0.3.0.tgz" + integrity sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ== + dependencies: + temp-dir "^1.0.0" + type-fest "^0.3.1" + unique-string "^1.0.0" + + terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + + terser-webpack-plugin@4.2.3: + version "4.2.3" + resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz" + integrity sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ== + dependencies: + cacache "^15.0.5" + find-cache-dir "^3.3.1" + jest-worker "^26.5.0" + p-limit "^3.0.2" + schema-utils "^3.0.0" + serialize-javascript "^5.0.1" + source-map "^0.6.1" + terser "^5.3.4" + webpack-sources "^1.4.3" + + terser-webpack-plugin@^1.4.3: + version "1.4.5" + resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz" + integrity sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw== + dependencies: + cacache "^12.0.2" + find-cache-dir "^2.1.0" + is-wsl "^1.1.0" + schema-utils "^1.0.0" + serialize-javascript "^4.0.0" + source-map "^0.6.1" + terser "^4.1.2" + webpack-sources "^1.4.0" + worker-farm "^1.7.0" + + terser@^4.1.2, terser@^4.6.2, terser@^4.6.3: + version "4.8.1" + resolved "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz" + integrity sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + + terser@^5.3.4: + version "5.15.0" + resolved "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz" + integrity sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA== + dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" + commander "^2.20.0" + source-map-support "~0.5.20" + + test-exclude@^6.0.0: + 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== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + + text-table@0.2.0, text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + + throat@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz" + integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== + + through2@^2.0.0: + version "2.0.5" + resolved "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + + thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + + timers-browserify@^2.0.4: + version "2.0.12" + resolved "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz" + integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== + dependencies: + setimmediate "^1.0.4" + + timsort@^0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz" + integrity sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A== + + tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + + to-arraybuffer@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz" + integrity sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA== + + to-fast-properties@^2.0.0: + 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== + + to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz" + integrity sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg== + dependencies: + kind-of "^3.0.2" + + to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz" + integrity sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg== + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + + to-regex-range@^5.0.1: + 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== + dependencies: + is-number "^7.0.0" + + to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + + toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + + tough-cookie@^4.0.0: + version "4.1.2" + resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz" + integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + + tr46@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz" + integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== + dependencies: + punycode "^2.1.1" + + tryer@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz" + integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== + + ts-pnp@1.2.0, ts-pnp@^1.1.6: + version "1.2.0" + resolved "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz" + integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== + + tsconfig-paths@^3.14.1: + version "3.14.1" + resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz" + integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.6" + strip-bom "^3.0.0" + + tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + + tslib@^2.0.3, tslib@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + + tsutils@^3.17.1, tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + + tty-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz" + integrity sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw== + + type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + + type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz" + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== + dependencies: + prelude-ls "~1.1.2" + + type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + + type-fest@^0.20.2: + 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== + + type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + + type-fest@^0.3.1: + version "0.3.1" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz" + integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ== + + type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + + type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + + type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + + type@^1.0.1: + version "1.2.0" + resolved "https://registry.npmjs.org/type/-/type-1.2.0.tgz" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + + type@^2.7.2: + version "2.7.2" + resolved "https://registry.npmjs.org/type/-/type-2.7.2.tgz" + integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== + + typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + + typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== + + typescript@^4.1.2: + version "4.8.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz" + integrity sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig== + + unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + + uncontrollable@^7.2.1: + version "7.2.1" + resolved "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz" + integrity sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ== + dependencies: + "@babel/runtime" "^7.6.3" + "@types/react" ">=16.9.11" + invariant "^2.2.4" + react-lifecycles-compat "^3.0.4" + + unicode-canonical-property-names-ecmascript@^2.0.0: + 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== + + unicode-match-property-ecmascript@^2.0.0: + 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== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + + unicode-match-property-value-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz" + integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== + + unicode-property-aliases-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz" + integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== + + union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + + uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz" + integrity sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA== + + uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz" + integrity sha512-mZdDpf3vBV5Efh29kMw5tXoup/buMgxLzOt/XKFKcVmi+15ManNQWr6HfZ2aiZTYlYixbdNJ0KFmIZIv52tHSQ== + + unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + + unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + + unique-string@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz" + integrity sha512-ODgiYu03y5g76A1I9Gt0/chLCzQjvzDy7DsZGsLOE/1MrF6wriEskSncj1+/C58Xk/kPZDppSctDybCwOSaGAg== + dependencies: + crypto-random-string "^1.0.0" + + universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + + universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + + universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + + unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + + unquote@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz" + integrity sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg== + + unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz" + integrity sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ== + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + + upath@^1.1.1, upath@^1.1.2, upath@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + + update-browserslist-db@^1.0.5: + version "1.0.7" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz" + integrity sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + + uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + + urix@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz" + integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== + + url-loader@4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz" + integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA== + dependencies: + loader-utils "^2.0.0" + mime-types "^2.1.27" + schema-utils "^3.0.0" + + url-parse@^1.5.10, url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + + url@^0.11.0: + version "0.11.0" + resolved "https://registry.npmjs.org/url/-/url-0.11.0.tgz" + integrity sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ== + dependencies: + punycode "1.3.2" + querystring "0.2.0" + + use-sync-external-store@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + + use@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/use/-/use-3.1.1.tgz" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + + util.promisify@1.0.0, util.promisify@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz" + integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + + util@0.10.3: + version "0.10.3" + resolved "https://registry.npmjs.org/util/-/util-0.10.3.tgz" + integrity sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ== + dependencies: + inherits "2.0.1" + + util@^0.11.0: + version "0.11.1" + resolved "https://registry.npmjs.org/util/-/util-0.11.1.tgz" + integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== + dependencies: + inherits "2.0.3" + + utila@~0.4: + version "0.4.0" + resolved "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz" + integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== + + utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + + uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + + uuid@^8.3.0, uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + + v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + + v8-to-istanbul@^7.0.0: + version "7.1.2" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz" + integrity sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + + validate-npm-package-license@^3.0.1: + 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== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + + vary@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + + vendors@^1.0.0: + version "1.0.4" + resolved "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz" + integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== + + vm-browserify@^1.0.1: + version "1.1.2" + resolved "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz" + integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== + + w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + + w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz" + integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== + dependencies: + xml-name-validator "^3.0.0" + + walker@^1.0.7, walker@~1.0.5: + version "1.0.8" + resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + + warning@^4.0.0, warning@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + + watchpack-chokidar2@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz" + integrity sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww== + dependencies: + chokidar "^2.1.8" + + watchpack@^1.7.4: + version "1.7.5" + resolved "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz" + integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== + dependencies: + graceful-fs "^4.1.2" + neo-async "^2.5.0" + optionalDependencies: + chokidar "^3.4.1" + watchpack-chokidar2 "^2.0.1" + + wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + + web-vitals@^1.0.1: + version "1.1.2" + resolved "https://registry.npmjs.org/web-vitals/-/web-vitals-1.1.2.tgz" + integrity sha512-PFMKIY+bRSXlMxVAQ+m2aw9c/ioUYfDgrYot0YUa+/xa0sakubWhSDyxAKwzymvXVdF4CZI71g06W+mqhzu6ig== + + webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== + + webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + + webpack-dev-middleware@^3.7.2: + version "3.7.3" + resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz" + integrity sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ== + dependencies: + memory-fs "^0.4.1" + mime "^2.4.4" + mkdirp "^0.5.1" + range-parser "^1.2.1" + webpack-log "^2.0.0" + + webpack-dev-server@3.11.1: + version "3.11.1" + resolved "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.1.tgz" + integrity sha512-u4R3mRzZkbxQVa+MBWi2uVpB5W59H3ekZAJsQlKUTdl7Elcah2EhygTPLmeFXybQkf9i2+L0kn7ik9SnXa6ihQ== + dependencies: + ansi-html "0.0.7" + bonjour "^3.5.0" + chokidar "^2.1.8" + compression "^1.7.4" + connect-history-api-fallback "^1.6.0" + debug "^4.1.1" + del "^4.1.1" + express "^4.17.1" + html-entities "^1.3.1" + http-proxy-middleware "0.19.1" + import-local "^2.0.0" + internal-ip "^4.3.0" + ip "^1.1.5" + is-absolute-url "^3.0.3" + killable "^1.0.1" + loglevel "^1.6.8" + opn "^5.5.0" + p-retry "^3.0.1" + portfinder "^1.0.26" + schema-utils "^1.0.0" + selfsigned "^1.10.8" + semver "^6.3.0" + serve-index "^1.9.1" + sockjs "^0.3.21" + sockjs-client "^1.5.0" + spdy "^4.0.2" + strip-ansi "^3.0.1" + supports-color "^6.1.0" + url "^0.11.0" + webpack-dev-middleware "^3.7.2" + webpack-log "^2.0.0" + ws "^6.2.1" + yargs "^13.3.2" + + webpack-log@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz" + integrity sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg== + dependencies: + ansi-colors "^3.0.0" + uuid "^3.3.2" + + webpack-manifest-plugin@2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-2.2.0.tgz" + integrity sha512-9S6YyKKKh/Oz/eryM1RyLVDVmy3NSPV0JXMRhZ18fJsq+AwGxUY34X54VNwkzYcEmEkDwNxuEOboCZEebJXBAQ== + dependencies: + fs-extra "^7.0.0" + lodash ">=3.5 <5" + object.entries "^1.1.0" + tapable "^1.0.0" + + webpack-sources@^1.1.0, webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: + version "1.4.3" + resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz" + integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== + dependencies: + source-list-map "^2.0.0" + source-map "~0.6.1" + + webpack@4.44.2: + version "4.44.2" + resolved "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz" + integrity sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/wasm-edit" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + acorn "^6.4.1" + ajv "^6.10.2" + ajv-keywords "^3.4.1" + chrome-trace-event "^1.0.2" + enhanced-resolve "^4.3.0" + eslint-scope "^4.0.3" + json-parse-better-errors "^1.0.2" + loader-runner "^2.4.0" + loader-utils "^1.2.3" + memory-fs "^0.4.1" + micromatch "^3.1.10" + mkdirp "^0.5.3" + neo-async "^2.6.1" + node-libs-browser "^2.2.1" + schema-utils "^1.0.0" + tapable "^1.1.3" + terser-webpack-plugin "^1.4.3" + watchpack "^1.7.4" + webpack-sources "^1.4.1" + + webrtc-adapter@^7.7.1: + version "7.7.1" + resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-7.7.1.tgz#b2c227a6144983b35057df67bd984a7d4bfd17f1" + integrity sha512-TbrbBmiQBL9n0/5bvDdORc6ZfRY/Z7JnEj+EYOD1ghseZdpJ+nF2yx14k3LgQKc7JZnG7HAcL+zHnY25So9d7A== + dependencies: + rtcpeerconnection-shim "^1.2.15" + sdp "^2.12.0" + + websocket-driver@>=0.5.1, websocket-driver@^0.7.4: + version "0.7.4" + resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + + websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + + whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + + whatwg-fetch@^3.4.1: + version "3.6.2" + resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz" + integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== + + whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + + whatwg-url@^8.0.0, whatwg-url@^8.5.0: + version "8.7.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz" + integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== + dependencies: + lodash "^4.7.0" + tr46 "^2.1.0" + webidl-conversions "^6.1.0" + + which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + 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" + + which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz" + integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== + + which@^1.2.9, which@^1.3.1: + version "1.3.1" + resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + + which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + + word-wrap@^1.2.3, word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + + workbox-background-sync@^5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-5.1.4.tgz" + integrity sha512-AH6x5pYq4vwQvfRDWH+vfOePfPIYQ00nCEB7dJRU1e0n9+9HMRyvI63FlDvtFT2AvXVRsXvUt7DNMEToyJLpSA== + dependencies: + workbox-core "^5.1.4" + + workbox-broadcast-update@^5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-5.1.4.tgz" + integrity sha512-HTyTWkqXvHRuqY73XrwvXPud/FN6x3ROzkfFPsRjtw/kGZuZkPzfeH531qdUGfhtwjmtO/ZzXcWErqVzJNdXaA== + dependencies: + workbox-core "^5.1.4" + + workbox-build@^5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/workbox-build/-/workbox-build-5.1.4.tgz" + integrity sha512-xUcZn6SYU8usjOlfLb9Y2/f86Gdo+fy1fXgH8tJHjxgpo53VVsqRX0lUDw8/JuyzNmXuo8vXX14pXX2oIm9Bow== + dependencies: + "@babel/core" "^7.8.4" + "@babel/preset-env" "^7.8.4" + "@babel/runtime" "^7.8.4" + "@hapi/joi" "^15.1.0" + "@rollup/plugin-node-resolve" "^7.1.1" + "@rollup/plugin-replace" "^2.3.1" + "@surma/rollup-plugin-off-main-thread" "^1.1.1" + common-tags "^1.8.0" + fast-json-stable-stringify "^2.1.0" + fs-extra "^8.1.0" + glob "^7.1.6" + lodash.template "^4.5.0" + pretty-bytes "^5.3.0" + rollup "^1.31.1" + rollup-plugin-babel "^4.3.3" + rollup-plugin-terser "^5.3.1" + source-map "^0.7.3" + source-map-url "^0.4.0" + stringify-object "^3.3.0" + strip-comments "^1.0.2" + tempy "^0.3.0" + upath "^1.2.0" + workbox-background-sync "^5.1.4" + workbox-broadcast-update "^5.1.4" + workbox-cacheable-response "^5.1.4" + workbox-core "^5.1.4" + workbox-expiration "^5.1.4" + workbox-google-analytics "^5.1.4" + workbox-navigation-preload "^5.1.4" + workbox-precaching "^5.1.4" + workbox-range-requests "^5.1.4" + workbox-routing "^5.1.4" + workbox-strategies "^5.1.4" + workbox-streams "^5.1.4" + workbox-sw "^5.1.4" + workbox-window "^5.1.4" + + workbox-cacheable-response@^5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-5.1.4.tgz" + integrity sha512-0bfvMZs0Of1S5cdswfQK0BXt6ulU5kVD4lwer2CeI+03czHprXR3V4Y8lPTooamn7eHP8Iywi5QjyAMjw0qauA== + dependencies: + workbox-core "^5.1.4" + + workbox-core@^5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz" + integrity sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg== + + workbox-expiration@^5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-5.1.4.tgz" + integrity sha512-oDO/5iC65h2Eq7jctAv858W2+CeRW5e0jZBMNRXpzp0ZPvuT6GblUiHnAsC5W5lANs1QS9atVOm4ifrBiYY7AQ== + dependencies: + workbox-core "^5.1.4" + + workbox-google-analytics@^5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-5.1.4.tgz" + integrity sha512-0IFhKoEVrreHpKgcOoddV+oIaVXBFKXUzJVBI+nb0bxmcwYuZMdteBTp8AEDJacENtc9xbR0wa9RDCnYsCDLjA== + dependencies: + workbox-background-sync "^5.1.4" + workbox-core "^5.1.4" + workbox-routing "^5.1.4" + workbox-strategies "^5.1.4" + + workbox-navigation-preload@^5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-5.1.4.tgz" + integrity sha512-Wf03osvK0wTflAfKXba//QmWC5BIaIZARU03JIhAEO2wSB2BDROWI8Q/zmianf54kdV7e1eLaIEZhth4K4MyfQ== + dependencies: + workbox-core "^5.1.4" + + workbox-precaching@^5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-5.1.4.tgz" + integrity sha512-gCIFrBXmVQLFwvAzuGLCmkUYGVhBb7D1k/IL7pUJUO5xacjLcFUaLnnsoVepBGAiKw34HU1y/YuqvTKim9qAZA== + dependencies: + workbox-core "^5.1.4" + + workbox-range-requests@^5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-5.1.4.tgz" + integrity sha512-1HSujLjgTeoxHrMR2muDW2dKdxqCGMc1KbeyGcmjZZAizJTFwu7CWLDmLv6O1ceWYrhfuLFJO+umYMddk2XMhw== + dependencies: + workbox-core "^5.1.4" + + workbox-routing@^5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/workbox-routing/-/workbox-routing-5.1.4.tgz" + integrity sha512-8ljknRfqE1vEQtnMtzfksL+UXO822jJlHTIR7+BtJuxQ17+WPZfsHqvk1ynR/v0EHik4x2+826Hkwpgh4GKDCw== + dependencies: + workbox-core "^5.1.4" + + workbox-strategies@^5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-5.1.4.tgz" + integrity sha512-VVS57LpaJTdjW3RgZvPwX0NlhNmscR7OQ9bP+N/34cYMDzXLyA6kqWffP6QKXSkca1OFo/v6v7hW7zrrguo6EA== + dependencies: + workbox-core "^5.1.4" + workbox-routing "^5.1.4" + + workbox-streams@^5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/workbox-streams/-/workbox-streams-5.1.4.tgz" + integrity sha512-xU8yuF1hI/XcVhJUAfbQLa1guQUhdLMPQJkdT0kn6HP5CwiPOGiXnSFq80rAG4b1kJUChQQIGPrq439FQUNVrw== + dependencies: + workbox-core "^5.1.4" + workbox-routing "^5.1.4" + + workbox-sw@^5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/workbox-sw/-/workbox-sw-5.1.4.tgz" + integrity sha512-9xKnKw95aXwSNc8kk8gki4HU0g0W6KXu+xks7wFuC7h0sembFnTrKtckqZxbSod41TDaGh+gWUA5IRXrL0ECRA== + + workbox-webpack-plugin@5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-5.1.4.tgz" + integrity sha512-PZafF4HpugZndqISi3rZ4ZK4A4DxO8rAqt2FwRptgsDx7NF8TVKP86/huHquUsRjMGQllsNdn4FNl8CD/UvKmQ== + dependencies: + "@babel/runtime" "^7.5.5" + fast-json-stable-stringify "^2.0.0" + source-map-url "^0.4.0" + upath "^1.1.2" + webpack-sources "^1.3.0" + workbox-build "^5.1.4" + + workbox-window@^5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/workbox-window/-/workbox-window-5.1.4.tgz" + integrity sha512-vXQtgTeMCUq/4pBWMfQX8Ee7N2wVC4Q7XYFqLnfbXJ2hqew/cU1uMTD2KqGEgEpE4/30luxIxgE+LkIa8glBYw== + dependencies: + workbox-core "^5.1.4" + + worker-farm@^1.7.0: + version "1.7.0" + resolved "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz" + integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== + dependencies: + errno "~0.1.7" + + worker-rpc@^0.1.0: + version "0.1.1" + resolved "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz" + integrity sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg== + dependencies: + microevent.ts "~0.1.1" + + wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + + wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + + wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + + write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + + ws@^6.2.1: + version "6.2.2" + resolved "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz" + integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== + dependencies: + async-limiter "~1.0.0" + + ws@^7.4.6: + version "7.5.9" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + + ws@~8.2.3: + version "8.2.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" + integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== + + xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + + xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + + xmlhttprequest-ssl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67" + integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== + + xtend@^4.0.0, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + + y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + + yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + + yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + + yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + + yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + + yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + + yargs@^13.3.2: + version "13.3.2" + resolved "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.2" + + yargs@^15.4.1: + version "15.4.1" + resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + + yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + + zustand@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/zustand/-/zustand-4.1.1.tgz" + integrity sha512-h4F3WMqsZgvvaE0n3lThx4MM81Ls9xebjvrABNzf5+jb3/03YjNTSgZXeyrvXDArMeV9untvWXRw1tY+ntPYbA== + dependencies: + use-sync-external-store "1.2.0" diff --git a/tracker/tracker/.eslintignore b/tracker/tracker/.eslintignore index 688a8d852..228c6174c 100644 --- a/tracker/tracker/.eslintignore +++ b/tracker/tracker/.eslintignore @@ -6,3 +6,6 @@ build .cache .eslintrc.cjs *.gen.ts + +**/*.test.ts +src/main/plugin.ts \ No newline at end of file diff --git a/tracker/tracker/.eslintrc.cjs b/tracker/tracker/.eslintrc.cjs index de1fed019..3b3ebc4fc 100644 --- a/tracker/tracker/.eslintrc.cjs +++ b/tracker/tracker/.eslintrc.cjs @@ -3,7 +3,7 @@ module.exports = { root: true, parser: '@typescript-eslint/parser', parserOptions: { - project: ['./tsconfig-base.json', './src/main/tsconfig-cjs.json'], + project: ['./tsconfig-base.json', './src/main/tsconfig-cjs.json'], // ??TODO: use correct project tsconfigRootDir: __dirname, }, plugins: ['prettier', '@typescript-eslint'], @@ -45,5 +45,6 @@ module.exports = { 'no-unused-expressions': 'off', '@typescript-eslint/no-unused-expressions': 'warn', '@typescript-eslint/no-useless-constructor': 'warn', + 'prefer-rest-params': 'off', }, }; diff --git a/tracker/tracker/CHANGELOG.md b/tracker/tracker/CHANGELOG.md new file mode 100644 index 000000000..559e4e865 --- /dev/null +++ b/tracker/tracker/CHANGELOG.md @@ -0,0 +1,20 @@ +## 5.0.0 + +- Added "tel" to supported input types +- Added `{ withCurrentTime: true }` to `tracker.getSessionURL` method which will return sessionURL with current session's timestamp +- Added Network module that captures fetch/xhr by default (with no plugin required) +- Use `timeOrigin()` instead of `performance.timing.navigationStart` in ResourceTiming messages +- Added app restart when service worker died after inactivity (mobile safari) +- **[breaking]** string dictionary to reduce session size + +## 4.1.8 + +- recalculate timeOrigin on start to prevent wrong timestamps on "sleeping" sessions + +## 4.1.7 + +- resend metadata on start + +## 4.1.6 + +- remove log that potentially caused crashed during slow initial render diff --git a/tracker/tracker/README.md b/tracker/tracker/README.md index c47f301dc..b1daf6d4d 100644 --- a/tracker/tracker/README.md +++ b/tracker/tracker/README.md @@ -2,10 +2,14 @@ The main package of the [OpenReplay](https://openreplay.com/) tracker. +## Documentation + +For launch options and available public methods, [refer to the documentation](https://docs.openreplay.com/installation/javascript-sdk#options) + ## Installation ```bash -npm i @openreplay/tracker +npm i @openreplay/tracker ``` ## Usage @@ -13,30 +17,30 @@ npm i @openreplay/tracker Initialize the package from your codebase entry point and start the tracker. You must set the `projectKey` option in the constructor. Its value can can be found in your OpenReplay dashboard under [Preferences -> Projects](https://app.openreplay.com/client/projects). ```js -import Tracker from '@openreplay/tracker'; +import Tracker from '@openreplay/tracker' const tracker = new Tracker({ projectKey: YOUR_PROJECT_KEY, -}); -tracker.start({ - userID: "Mr.Smith", - metadata: { - version: "3.5.0", - balance: "10M", - role: "admin", - } -}).then(startedSession => { - if (startedSession.success) { - console.log(startedSession) - } }) +tracker + .start({ + userID: 'Mr.Smith', + metadata: { + version: '3.5.0', + balance: '10M', + role: 'admin', + }, + }) + .then((startedSession) => { + if (startedSession.success) { + console.log(startedSession) + } + }) ``` Then you can use OpenReplay JavaScript API anywhere in your code. ```js -tracker.setUserID('my_user_id'); -tracker.setMetadata('env', 'prod'); +tracker.setUserID('my_user_id') +tracker.setMetadata('env', 'prod') ``` - -Read [our docs](https://docs.openreplay.com/) for more information. diff --git a/tracker/tracker/jest.config.js b/tracker/tracker/jest.config.js new file mode 100644 index 000000000..93fae253a --- /dev/null +++ b/tracker/tracker/jest.config.js @@ -0,0 +1,11 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +const config = { + preset: 'ts-jest', + testEnvironment: 'jsdom', + // .js file extension fix + moduleNameMapper: { + '(.+)\\.js': '$1', + }, +} + +export default config diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index 6cf707862..c45c15e4a 100644 --- a/tracker/tracker/package.json +++ b/tracker/tracker/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker", "description": "The OpenReplay tracker main package", - "version": "4.1.9", + "version": "5.0.0", "keywords": [ "logging", "replay" @@ -21,10 +21,12 @@ "compile": "node --experimental-modules --experimental-json-modules scripts/compile.cjs", "build": "npm run clean && npm run tscRun && npm run rollup && npm run compile", "prepare": "cd ../../ && husky install tracker/.husky/", - "lint-front": "lint-staged" + "lint-front": "lint-staged", + "test": "jest" }, "devDependencies": { "@babel/core": "^7.10.2", + "@jest/globals": "^29.3.1", "@rollup/plugin-babel": "^5.0.3", "@rollup/plugin-node-resolve": "^10.0.0", "@typescript-eslint/eslint-plugin": "^5.30.0", @@ -33,13 +35,16 @@ "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", "husky": "^8.0.1", + "jest": "^29.3.1", + "jest-environment-jsdom": "^29.3.1", "lint-staged": "^13.0.3", "prettier": "^2.7.1", "replace-in-files": "^2.0.3", "rollup": "^2.17.0", "rollup-plugin-terser": "^6.1.0", "semver": "^6.3.0", - "typescript": "4.6.0-dev.20211126" + "ts-jest": "^29.0.3", + "typescript": "^4.9.4" }, "dependencies": { "error-stack-parser": "^2.0.6" diff --git a/tracker/tracker/src/common/interaction.ts b/tracker/tracker/src/common/interaction.ts index 0720bf2a8..9fe543661 100644 --- a/tracker/tracker/src/common/interaction.ts +++ b/tracker/tracker/src/common/interaction.ts @@ -26,4 +26,4 @@ type Failure = { reason: string } -export type FromWorkerData = 'restart' | Failure +export type FromWorkerData = 'restart' | Failure | 'not_init' diff --git a/tracker/tracker/src/common/messages.gen.ts b/tracker/tracker/src/common/messages.gen.ts index e5f7fe28c..8f61ab917 100644 --- a/tracker/tracker/src/common/messages.gen.ts +++ b/tracker/tracker/src/common/messages.gen.ts @@ -2,8 +2,6 @@ /* eslint-disable */ export declare const enum Type { - BatchMetadata = 81, - PartitionedMessage = 82, Timestamp = 0, SetPageLocation = 4, SetViewportSize = 5, @@ -21,11 +19,11 @@ export declare const enum Type { SetInputValue = 18, SetInputChecked = 19, MouseMove = 20, + NetworkRequest = 21, ConsoleLog = 22, PageLoadTiming = 23, PageRenderTiming = 24, - JSExceptionDeprecated = 25, - RawCustomEvent = 27, + CustomEvent = 27, UserID = 28, UserAnonymousID = 29, Metadata = 30, @@ -41,6 +39,8 @@ export declare const enum Type { NgRx = 47, GraphQL = 48, PerformanceTrack = 49, + StringDict = 50, + SetNodeAttributeDict = 51, ResourceTiming = 53, ConnectionInformation = 54, SetPageVisibility = 55, @@ -59,26 +59,13 @@ export declare const enum Type { AdoptedSSDeleteRule = 75, AdoptedSSAddOwner = 76, AdoptedSSRemoveOwner = 77, - Zustand = 79, JSException = 78, + Zustand = 79, + BatchMetadata = 81, + PartitionedMessage = 82, } -export type BatchMetadata = [ - /*type:*/ Type.BatchMetadata, - /*version:*/ number, - /*pageNo:*/ number, - /*firstIndex:*/ number, - /*timestamp:*/ number, - /*location:*/ string, -] - -export type PartitionedMessage = [ - /*type:*/ Type.PartitionedMessage, - /*partNo:*/ number, - /*partTotal:*/ number, -] - export type Timestamp = [ /*type:*/ Type.Timestamp, /*timestamp:*/ number, @@ -187,6 +174,18 @@ export type MouseMove = [ /*y:*/ number, ] +export type NetworkRequest = [ + /*type:*/ Type.NetworkRequest, + /*type:*/ string, + /*method:*/ string, + /*url:*/ string, + /*request:*/ string, + /*response:*/ string, + /*status:*/ number, + /*timestamp:*/ number, + /*duration:*/ number, +] + export type ConsoleLog = [ /*type:*/ Type.ConsoleLog, /*level:*/ string, @@ -213,15 +212,8 @@ export type PageRenderTiming = [ /*timeToInteractive:*/ number, ] -export type JSExceptionDeprecated = [ - /*type:*/ Type.JSExceptionDeprecated, - /*name:*/ string, - /*message:*/ string, - /*payload:*/ string, -] - -export type RawCustomEvent = [ - /*type:*/ Type.RawCustomEvent, +export type CustomEvent = [ + /*type:*/ Type.CustomEvent, /*name:*/ string, /*payload:*/ string, ] @@ -327,6 +319,19 @@ export type PerformanceTrack = [ /*usedJSHeapSize:*/ number, ] +export type StringDict = [ + /*type:*/ Type.StringDict, + /*key:*/ number, + /*value:*/ string, +] + +export type SetNodeAttributeDict = [ + /*type:*/ Type.SetNodeAttributeDict, + /*id:*/ number, + /*nameKey:*/ number, + /*valueKey:*/ number, +] + export type ResourceTiming = [ /*type:*/ Type.ResourceTiming, /*timestamp:*/ number, @@ -456,12 +461,6 @@ export type AdoptedSSRemoveOwner = [ /*id:*/ number, ] -export type Zustand = [ - /*type:*/ Type.Zustand, - /*mutation:*/ string, - /*state:*/ string, -] - export type JSException = [ /*type:*/ Type.JSException, /*name:*/ string, @@ -470,6 +469,27 @@ export type JSException = [ /*metadata:*/ string, ] +export type Zustand = [ + /*type:*/ Type.Zustand, + /*mutation:*/ string, + /*state:*/ string, +] -type Message = BatchMetadata | PartitionedMessage | Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | ConsoleLog | PageLoadTiming | PageRenderTiming | JSExceptionDeprecated | RawCustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | ResourceTiming | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | Zustand | JSException +export type BatchMetadata = [ + /*type:*/ Type.BatchMetadata, + /*version:*/ number, + /*pageNo:*/ number, + /*firstIndex:*/ number, + /*timestamp:*/ number, + /*location:*/ string, +] + +export type PartitionedMessage = [ + /*type:*/ Type.PartitionedMessage, + /*partNo:*/ number, + /*partTotal:*/ number, +] + + +type Message = Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequest | ConsoleLog | PageLoadTiming | PageRenderTiming | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | StringDict | SetNodeAttributeDict | ResourceTiming | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | JSException | Zustand | BatchMetadata | PartitionedMessage export default Message diff --git a/tracker/tracker/src/main/app/guards.ts b/tracker/tracker/src/main/app/guards.ts index 56366a2cf..c58d03a74 100644 --- a/tracker/tracker/src/main/app/guards.ts +++ b/tracker/tracker/src/main/app/guards.ts @@ -24,21 +24,21 @@ export function isRootNode(node: Node): node is Document | DocumentFragment { } type TagTypeMap = { - HTML: HTMLHtmlElement - BODY: HTMLBodyElement - IMG: HTMLImageElement - INPUT: HTMLInputElement - TEXTAREA: HTMLTextAreaElement - SELECT: HTMLSelectElement - LABEL: HTMLLabelElement - IFRAME: HTMLIFrameElement - STYLE: HTMLStyleElement - style: SVGStyleElement - LINK: HTMLLinkElement + html: HTMLHtmlElement + body: HTMLBodyElement + img: HTMLImageElement + input: HTMLInputElement + textarea: HTMLTextAreaElement + select: HTMLSelectElement + label: HTMLLabelElement + iframe: HTMLIFrameElement + style: HTMLStyleElement | SVGStyleElement + link: HTMLLinkElement } export function hasTag( el: Node, tagName: T, ): el is TagTypeMap[typeof tagName] { - return el.nodeName === tagName + // @ts-ignore + return el.localName === tagName } diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index bec00d01b..f6d947dd9 100644 --- a/tracker/tracker/src/main/app/index.ts +++ b/tracker/tracker/src/main/app/index.ts @@ -1,5 +1,5 @@ import type Message from './messages.gen.js' -import { Timestamp, Metadata, UserID } from './messages.gen.js' +import { Timestamp, Metadata, UserID, Type as MType } from './messages.gen.js' import { now, adjustTimeOrigin, deprecationWarn } from '../utils.js' import Nodes from './nodes.js' import Observer from './observer/top_observer.js' @@ -165,6 +165,8 @@ export default class App { if (data === 'restart') { this.stop(false) this.start({}, true) + } else if (data === 'not_init') { + console.warn('WebWorker: writer not initialised. Restarting tracker') } else if (data.type === 'failure') { this.stop(false) this._debug('worker_failed', data.reason) @@ -199,10 +201,22 @@ export default class App { this.debug.error('OpenReplay error: ', context, e) } + private _usingOldFetchPlugin = false send(message: Message, urgent = false): void { if (this.activityState === ActivityState.NotActive) { return } + // === Back compatibility with Fetch/Axios plugins === + if (message[0] === MType.Fetch) { + this._usingOldFetchPlugin = true + deprecationWarn('Fetch plugin', "'network' init option", '/installation/network-options') + deprecationWarn('Axios plugin', "'network' init option", '/installation/network-options') + } + if (this._usingOldFetchPlugin && message[0] === MType.NetworkRequest) { + return + } + // ==================================================== + this.messages.push(message) // TODO: commit on start if there were `urgent` sends; // Clarify where urgent can be used for; @@ -312,18 +326,25 @@ export default class App { return this.session.getInfo().sessionID || undefined } - getSessionURL(): string | undefined { - const { projectID, sessionID } = this.session.getInfo() + getSessionURL(options?: { withCurrentTime?: boolean }): string | undefined { + const { projectID, sessionID, timestamp } = this.session.getInfo() if (!projectID || !sessionID) { this.debug.error('OpenReplay error: Unable to build session URL') return undefined } const ingest = this.options.ingestPoint - const isSaas = ingest === DEFAULT_INGEST_POINT + const isSaas = /api\.openreplay\.com/.test(ingest) - const projectPath = isSaas ? ingest.replace('api', 'app') : ingest + const projectPath = isSaas ? 'https://openreplay.com/ingest' : ingest - return projectPath.replace(/ingest$/, `${projectID}/session/${sessionID}`) + const url = projectPath.replace(/ingest$/, `${projectID}/session/${sessionID}`) + + if (options?.withCurrentTime) { + const jumpTo = now() - timestamp + return `${url}?jumpto=${jumpTo}` + } + + return url } getHost(): string { @@ -445,7 +466,7 @@ export default class App { return Promise.reject('no worker found after start request (this might not happen)') } if (this.activityState === ActivityState.NotActive) { - return Promise.reject('Tracker stopped during authorisation') + return Promise.reject('Tracker stopped during authorization') } const { token, diff --git a/tracker/tracker/src/main/app/messages.gen.ts b/tracker/tracker/src/main/app/messages.gen.ts index b9f235588..bdec57a0f 100644 --- a/tracker/tracker/src/main/app/messages.gen.ts +++ b/tracker/tracker/src/main/app/messages.gen.ts @@ -2,37 +2,9 @@ /* eslint-disable */ import * as Messages from '../../common/messages.gen.js' -export { default } from '../../common/messages.gen.js' +export { default, Type } from '../../common/messages.gen.js' -export function BatchMetadata( - version: number, - pageNo: number, - firstIndex: number, - timestamp: number, - location: string, -): Messages.BatchMetadata { - return [ - Messages.Type.BatchMetadata, - version, - pageNo, - firstIndex, - timestamp, - location, - ] -} - -export function PartitionedMessage( - partNo: number, - partTotal: number, -): Messages.PartitionedMessage { - return [ - Messages.Type.PartitionedMessage, - partNo, - partTotal, - ] -} - export function Timestamp( timestamp: number, ): Messages.Timestamp { @@ -232,6 +204,29 @@ export function MouseMove( ] } +export function NetworkRequest( + type: string, + method: string, + url: string, + request: string, + response: string, + status: number, + timestamp: number, + duration: number, +): Messages.NetworkRequest { + return [ + Messages.Type.NetworkRequest, + type, + method, + url, + request, + response, + status, + timestamp, + duration, + ] +} + export function ConsoleLog( level: string, value: string, @@ -281,25 +276,12 @@ export function PageRenderTiming( ] } -export function JSExceptionDeprecated( - name: string, - message: string, - payload: string, -): Messages.JSExceptionDeprecated { - return [ - Messages.Type.JSExceptionDeprecated, - name, - message, - payload, - ] -} - -export function RawCustomEvent( +export function CustomEvent( name: string, payload: string, -): Messages.RawCustomEvent { +): Messages.CustomEvent { return [ - Messages.Type.RawCustomEvent, + Messages.Type.CustomEvent, name, payload, ] @@ -492,6 +474,30 @@ export function PerformanceTrack( ] } +export function StringDict( + key: number, + value: string, +): Messages.StringDict { + return [ + Messages.Type.StringDict, + key, + value, + ] +} + +export function SetNodeAttributeDict( + id: number, + nameKey: number, + valueKey: number, +): Messages.SetNodeAttributeDict { + return [ + Messages.Type.SetNodeAttributeDict, + id, + nameKey, + valueKey, + ] +} + export function ResourceTiming( timestamp: number, duration: number, @@ -732,17 +738,6 @@ export function AdoptedSSRemoveOwner( ] } -export function Zustand( - mutation: string, - state: string, -): Messages.Zustand { - return [ - Messages.Type.Zustand, - mutation, - state, - ] -} - export function JSException( name: string, message: string, @@ -758,3 +753,42 @@ export function JSException( ] } +export function Zustand( + mutation: string, + state: string, +): Messages.Zustand { + return [ + Messages.Type.Zustand, + mutation, + state, + ] +} + +export function BatchMetadata( + version: number, + pageNo: number, + firstIndex: number, + timestamp: number, + location: string, +): Messages.BatchMetadata { + return [ + Messages.Type.BatchMetadata, + version, + pageNo, + firstIndex, + timestamp, + location, + ] +} + +export function PartitionedMessage( + partNo: number, + partTotal: number, +): Messages.PartitionedMessage { + return [ + Messages.Type.PartitionedMessage, + partNo, + partTotal, + ] +} + diff --git a/tracker/tracker/src/main/app/messages.ts b/tracker/tracker/src/main/app/messages.ts deleted file mode 100644 index 4b740eddb..000000000 --- a/tracker/tracker/src/main/app/messages.ts +++ /dev/null @@ -1,339 +0,0 @@ -// Auto-generated, do not edit - -import * as Messages from '../../common/messages.gen.js' -export { default } from '../../common/messages.gen.js' - -export function BatchMetadata( - version: number, - pageNo: number, - firstIndex: number, - timestamp: number, - location: string, -): Messages.BatchMetadata { - return [Messages.Type.BatchMetadata, version, pageNo, firstIndex, timestamp, location] -} - -export function PartitionedMessage(partNo: number, partTotal: number): Messages.PartitionedMessage { - return [Messages.Type.PartitionedMessage, partNo, partTotal] -} - -export function Timestamp(timestamp: number): Messages.Timestamp { - return [Messages.Type.Timestamp, timestamp] -} - -export function SetPageLocation( - url: string, - referrer: string, - navigationStart: number, -): Messages.SetPageLocation { - return [Messages.Type.SetPageLocation, url, referrer, navigationStart] -} - -export function SetViewportSize(width: number, height: number): Messages.SetViewportSize { - return [Messages.Type.SetViewportSize, width, height] -} - -export function SetViewportScroll(x: number, y: number): Messages.SetViewportScroll { - return [Messages.Type.SetViewportScroll, x, y] -} - -export function CreateDocument(): Messages.CreateDocument { - return [Messages.Type.CreateDocument] -} - -export function CreateElementNode( - id: number, - parentID: number, - index: number, - tag: string, - svg: boolean, -): Messages.CreateElementNode { - return [Messages.Type.CreateElementNode, id, parentID, index, tag, svg] -} - -export function CreateTextNode( - id: number, - parentID: number, - index: number, -): Messages.CreateTextNode { - return [Messages.Type.CreateTextNode, id, parentID, index] -} - -export function MoveNode(id: number, parentID: number, index: number): Messages.MoveNode { - return [Messages.Type.MoveNode, id, parentID, index] -} - -export function RemoveNode(id: number): Messages.RemoveNode { - return [Messages.Type.RemoveNode, id] -} - -export function SetNodeAttribute( - id: number, - name: string, - value: string, -): Messages.SetNodeAttribute { - return [Messages.Type.SetNodeAttribute, id, name, value] -} - -export function RemoveNodeAttribute(id: number, name: string): Messages.RemoveNodeAttribute { - return [Messages.Type.RemoveNodeAttribute, id, name] -} - -export function SetNodeData(id: number, data: string): Messages.SetNodeData { - return [Messages.Type.SetNodeData, id, data] -} - -export function SetNodeScroll(id: number, x: number, y: number): Messages.SetNodeScroll { - return [Messages.Type.SetNodeScroll, id, x, y] -} - -export function SetInputTarget(id: number, label: string): Messages.SetInputTarget { - return [Messages.Type.SetInputTarget, id, label] -} - -export function SetInputValue(id: number, value: string, mask: number): Messages.SetInputValue { - return [Messages.Type.SetInputValue, id, value, mask] -} - -export function SetInputChecked(id: number, checked: boolean): Messages.SetInputChecked { - return [Messages.Type.SetInputChecked, id, checked] -} - -export function MouseMove(x: number, y: number): Messages.MouseMove { - return [Messages.Type.MouseMove, x, y] -} - -export function ConsoleLog(level: string, value: string): Messages.ConsoleLog { - return [Messages.Type.ConsoleLog, level, value] -} - -export function PageLoadTiming( - requestStart: number, - responseStart: number, - responseEnd: number, - domContentLoadedEventStart: number, - domContentLoadedEventEnd: number, - loadEventStart: number, - loadEventEnd: number, - firstPaint: number, - firstContentfulPaint: number, -): Messages.PageLoadTiming { - return [ - Messages.Type.PageLoadTiming, - requestStart, - responseStart, - responseEnd, - domContentLoadedEventStart, - domContentLoadedEventEnd, - loadEventStart, - loadEventEnd, - firstPaint, - firstContentfulPaint, - ] -} - -export function PageRenderTiming( - speedIndex: number, - visuallyComplete: number, - timeToInteractive: number, -): Messages.PageRenderTiming { - return [Messages.Type.PageRenderTiming, speedIndex, visuallyComplete, timeToInteractive] -} - -export function JSException( - name: string, - message: string, - payload: string, - metadata: string, -): Messages.JSException { - return [Messages.Type.JSException, name, message, payload, metadata] -} - -export function RawCustomEvent(name: string, payload: string): Messages.RawCustomEvent { - return [Messages.Type.RawCustomEvent, name, payload] -} - -export function UserID(id: string): Messages.UserID { - return [Messages.Type.UserID, id] -} - -export function UserAnonymousID(id: string): Messages.UserAnonymousID { - return [Messages.Type.UserAnonymousID, id] -} - -export function Metadata(key: string, value: string): Messages.Metadata { - return [Messages.Type.Metadata, key, value] -} - -export function CSSInsertRule(id: number, rule: string, index: number): Messages.CSSInsertRule { - return [Messages.Type.CSSInsertRule, id, rule, index] -} - -export function CSSDeleteRule(id: number, index: number): Messages.CSSDeleteRule { - return [Messages.Type.CSSDeleteRule, id, index] -} - -export function Fetch( - method: string, - url: string, - request: string, - response: string, - status: number, - timestamp: number, - duration: number, -): Messages.Fetch { - return [Messages.Type.Fetch, method, url, request, response, status, timestamp, duration] -} - -export function Profiler( - name: string, - duration: number, - args: string, - result: string, -): Messages.Profiler { - return [Messages.Type.Profiler, name, duration, args, result] -} - -export function OTable(key: string, value: string): Messages.OTable { - return [Messages.Type.OTable, key, value] -} - -export function StateAction(type: string): Messages.StateAction { - return [Messages.Type.StateAction, type] -} - -export function Redux(action: string, state: string, duration: number): Messages.Redux { - return [Messages.Type.Redux, action, state, duration] -} - -export function Vuex(mutation: string, state: string): Messages.Vuex { - return [Messages.Type.Vuex, mutation, state] -} - -export function MobX(type: string, payload: string): Messages.MobX { - return [Messages.Type.MobX, type, payload] -} - -export function NgRx(action: string, state: string, duration: number): Messages.NgRx { - return [Messages.Type.NgRx, action, state, duration] -} - -export function GraphQL( - operationKind: string, - operationName: string, - variables: string, - response: string, -): Messages.GraphQL { - return [Messages.Type.GraphQL, operationKind, operationName, variables, response] -} - -export function PerformanceTrack( - frames: number, - ticks: number, - totalJSHeapSize: number, - usedJSHeapSize: number, -): Messages.PerformanceTrack { - return [Messages.Type.PerformanceTrack, frames, ticks, totalJSHeapSize, usedJSHeapSize] -} - -export function ResourceTiming( - timestamp: number, - duration: number, - ttfb: number, - headerSize: number, - encodedBodySize: number, - decodedBodySize: number, - url: string, - initiator: string, -): Messages.ResourceTiming { - return [ - Messages.Type.ResourceTiming, - timestamp, - duration, - ttfb, - headerSize, - encodedBodySize, - decodedBodySize, - url, - initiator, - ] -} - -export function ConnectionInformation( - downlink: number, - type: string, -): Messages.ConnectionInformation { - return [Messages.Type.ConnectionInformation, downlink, type] -} - -export function SetPageVisibility(hidden: boolean): Messages.SetPageVisibility { - return [Messages.Type.SetPageVisibility, hidden] -} - -export function LongTask( - timestamp: number, - duration: number, - context: number, - containerType: number, - containerSrc: string, - containerId: string, - containerName: string, -): Messages.LongTask { - return [ - Messages.Type.LongTask, - timestamp, - duration, - context, - containerType, - containerSrc, - containerId, - containerName, - ] -} - -export function SetNodeAttributeURLBased( - id: number, - name: string, - value: string, - baseURL: string, -): Messages.SetNodeAttributeURLBased { - return [Messages.Type.SetNodeAttributeURLBased, id, name, value, baseURL] -} - -export function SetCSSDataURLBased( - id: number, - data: string, - baseURL: string, -): Messages.SetCSSDataURLBased { - return [Messages.Type.SetCSSDataURLBased, id, data, baseURL] -} - -export function TechnicalInfo(type: string, value: string): Messages.TechnicalInfo { - return [Messages.Type.TechnicalInfo, type, value] -} - -export function CustomIssue(name: string, payload: string): Messages.CustomIssue { - return [Messages.Type.CustomIssue, name, payload] -} - -export function CSSInsertRuleURLBased( - id: number, - rule: string, - index: number, - baseURL: string, -): Messages.CSSInsertRuleURLBased { - return [Messages.Type.CSSInsertRuleURLBased, id, rule, index, baseURL] -} - -export function MouseClick( - id: number, - hesitationTime: number, - label: string, - selector: string, -): Messages.MouseClick { - return [Messages.Type.MouseClick, id, hesitationTime, label, selector] -} - -export function CreateIFrameDocument(frameID: number, id: number): Messages.CreateIFrameDocument { - return [Messages.Type.CreateIFrameDocument, frameID, id] -} diff --git a/tracker/tracker/src/main/app/observer/observer.ts b/tracker/tracker/src/main/app/observer/observer.ts index dec086509..c13739622 100644 --- a/tracker/tracker/src/main/app/observer/observer.ts +++ b/tracker/tracker/src/main/app/observer/observer.ts @@ -144,7 +144,7 @@ export default abstract class Observer { } if ( name === 'value' && - hasTag(node, 'INPUT') && + hasTag(node, 'input') && node.type !== 'button' && node.type !== 'reset' && node.type !== 'submit' @@ -155,7 +155,7 @@ export default abstract class Observer { this.app.send(RemoveNodeAttribute(id, name)) return } - if (name === 'style' || (name === 'href' && hasTag(node, 'LINK'))) { + if (name === 'style' || (name === 'href' && hasTag(node, 'link'))) { this.app.send(SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref())) return } @@ -166,7 +166,7 @@ export default abstract class Observer { } private sendNodeData(id: number, parentElement: Element, data: string): void { - if (hasTag(parentElement, 'STYLE') || hasTag(parentElement, 'style')) { + if (hasTag(parentElement, 'style')) { this.app.send(SetCSSDataURLBased(id, data, this.app.getBaseHref())) return } @@ -242,7 +242,7 @@ export default abstract class Observer { // Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before) // TODO: get rid of "special" cases (there is an issue with CreateDocument altered behaviour though) // TODO: Clean the logic (though now it workd fine) - if (!hasTag(node, 'HTML') || !this.isTopContext) { + if (!hasTag(node, 'html') || !this.isTopContext) { if (parent === null) { // Sometimes one observation contains attribute mutations for the removimg node, which gets ignored here. // That shouldn't affect the visual rendering ( should it? maybe when transition applied? ) @@ -352,7 +352,7 @@ export default abstract class Observer { this.clear() } - // ISSSUE (nodeToBinde should be the same as node. Look at the comment about 0-node at the beginning of the file.) + // ISSSUE (nodeToBinde should be the same as node in all cases. Look at the comment about 0-node at the beginning of the file.) // TODO: use one observer instance for all iframes/shadowRoots (composition instiad of inheritance) protected observeRoot( node: Node, diff --git a/tracker/tracker/src/main/app/observer/top_observer.ts b/tracker/tracker/src/main/app/observer/top_observer.ts index be1a1ce33..38944c5c9 100644 --- a/tracker/tracker/src/main/app/observer/top_observer.ts +++ b/tracker/tracker/src/main/app/observer/top_observer.ts @@ -34,7 +34,7 @@ export default class TopObserver extends Observer { // IFrames this.app.nodes.attachNodeCallback((node) => { if ( - hasTag(node, 'IFRAME') && + hasTag(node, 'iframe') && ((this.options.captureIFrames && !hasOpenreplayAttribute(node, 'obscured')) || hasOpenreplayAttribute(node, 'capture')) ) { diff --git a/tracker/tracker/src/main/index.ts b/tracker/tracker/src/main/index.ts index 069336ef7..eae1c867c 100644 --- a/tracker/tracker/src/main/index.ts +++ b/tracker/tracker/src/main/index.ts @@ -1,7 +1,7 @@ import App, { DEFAULT_INGEST_POINT } from './app/index.js' export { default as App } from './app/index.js' -import { UserAnonymousID, RawCustomEvent, CustomIssue } from './app/messages.gen.js' +import { UserAnonymousID, CustomEvent, CustomIssue } from './app/messages.gen.js' import * as _Messages from './app/messages.gen.js' export const Messages = _Messages export { SanitizeLevel } from './app/sanitizer.js' @@ -22,6 +22,7 @@ import Viewport from './modules/viewport.js' import CSSRules from './modules/cssrules.js' import Focus from './modules/focus.js' import Fonts from './modules/fonts.js' +import Network from './modules/network.js' import ConstructedStyleSheets from './modules/constructedStyleSheets.js' import { IN_BROWSER, deprecationWarn, DOCS_HOST } from './utils.js' @@ -31,6 +32,7 @@ import type { Options as ExceptionOptions } from './modules/exception.js' import type { Options as InputOptions } from './modules/input.js' import type { Options as PerformanceOptions } from './modules/performance.js' import type { Options as TimingOptions } from './modules/timing.js' +import type { Options as NetworkOptions } from './modules/network.js' import type { StartOptions } from './app/index.js' //TODO: unique options init import type { StartPromiseReturn } from './app/index.js' @@ -43,11 +45,12 @@ export type Options = Partial< sessionToken?: string respectDoNotTrack?: boolean autoResetOnWindowOpen?: boolean + network?: NetworkOptions // dev only __DISABLE_SECURE_MODE?: boolean } -const DOCS_SETUP = '/installation/setup-or' +const DOCS_SETUP = '/installation/javascript-sdk' function processOptions(obj: any): obj is Options { if (obj == null) { @@ -127,6 +130,7 @@ export default class API { Scroll(app) Focus(app) Fonts(app) + Network(app, options.network) ;(window as any).__OPENREPLAY__ = this if (options.autoResetOnWindowOpen) { @@ -212,11 +216,11 @@ export default class API { return this.getSessionID() } - getSessionURL(): string | undefined { + getSessionURL(options?: { withCurrentTime?: boolean }): string | undefined { if (this.app === null) { return undefined } - return this.app.getSessionURL() + return this.app.getSessionURL(options) } setUserID(id: string): void { @@ -259,7 +263,7 @@ export default class API { } catch (e) { return } - this.app.send(RawCustomEvent(key, payload)) + this.app.send(CustomEvent(key, payload)) } } } diff --git a/tracker/tracker/src/main/modules/cssrules.ts b/tracker/tracker/src/main/modules/cssrules.ts index 08db23af8..8636c68c0 100644 --- a/tracker/tracker/src/main/modules/cssrules.ts +++ b/tracker/tracker/src/main/modules/cssrules.ts @@ -87,7 +87,7 @@ export default function (app: App | null) { app.observer.attachContextCallback(patchContext) app.nodes.attachNodeCallback((node: Node): void => { - if (!(hasTag(node, 'STYLE') || hasTag(node, 'style')) || !node.sheet) { + if (!hasTag(node, 'style') || !node.sheet) { return } if (node.textContent !== null && node.textContent.trim().length > 0) { diff --git a/tracker/tracker/src/main/modules/focus.ts b/tracker/tracker/src/main/modules/focus.ts index d9db865db..3c95fd774 100644 --- a/tracker/tracker/src/main/modules/focus.ts +++ b/tracker/tracker/src/main/modules/focus.ts @@ -12,7 +12,7 @@ export default function (app: App): void { let blurred = false app.nodes.attachNodeCallback((node) => { - if (!hasTag(node, 'BODY')) { + if (!hasTag(node, 'body')) { return } app.nodes.attachNodeListener(node, 'focus', (e: FocusEvent): void => { @@ -35,7 +35,7 @@ export default function (app: App): void { }) app.attachStartCallback(() => { let elem = document.activeElement - while (elem && hasTag(elem, 'IFRAME') && elem.contentDocument) { + while (elem && hasTag(elem, 'iframe') && elem.contentDocument) { elem = elem.contentDocument.activeElement } if (elem && elem !== elem.ownerDocument.body) { diff --git a/tracker/tracker/src/main/modules/fonts.ts b/tracker/tracker/src/main/modules/fonts.ts index 1dfb6069c..2b39db7e5 100644 --- a/tracker/tracker/src/main/modules/fonts.ts +++ b/tracker/tracker/src/main/modules/fonts.ts @@ -45,22 +45,24 @@ export default function (app: App) { app.observer.attachContextCallback(patchWindow) patchWindow(window) - app.nodes.attachNodeCallback((node) => { - if (!isDocument(node)) { - return - } - const ffDataArr = docFonts.get(node) - if (!ffDataArr) { - return - } + app.nodes.attachNodeCallback( + app.safe((node) => { + if (!isDocument(node)) { + return + } + const ffDataArr = docFonts.get(node) + if (!ffDataArr) { + return + } - const parentID = node.defaultView === window ? 0 : app.nodes.getID(node) - if (parentID === undefined) { - return - } + const parentID = node.defaultView === window ? 0 : app.nodes.getID(node) + if (parentID === undefined) { + return + } - ffDataArr.forEach((ffData) => { - app.send(LoadFontFace(parentID, ...ffData)) - }) - }) + ffDataArr.forEach((ffData) => { + app.send(LoadFontFace(parentID, ...ffData)) + }) + }), + ) } diff --git a/tracker/tracker/src/main/modules/img.ts b/tracker/tracker/src/main/modules/img.ts index c6dedc6e0..b5dddb293 100644 --- a/tracker/tracker/src/main/modules/img.ts +++ b/tracker/tracker/src/main/modules/img.ts @@ -5,14 +5,15 @@ import { hasTag } from '../app/guards.js' function resolveURL(url: string, location: Location = document.location) { url = url.trim() - if (url.startsWith('/')) { - return location.origin + url - } else if ( + if ( + url.startsWith('//') || url.startsWith('http://') || url.startsWith('https://') || url.startsWith('data:') // any other possible value here? https://bugzilla.mozilla.org/show_bug.cgi?id=1758035 ) { return url + } else if (url.startsWith('/')) { + return location.origin + url } else { return location.origin + location.pathname + url } @@ -104,7 +105,7 @@ export default function (app: App): void { }) app.nodes.attachNodeCallback((node: Node): void => { - if (!hasTag(node, 'IMG')) { + if (!hasTag(node, 'img')) { return } app.nodes.attachNodeListener(node, 'error', () => sendImgError(node)) diff --git a/tracker/tracker/src/main/modules/input.ts b/tracker/tracker/src/main/modules/input.ts index 7b981576a..15acecaa9 100644 --- a/tracker/tracker/src/main/modules/input.ts +++ b/tracker/tracker/src/main/modules/input.ts @@ -3,15 +3,15 @@ import { normSpaces, IN_BROWSER, getLabelAttribute } from '../utils.js' import { hasTag } from '../app/guards.js' import { SetInputTarget, SetInputValue, SetInputChecked } from '../app/messages.gen.js' -const INPUT_TYPES = ['text', 'password', 'email', 'search', 'number', 'range', 'date'] +const INPUT_TYPES = ['text', 'password', 'email', 'search', 'number', 'range', 'date', 'tel'] // TODO: take into consideration "contenteditable" attribute type TextEditableElement = HTMLInputElement | HTMLTextAreaElement function isTextEditable(node: any): node is TextEditableElement { - if (hasTag(node, 'TEXTAREA')) { + if (hasTag(node, 'textarea')) { return true } - if (!hasTag(node, 'INPUT')) { + if (!hasTag(node, 'input')) { return false } @@ -19,7 +19,7 @@ function isTextEditable(node: any): node is TextEditableElement { } function isCheckable(node: any): node is HTMLInputElement { - if (!hasTag(node, 'INPUT')) { + if (!hasTag(node, 'input')) { return false } const type = node.type @@ -31,7 +31,7 @@ const labelElementFor: (element: TextEditableElement) => HTMLLabelElement | unde ? (node) => { let p: Node | null = node while ((p = p.parentNode) !== null) { - if (hasTag(p, 'LABEL')) { + if (hasTag(p, 'label')) { return p } } @@ -43,7 +43,7 @@ const labelElementFor: (element: TextEditableElement) => HTMLLabelElement | unde : (node) => { let p: Node | null = node while ((p = p.parentNode) !== null) { - if (hasTag(p, 'LABEL')) { + if (hasTag(p, 'label')) { return p } } @@ -142,12 +142,8 @@ export default function (app: App, opts: Partial): void { app.ticker.attach((): void => { inputValues.forEach((value, id) => { - const node = app.nodes.getNode(id) - if (!node) return - if (!isTextEditable(node)) { - inputValues.delete(id) - return - } + const node = app.nodes.getNode(id) as HTMLInputElement + if (!node) return inputValues.delete(id) if (value !== node.value) { inputValues.set(id, node.value) if (!registeredTargets.has(id)) { @@ -158,12 +154,8 @@ export default function (app: App, opts: Partial): void { } }) checkableValues.forEach((checked, id) => { - const node = app.nodes.getNode(id) - if (!node) return - if (!isCheckable(node)) { - checkableValues.delete(id) - return - } + const node = app.nodes.getNode(id) as HTMLInputElement + if (!node) return checkableValues.delete(id) if (checked !== node.checked) { checkableValues.set(id, node.checked) app.send(SetInputChecked(id, node.checked)) @@ -179,7 +171,7 @@ export default function (app: App, opts: Partial): void { return } // TODO: support multiple select (?): use selectedOptions; Need send target? - if (hasTag(node, 'SELECT')) { + if (hasTag(node, 'select')) { sendInputValue(id, node) app.attachEventListener(node, 'change', () => { sendInputValue(id, node) diff --git a/tracker/tracker/src/main/modules/mouse.ts b/tracker/tracker/src/main/modules/mouse.ts index 15c5e786c..b00d6d304 100644 --- a/tracker/tracker/src/main/modules/mouse.ts +++ b/tracker/tracker/src/main/modules/mouse.ts @@ -86,7 +86,7 @@ export default function (app: App): void { if (dl !== null) { return dl } - if (hasTag(target, 'INPUT')) { + if (hasTag(target, 'input')) { return getInputLabel(target) } if (isClickable(target)) { diff --git a/tracker/tracker/src/main/modules/network.ts b/tracker/tracker/src/main/modules/network.ts new file mode 100644 index 000000000..fdaed16c6 --- /dev/null +++ b/tracker/tracker/src/main/modules/network.ts @@ -0,0 +1,313 @@ +import type App from '../app/index.js' +import { NetworkRequest } from '../app/messages.gen.js' +import { getTimeOrigin } from '../utils.js' + +type WindowFetch = typeof window.fetch +type XHRRequestBody = Parameters[0] +type FetchRequestBody = RequestInit['body'] + +// Request: +// declare const enum BodyType { +// Blob = "Blob", +// ArrayBuffer = "ArrayBuffer", +// TypedArray = "TypedArray", +// DataView = "DataView", +// FormData = "FormData", +// URLSearchParams = "URLSearchParams", +// Document = "Document", // XHR only +// ReadableStream = "ReadableStream", // Fetch only +// Literal = "literal", +// Unknown = "unk", +// } +// XHRResponse body: ArrayBuffer, a Blob, a Document, a JavaScript Object, or a string + +// TODO: extract maximum of useful information from any type of Request/Responce bodies +// function objectifyBody(body: any): RequestBody { +// if (body instanceof Blob) { +// return { +// body: `; size: ${body.size}`, +// bodyType: BodyType.Blob, +// } +// } +// return { +// body, +// bodyType: BodyType.Literal, +// } +// } + +interface RequestData { + body: XHRRequestBody | FetchRequestBody + headers: Record +} +interface ResponseData { + body: any + headers: Record +} + +interface RequestResponseData { + readonly status: number + readonly method: string + url: string + request: RequestData + response: ResponseData +} + +interface XHRRequestData { + body: XHRRequestBody + headers: Record +} + +function getXHRRequestDataObject(xhr: XMLHttpRequest): XHRRequestData { + // @ts-ignore this is 3x faster than using Map + if (!xhr.__or_req_data__) { + // @ts-ignore + xhr.__or_req_data__ = { body: undefined, headers: {} } + } + // @ts-ignore + return xhr.__or_req_data__ +} + +function strMethod(method?: string) { + return typeof method === 'string' ? method.toUpperCase() : 'GET' +} + +type Sanitizer = (data: RequestResponseData) => RequestResponseData | null + +export interface Options { + sessionTokenHeader: string | boolean + failuresOnly: boolean + ignoreHeaders: Array | boolean + capturePayload: boolean + sanitizer?: Sanitizer +} + +export default function (app: App, opts: Partial = {}) { + const options: Options = Object.assign( + { + failuresOnly: false, + ignoreHeaders: ['Cookie', 'Set-Cookie', 'Authorization'], + capturePayload: false, + sessionTokenHeader: false, + }, + opts, + ) + + const ignoreHeaders = options.ignoreHeaders + const isHIgnored = Array.isArray(ignoreHeaders) + ? (name: string) => ignoreHeaders.includes(name) + : () => ignoreHeaders + + const stHeader = + options.sessionTokenHeader === true ? 'X-OpenReplay-SessionToken' : options.sessionTokenHeader + function setSessionTokenHeader(setRequestHeader: (name: string, value: string) => void) { + if (stHeader) { + const sessionToken = app.getSessionToken() + if (sessionToken) { + app.safe(setRequestHeader)(stHeader, sessionToken) + } + } + } + + function sanitize(reqResInfo: RequestResponseData) { + if (!options.capturePayload) { + delete reqResInfo.request.body + delete reqResInfo.response.body + } + if (options.sanitizer) { + const resBody = reqResInfo.response.body + if (typeof resBody === 'string') { + // Parse response in order to have handy view in sanitisation function + try { + reqResInfo.response.body = JSON.parse(resBody) + } catch {} + } + return options.sanitizer(reqResInfo) + } + return reqResInfo + } + + function stringify(r: RequestData | ResponseData): string { + if (r && typeof r.body !== 'string') { + try { + r.body = JSON.stringify(r.body) + } catch { + r.body = '' + app.notify.warn("Openreplay fetch couldn't stringify body:", r.body) + } + } + return JSON.stringify(r) + } + + /* ====== Fetch ====== */ + const origFetch = window.fetch.bind(window) as WindowFetch + window.fetch = (input, init = {}) => { + if (!(typeof input === 'string' || input instanceof URL) || app.isServiceURL(String(input))) { + return origFetch(input, init) + } + + setSessionTokenHeader(function (name, value) { + if (init.headers === undefined) { + init.headers = {} + } + if (init.headers instanceof Headers) { + init.headers.append(name, value) + } else if (Array.isArray(init.headers)) { + init.headers.push([name, value]) + } else { + init.headers[name] = value + } + }) + + const startTime = performance.now() + return origFetch(input, init).then((response) => { + const duration = performance.now() - startTime + if (options.failuresOnly && response.status < 400) { + return response + } + + const r = response.clone() + r.text() + .then((text) => { + const reqHs: Record = {} + const resHs: Record = {} + if (ignoreHeaders !== true) { + // request headers + const writeReqHeader = ([n, v]: [string, string]) => { + if (!isHIgnored(n)) { + reqHs[n] = v + } + } + if (init.headers instanceof Headers) { + init.headers.forEach((v, n) => writeReqHeader([n, v])) + } else if (Array.isArray(init.headers)) { + init.headers.forEach(writeReqHeader) + } else if (typeof init.headers === 'object') { + Object.entries(init.headers).forEach(writeReqHeader) + } + // response headers + r.headers.forEach((v, n) => { + if (!isHIgnored(n)) resHs[n] = v + }) + } + const method = strMethod(init.method) + + const reqResInfo = sanitize({ + url: String(input), + method, + status: r.status, + request: { + headers: reqHs, + body: init.body, + }, + response: { + headers: resHs, + body: text, + }, + }) + if (!reqResInfo) { + return + } + + app.send( + NetworkRequest( + 'fetch', + method, + String(reqResInfo.url), + stringify(reqResInfo.request), + stringify(reqResInfo.response), + r.status, + startTime + getTimeOrigin(), + duration, + ), + ) + }) + .catch((e) => app.debug.error('Could not process Fetch response:', e)) + + return response + }) + } + /* ====== <> ====== */ + + /* ====== XHR ====== */ + const nativeOpen = XMLHttpRequest.prototype.open + XMLHttpRequest.prototype.open = function (initMethod, url) { + const xhr = this + setSessionTokenHeader((name, value) => xhr.setRequestHeader(name, value)) + + let startTime = 0 + xhr.addEventListener('loadstart', (e) => { + startTime = e.timeStamp + }) + xhr.addEventListener( + 'load', + app.safe((e) => { + const { headers: reqHs, body: reqBody } = getXHRRequestDataObject(xhr) + const duration = startTime > 0 ? e.timeStamp - startTime : 0 + + const hString: string | null = ignoreHeaders ? '' : xhr.getAllResponseHeaders() // might be null (though only if no response received though) + const resHs = hString + ? hString + .split('\r\n') + .map((h) => h.split(':')) + .filter((entry) => !isHIgnored(entry[0])) + .reduce( + (hds, [name, value]) => ({ ...hds, [name]: value }), + {} as Record, + ) + : {} + + const method = strMethod(initMethod) + const reqResInfo = sanitize({ + url: String(url), + method, + status: xhr.status, + request: { + headers: reqHs, + body: reqBody, + }, + response: { + headers: resHs, + body: xhr.response, + }, + }) + if (!reqResInfo) { + return + } + + app.send( + NetworkRequest( + 'xhr', + method, + String(reqResInfo.url), + stringify(reqResInfo.request), + stringify(reqResInfo.response), + xhr.status, + startTime + getTimeOrigin(), + duration, + ), + ) + }), + ) + + //TODO: handle error (though it has no Error API nor any useful information) + //xhr.addEventListener('error', (e) => {}) + return nativeOpen.apply(this, arguments) + } + const nativeSend = XMLHttpRequest.prototype.send + XMLHttpRequest.prototype.send = function (body) { + const rdo = getXHRRequestDataObject(this) + rdo.body = body + + return nativeSend.apply(this, arguments) + } + const nativeSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader + XMLHttpRequest.prototype.setRequestHeader = function (name, value) { + if (!isHIgnored(name)) { + const rdo = getXHRRequestDataObject(this) + rdo.headers[name] = value + } + + return nativeSetRequestHeader.apply(this, arguments) + } + /* ====== <> ====== */ +} diff --git a/tracker/tracker/src/main/modules/timing.ts b/tracker/tracker/src/main/modules/timing.ts index f2d2cbf11..54c094126 100644 --- a/tracker/tracker/src/main/modules/timing.ts +++ b/tracker/tracker/src/main/modules/timing.ts @@ -1,6 +1,6 @@ import type App from '../app/index.js' import { hasTag } from '../app/guards.js' -import { isURL } from '../utils.js' +import { isURL, getTimeOrigin } from '../utils.js' import { ResourceTiming, PageLoadTiming, PageRenderTiming } from '../app/messages.gen.js' // Inspired by https://github.com/WPO-Foundation/RUM-SpeedIndex/blob/master/src/rum-speedindex.js @@ -21,7 +21,7 @@ function getPaintBlocks(resources: ResourcesTimeMap): Array { for (let i = 0; i < elements.length; i++) { const element = elements[i] let src = '' - if (hasTag(element, 'IMG')) { + if (hasTag(element, 'img')) { src = element.currentSrc || element.src } if (!src) { @@ -110,7 +110,7 @@ export default function (app: App, opts: Partial): void { } app.send( ResourceTiming( - entry.startTime + performance.timing.navigationStart, + entry.startTime + getTimeOrigin(), entry.duration, entry.responseStart && entry.startTime ? entry.responseStart - entry.startTime : 0, entry.transferSize > entry.encodedBodySize ? entry.transferSize - entry.encodedBodySize : 0, @@ -122,9 +122,7 @@ export default function (app: App, opts: Partial): void { ) } - const observer: PerformanceObserver = new PerformanceObserver((list) => - list.getEntries().forEach(resourceTiming), - ) + const observer = new PerformanceObserver((list) => list.getEntries().forEach(resourceTiming)) let prevSessionID: string | undefined app.attachStartCallback(function ({ sessionID }) { @@ -165,6 +163,9 @@ export default function (app: App, opts: Partial): void { if (performance.timing.loadEventEnd || performance.now() > 30000) { pageLoadTimingSent = true const { + // should be ok to use here, (https://github.com/mdn/content/issues/4713) + // since it is compared with the values obtained on the page load (before any possible sleep state) + // deprecated though navigationStart, requestStart, responseStart, diff --git a/tracker/tracker/src/main/modules/viewport.ts b/tracker/tracker/src/main/modules/viewport.ts index 9541f085e..16ed279ab 100644 --- a/tracker/tracker/src/main/modules/viewport.ts +++ b/tracker/tracker/src/main/modules/viewport.ts @@ -5,13 +5,15 @@ import { SetPageLocation, SetViewportSize, SetPageVisibility } from '../app/mess export default function (app: App): void { let url: string, width: number, height: number let navigationStart: number + let referrer = document.referrer const sendSetPageLocation = app.safe(() => { const { URL } = document if (URL !== url) { url = URL - app.send(SetPageLocation(url, document.referrer, navigationStart)) + app.send(SetPageLocation(url, referrer, navigationStart)) navigationStart = 0 + referrer = url } }) diff --git a/tracker/tracker/src/main/tsconfig.json b/tracker/tracker/src/main/tsconfig.json index 14a932c39..5bc2f3a50 100644 --- a/tracker/tracker/src/main/tsconfig.json +++ b/tracker/tracker/src/main/tsconfig.json @@ -1,11 +1,8 @@ { "extends": "../../tsconfig-base.json", "compilerOptions": { - "allowSyntheticDefaultImports": true, - "lib": ["es6", "dom"], + "allowSyntheticDefaultImports": true, + "lib": ["es6", "dom"] }, - "references": [ - { "path": "../common" } - ], - "exclude": ["app/observer"] + "references": [{ "path": "../common" }] } diff --git a/tracker/tracker/src/webworker/BatchWriter.ts b/tracker/tracker/src/webworker/BatchWriter.ts index 47ef6560c..d9275ada3 100644 --- a/tracker/tracker/src/webworker/BatchWriter.ts +++ b/tracker/tracker/src/webworker/BatchWriter.ts @@ -1,6 +1,7 @@ import type Message from '../common/messages.gen.js' import * as Messages from '../common/messages.gen.js' import MessageEncoder from './MessageEncoder.gen.js' +import StringDictionary from './StringDictionary.js' const SIZE_BYTES = 3 const MAX_M_SIZE = (1 << (SIZE_BYTES * 8)) - 1 @@ -9,6 +10,7 @@ export default class BatchWriter { private nextIndex = 0 private beaconSize = 2 * 1e5 // Default 200kB private encoder = new MessageEncoder(this.beaconSize) + private strDict = new StringDictionary() private readonly sizeBuffer = new Uint8Array(SIZE_BYTES) private isEmpty = true @@ -36,7 +38,7 @@ export default class BatchWriter { } private prepare(): void { - if (!this.encoder.isEmpty()) { + if (!this.encoder.isEmpty) { return } @@ -84,6 +86,14 @@ export default class BatchWriter { this.beaconSizeLimit = limit } + private applyDict(str: string): number { + const [key, isNew] = this.strDict.getKey(str) + if (isNew) { + this.writeMessage([Messages.Type.StringDict, key, str]) + } + return key + } + writeMessage(message: Message) { if (message[0] === Messages.Type.Timestamp) { this.timestamp = message[1] // .timestamp @@ -91,22 +101,31 @@ export default class BatchWriter { if (message[0] === Messages.Type.SetPageLocation) { this.url = message[1] // .url } + if (message[0] === Messages.Type.SetNodeAttribute) { + message = [ + Messages.Type.SetNodeAttributeDict, + message[1], + this.applyDict(message[2]), + this.applyDict(message[3]), + ] as Messages.SetNodeAttributeDict + } if (this.writeWithSize(message)) { return } + // buffer overflow, send already written data first then try again this.finaliseBatch() - while (!this.writeWithSize(message)) { - if (this.beaconSize === this.beaconSizeLimit) { - console.warn('OpenReplay: beacon size overflow. Skipping large message.', message, this) - this.encoder.reset() - this.prepare() - return - } - // MBTODO: tempWriter for one message? - this.beaconSize = Math.min(this.beaconSize * 2, this.beaconSizeLimit) - this.encoder = new MessageEncoder(this.beaconSize) - this.prepare() + if (this.writeWithSize(message)) { + return } + // buffer is too small. Create one with maximal capacity + this.encoder = new MessageEncoder(this.beaconSizeLimit) + this.prepare() + if (!this.writeWithSize(message)) { + console.warn('OpenReplay: beacon size overflow. Skipping large message.', message, this) + } + // reset encoder to normal size + this.encoder = new MessageEncoder(this.beaconSize) + this.prepare() } finaliseBatch() { diff --git a/tracker/tracker/src/webworker/MessageEncoder.gen.ts b/tracker/tracker/src/webworker/MessageEncoder.gen.ts index c4e2d70d6..e6e522dd4 100644 --- a/tracker/tracker/src/webworker/MessageEncoder.gen.ts +++ b/tracker/tracker/src/webworker/MessageEncoder.gen.ts @@ -9,243 +9,251 @@ import PrimitiveEncoder from './PrimitiveEncoder.js' export default class MessageEncoder extends PrimitiveEncoder { encode(msg: Message): boolean { switch(msg[0]) { - - case Messages.Type.BatchMetadata: - return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.int(msg[4]) && this.string(msg[5]) - break - - case Messages.Type.PartitionedMessage: - return this.uint(msg[1]) && this.uint(msg[2]) - break - + case Messages.Type.Timestamp: - return this.uint(msg[1]) + return this.uint(msg[1]) break - + case Messages.Type.SetPageLocation: - return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) + return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) break - + case Messages.Type.SetViewportSize: - return this.uint(msg[1]) && this.uint(msg[2]) + return this.uint(msg[1]) && this.uint(msg[2]) break - + case Messages.Type.SetViewportScroll: - return this.int(msg[1]) && this.int(msg[2]) + return this.int(msg[1]) && this.int(msg[2]) break - + case Messages.Type.CreateDocument: return true break - + case Messages.Type.CreateElementNode: - return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.string(msg[4]) && this.boolean(msg[5]) + return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.string(msg[4]) && this.boolean(msg[5]) break - + case Messages.Type.CreateTextNode: - return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) + return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) break - + case Messages.Type.MoveNode: - return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) + return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) break - + case Messages.Type.RemoveNode: - return this.uint(msg[1]) + return this.uint(msg[1]) break - + case Messages.Type.SetNodeAttribute: - return this.uint(msg[1]) && this.string(msg[2]) && this.string(msg[3]) + return this.uint(msg[1]) && this.string(msg[2]) && this.string(msg[3]) break - + case Messages.Type.RemoveNodeAttribute: - return this.uint(msg[1]) && this.string(msg[2]) + return this.uint(msg[1]) && this.string(msg[2]) break - + case Messages.Type.SetNodeData: - return this.uint(msg[1]) && this.string(msg[2]) + return this.uint(msg[1]) && this.string(msg[2]) break - + case Messages.Type.SetNodeScroll: - return this.uint(msg[1]) && this.int(msg[2]) && this.int(msg[3]) + return this.uint(msg[1]) && this.int(msg[2]) && this.int(msg[3]) break - + case Messages.Type.SetInputTarget: - return this.uint(msg[1]) && this.string(msg[2]) + return this.uint(msg[1]) && this.string(msg[2]) break - + case Messages.Type.SetInputValue: - return this.uint(msg[1]) && this.string(msg[2]) && this.int(msg[3]) + return this.uint(msg[1]) && this.string(msg[2]) && this.int(msg[3]) break - + case Messages.Type.SetInputChecked: - return this.uint(msg[1]) && this.boolean(msg[2]) + return this.uint(msg[1]) && this.boolean(msg[2]) break - + case Messages.Type.MouseMove: - return this.uint(msg[1]) && this.uint(msg[2]) + return this.uint(msg[1]) && this.uint(msg[2]) break - + + case Messages.Type.NetworkRequest: + return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) && this.string(msg[5]) && this.uint(msg[6]) && this.uint(msg[7]) && this.uint(msg[8]) + break + case Messages.Type.ConsoleLog: - return this.string(msg[1]) && this.string(msg[2]) + return this.string(msg[1]) && this.string(msg[2]) break - + case Messages.Type.PageLoadTiming: - return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.uint(msg[4]) && this.uint(msg[5]) && this.uint(msg[6]) && this.uint(msg[7]) && this.uint(msg[8]) && this.uint(msg[9]) + return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.uint(msg[4]) && this.uint(msg[5]) && this.uint(msg[6]) && this.uint(msg[7]) && this.uint(msg[8]) && this.uint(msg[9]) break - + case Messages.Type.PageRenderTiming: - return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) + return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) break - - case Messages.Type.JSExceptionDeprecated: - return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) + + case Messages.Type.CustomEvent: + return this.string(msg[1]) && this.string(msg[2]) break - - case Messages.Type.RawCustomEvent: - return this.string(msg[1]) && this.string(msg[2]) - break - + case Messages.Type.UserID: - return this.string(msg[1]) + return this.string(msg[1]) break - + case Messages.Type.UserAnonymousID: - return this.string(msg[1]) + return this.string(msg[1]) break - + case Messages.Type.Metadata: - return this.string(msg[1]) && this.string(msg[2]) + return this.string(msg[1]) && this.string(msg[2]) break - + case Messages.Type.CSSInsertRule: - return this.uint(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) + return this.uint(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) break - + case Messages.Type.CSSDeleteRule: - return this.uint(msg[1]) && this.uint(msg[2]) + return this.uint(msg[1]) && this.uint(msg[2]) break - + case Messages.Type.Fetch: - return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) && this.uint(msg[5]) && this.uint(msg[6]) && this.uint(msg[7]) + return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) && this.uint(msg[5]) && this.uint(msg[6]) && this.uint(msg[7]) break - + case Messages.Type.Profiler: - return this.string(msg[1]) && this.uint(msg[2]) && this.string(msg[3]) && this.string(msg[4]) + return this.string(msg[1]) && this.uint(msg[2]) && this.string(msg[3]) && this.string(msg[4]) break - + case Messages.Type.OTable: - return this.string(msg[1]) && this.string(msg[2]) + return this.string(msg[1]) && this.string(msg[2]) break - + case Messages.Type.StateAction: - return this.string(msg[1]) + return this.string(msg[1]) break - + case Messages.Type.Redux: - return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) + return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) break - + case Messages.Type.Vuex: - return this.string(msg[1]) && this.string(msg[2]) + return this.string(msg[1]) && this.string(msg[2]) break - + case Messages.Type.MobX: - return this.string(msg[1]) && this.string(msg[2]) + return this.string(msg[1]) && this.string(msg[2]) break - + case Messages.Type.NgRx: - return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) + return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) break - + case Messages.Type.GraphQL: - return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) + return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) break - + case Messages.Type.PerformanceTrack: - return this.int(msg[1]) && this.int(msg[2]) && this.uint(msg[3]) && this.uint(msg[4]) + return this.int(msg[1]) && this.int(msg[2]) && this.uint(msg[3]) && this.uint(msg[4]) break - + + case Messages.Type.StringDict: + return this.uint(msg[1]) && this.string(msg[2]) + break + + case Messages.Type.SetNodeAttributeDict: + return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) + break + case Messages.Type.ResourceTiming: - return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.uint(msg[4]) && this.uint(msg[5]) && this.uint(msg[6]) && this.string(msg[7]) && this.string(msg[8]) + return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.uint(msg[4]) && this.uint(msg[5]) && this.uint(msg[6]) && this.string(msg[7]) && this.string(msg[8]) break - + case Messages.Type.ConnectionInformation: - return this.uint(msg[1]) && this.string(msg[2]) + return this.uint(msg[1]) && this.string(msg[2]) break - + case Messages.Type.SetPageVisibility: - return this.boolean(msg[1]) + return this.boolean(msg[1]) break - + case Messages.Type.LoadFontFace: - return this.uint(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) + return this.uint(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) break - + case Messages.Type.SetNodeFocus: - return this.int(msg[1]) + return this.int(msg[1]) break - + case Messages.Type.LongTask: - return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.uint(msg[4]) && this.string(msg[5]) && this.string(msg[6]) && this.string(msg[7]) + return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.uint(msg[4]) && this.string(msg[5]) && this.string(msg[6]) && this.string(msg[7]) break - + case Messages.Type.SetNodeAttributeURLBased: - return this.uint(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) + return this.uint(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) break - + case Messages.Type.SetCSSDataURLBased: - return this.uint(msg[1]) && this.string(msg[2]) && this.string(msg[3]) + return this.uint(msg[1]) && this.string(msg[2]) && this.string(msg[3]) break - + case Messages.Type.TechnicalInfo: - return this.string(msg[1]) && this.string(msg[2]) + return this.string(msg[1]) && this.string(msg[2]) break - + case Messages.Type.CustomIssue: - return this.string(msg[1]) && this.string(msg[2]) + return this.string(msg[1]) && this.string(msg[2]) break - + case Messages.Type.CSSInsertRuleURLBased: - return this.uint(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) && this.string(msg[4]) + return this.uint(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) && this.string(msg[4]) break - + case Messages.Type.MouseClick: - return this.uint(msg[1]) && this.uint(msg[2]) && this.string(msg[3]) && this.string(msg[4]) + return this.uint(msg[1]) && this.uint(msg[2]) && this.string(msg[3]) && this.string(msg[4]) break - + case Messages.Type.CreateIFrameDocument: - return this.uint(msg[1]) && this.uint(msg[2]) + return this.uint(msg[1]) && this.uint(msg[2]) break - + case Messages.Type.AdoptedSSReplaceURLBased: - return this.uint(msg[1]) && this.string(msg[2]) && this.string(msg[3]) + return this.uint(msg[1]) && this.string(msg[2]) && this.string(msg[3]) break - + case Messages.Type.AdoptedSSInsertRuleURLBased: - return this.uint(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) && this.string(msg[4]) + return this.uint(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) && this.string(msg[4]) break - + case Messages.Type.AdoptedSSDeleteRule: - return this.uint(msg[1]) && this.uint(msg[2]) + return this.uint(msg[1]) && this.uint(msg[2]) break - + case Messages.Type.AdoptedSSAddOwner: - return this.uint(msg[1]) && this.uint(msg[2]) + return this.uint(msg[1]) && this.uint(msg[2]) break - + case Messages.Type.AdoptedSSRemoveOwner: - return this.uint(msg[1]) && this.uint(msg[2]) + return this.uint(msg[1]) && this.uint(msg[2]) break - - case Messages.Type.Zustand: - return this.string(msg[1]) && this.string(msg[2]) - break - + case Messages.Type.JSException: - return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) + return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) break - + + case Messages.Type.Zustand: + return this.string(msg[1]) && this.string(msg[2]) + break + + case Messages.Type.BatchMetadata: + return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]) && this.int(msg[4]) && this.string(msg[5]) + break + + case Messages.Type.PartitionedMessage: + return this.uint(msg[1]) && this.uint(msg[2]) + break + } } diff --git a/tracker/tracker/src/webworker/PrimitiveEncoder.ts b/tracker/tracker/src/webworker/PrimitiveEncoder.ts index 34cd3f055..ac3e98a03 100644 --- a/tracker/tracker/src/webworker/PrimitiveEncoder.ts +++ b/tracker/tracker/src/webworker/PrimitiveEncoder.ts @@ -66,7 +66,7 @@ export default class PrimitiveEncoder { checkpoint() { this.checkpointOffset = this.offset } - isEmpty(): boolean { + get isEmpty(): boolean { return this.offset === 0 } skip(n: number): boolean { diff --git a/tracker/tracker/src/webworker/PrimitiveEncoder.unit.test.ts b/tracker/tracker/src/webworker/PrimitiveEncoder.unit.test.ts new file mode 100644 index 000000000..2d4be80b4 --- /dev/null +++ b/tracker/tracker/src/webworker/PrimitiveEncoder.unit.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, test, jest, beforeEach, afterEach } from '@jest/globals' +import PrimitiveEncoder from './PrimitiveEncoder.js' + +describe('PrimitiveEncoder', () => { + test('initial state', () => { + const enc = new PrimitiveEncoder(10) + + expect(enc.getCurrentOffset()).toBe(0) + expect(enc.isEmpty).toBe(true) + expect(enc.flush().length).toBe(0) + }) + + test('skip()', () => { + const enc = new PrimitiveEncoder(10) + enc.skip(5) + expect(enc.getCurrentOffset()).toBe(5) + expect(enc.isEmpty).toBe(false) + }) + + test('checkpoint()', () => { + const enc = new PrimitiveEncoder(10) + enc.skip(5) + enc.checkpoint() + expect(enc.flush().length).toBe(5) + }) + + test('boolean(true)', () => { + const enc = new PrimitiveEncoder(10) + enc.boolean(true) + enc.checkpoint() + const bytes = enc.flush() + expect(bytes.length).toBe(1) + expect(bytes[0]).toBe(1) + }) + test('boolean(false)', () => { + const enc = new PrimitiveEncoder(10) + enc.boolean(false) + enc.checkpoint() + const bytes = enc.flush() + expect(bytes.length).toBe(1) + expect(bytes[0]).toBe(0) + }) + // TODO: test correct enc/dec on a top level(?) with player(PrimitiveReader.ts)/tracker(PrimitiveEncoder.ts) + + test('buffer oveflow with string()', () => { + const N = 10 + const enc = new PrimitiveEncoder(N) + const wasWritten = enc.string('long string'.repeat(N)) + expect(wasWritten).toBe(false) + }) + test('buffer oveflow with uint()', () => { + const enc = new PrimitiveEncoder(1) + const wasWritten = enc.uint(Number.MAX_SAFE_INTEGER) + expect(wasWritten).toBe(false) + }) + test('buffer oveflow with boolean()', () => { + const enc = new PrimitiveEncoder(1) + let wasWritten = enc.boolean(true) + expect(wasWritten).toBe(true) + wasWritten = enc.boolean(true) + expect(wasWritten).toBe(false) + }) +}) diff --git a/tracker/tracker/src/webworker/QueueSender.ts b/tracker/tracker/src/webworker/QueueSender.ts index aa1ff4589..d37d7c313 100644 --- a/tracker/tracker/src/webworker/QueueSender.ts +++ b/tracker/tracker/src/webworker/QueueSender.ts @@ -2,24 +2,6 @@ const INGEST_PATH = '/v1/web/i' const KEEPALIVE_SIZE_LIMIT = 64 << 10 // 64 kB -// function sendXHR(url: string, token: string, batch: Uint8Array): Promise { -// const req = new XMLHttpRequest() -// req.open("POST", url) -// req.setRequestHeader("Authorization", "Bearer " + token) -// return new Promise((res, rej) => { -// req.onreadystatechange = function() { -// if (this.readyState === 4) { -// if (this.status == 0) { -// return; // happens simultaneously with onerror -// } -// res(this) -// } -// } -// req.onerror = rej -// req.send(batch.buffer) -// }) -// } - export default class QueueSender { private attemptsCount = 0 private busy = false @@ -38,6 +20,10 @@ export default class QueueSender { authorise(token: string): void { this.token = token + if (!this.busy) { + // TODO: transparent busy/send logic + this.sendNext() + } } push(batch: Uint8Array): void { @@ -48,9 +34,19 @@ export default class QueueSender { } } + private sendNext() { + const nextBatch = this.queue.shift() + if (nextBatch) { + this.sendBatch(nextBatch) + } else { + this.busy = false + } + } + private retry(batch: Uint8Array): void { if (this.attemptsCount >= this.MAX_ATTEMPTS_COUNT) { this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`) + // remains this.busy === true return } this.attemptsCount++ @@ -61,6 +57,7 @@ export default class QueueSender { private sendBatch(batch: Uint8Array): void { this.busy = true + // @ts-ignore fetch(this.ingestURL, { body: batch, method: 'POST', @@ -70,7 +67,7 @@ export default class QueueSender { }, keepalive: batch.length < KEEPALIVE_SIZE_LIMIT, }) - .then((r) => { + .then((r: Record) => { if (r.status === 401) { // TODO: continuous session ? this.busy = false @@ -83,14 +80,9 @@ export default class QueueSender { // Success this.attemptsCount = 0 - const nextBatch = this.queue.shift() - if (nextBatch) { - this.sendBatch(nextBatch) - } else { - this.busy = false - } + this.sendNext() }) - .catch((e) => { + .catch((e: any) => { console.warn('OpenReplay:', e) this.retry(batch) }) @@ -98,5 +90,6 @@ export default class QueueSender { clean() { this.queue.length = 0 + this.token = null } } diff --git a/tracker/tracker/src/webworker/QueueSender.unit.test.ts b/tracker/tracker/src/webworker/QueueSender.unit.test.ts new file mode 100644 index 000000000..fc89a60a4 --- /dev/null +++ b/tracker/tracker/src/webworker/QueueSender.unit.test.ts @@ -0,0 +1,95 @@ +import { describe, expect, test, jest, beforeEach, afterEach } from '@jest/globals' +import QueueSender from './QueueSender.js' + +global.fetch = () => Promise.resolve(new Response()) // jsdom does not have it + +function mockFetch(status: number) { + return jest + .spyOn(global, 'fetch') + .mockImplementation(() => Promise.resolve({ status } as Response)) +} +const baseURL = 'MYBASEURL' +const sampleArray = new Uint8Array(1) +const randomToken = 'abc' +function defaultQueueSender({ + url = baseURL, + onUnauthorised = () => {}, + onFailed = () => {}, +} = {}) { + return new QueueSender(baseURL, onUnauthorised, onFailed) +} + +describe('QueueSender', () => { + afterEach(() => { + jest.restoreAllMocks() + }) + + // Test fetch first parameter + authorization header to be present + + // authorise() / push() + test('Does not call fetch if not authorised', () => { + const queueSender = defaultQueueSender() + const fetchMock = mockFetch(200) + + queueSender.push(sampleArray) + expect(fetchMock).not.toBeCalled() + }) + test('Calls fetch on push() if authorised', () => { + const queueSender = defaultQueueSender() + const fetchMock = mockFetch(200) + + queueSender.authorise(randomToken) + expect(fetchMock).toBeCalledTimes(0) + queueSender.push(sampleArray) + expect(fetchMock).toBeCalledTimes(1) + }) + test('Calls fetch on authorisation if there was a push() call before', () => { + const queueSender = defaultQueueSender() + const fetchMock = mockFetch(200) + + queueSender.push(sampleArray) + queueSender.authorise(randomToken) + expect(fetchMock).toBeCalledTimes(1) + }) + + // .clean() + test("Doesn't call fetch on push() after clean()", () => { + const queueSender = defaultQueueSender() + const fetchMock = mockFetch(200) + + queueSender.authorise(randomToken) + queueSender.clean() + queueSender.push(sampleArray) + expect(fetchMock).not.toBeCalled() + }) + test("Doesn't call fetch on authorisation if there was push() & clean() calls before", () => { + const queueSender = defaultQueueSender() + const fetchMock = mockFetch(200) + + queueSender.push(sampleArray) + queueSender.clean() + queueSender.authorise(randomToken) + expect(fetchMock).not.toBeCalled() + }) + + //Test N sequential ToBeCalledTimes(N) + //Test N sequential pushes with different timeouts to be sequential + + // onUnauthorised + test('Calls onUnauthorized callback on 401', (done) => { + const onUnauthorised = jest.fn() + const queueSender = defaultQueueSender({ + onUnauthorised, + }) + const fetchMock = mockFetch(401) + queueSender.authorise(randomToken) + queueSender.push(sampleArray) + setTimeout(() => { + // how to make test simpler and more explicit? + expect(onUnauthorised).toBeCalled() + done() + }, 100) + }) + //Test onFailure + //Test attempts timeout/ attempts count (toBeCalledTimes on one batch) +}) diff --git a/tracker/tracker/src/webworker/StringDictionary.ts b/tracker/tracker/src/webworker/StringDictionary.ts new file mode 100644 index 000000000..b183ce862 --- /dev/null +++ b/tracker/tracker/src/webworker/StringDictionary.ts @@ -0,0 +1,13 @@ +export default class StringDictionary { + private idx = 1 + private backDict: Record = {} + + getKey(str: string): [number, boolean] { + let isNew = false + if (!this.backDict[str]) { + isNew = true + this.backDict[str] = this.idx++ + } + return [this.backDict[str], isNew] + } +} diff --git a/tracker/tracker/src/webworker/StringDictionary.unit.test.ts b/tracker/tracker/src/webworker/StringDictionary.unit.test.ts new file mode 100644 index 000000000..8bf17e14b --- /dev/null +++ b/tracker/tracker/src/webworker/StringDictionary.unit.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, test, jest, beforeEach, afterEach } from '@jest/globals' +import StringDictionary from './StringDictionary.js' + +describe('StringDictionary', () => { + test('key is non-zero', () => { + const dict = new StringDictionary() + + const [key, isNew] = dict.getKey('We are Asayer') + + expect(key).not.toBe(0) + expect(isNew).toBe(true) + }) + + test('Different strings have different keys', () => { + const dict = new StringDictionary() + + const [key1, isNew1] = dict.getKey('Datadog') + const [key2, isNew2] = dict.getKey('PostHog') + + expect(key1).not.toBe(key2) + expect(isNew2).toBe(true) + }) + + test('Similar strings have similar keys', () => { + const dict = new StringDictionary() + + const [key1, isNew1] = dict.getKey("What's up?") + const [key2, isNew2] = dict.getKey("What's up?") + + expect(key1).toBe(key2) + expect(isNew2).toBe(false) + }) +}) diff --git a/tracker/tracker/src/webworker/index.ts b/tracker/tracker/src/webworker/index.ts index 6cddb8a6b..656796b98 100644 --- a/tracker/tracker/src/webworker/index.ts +++ b/tracker/tracker/src/webworker/index.ts @@ -1,3 +1,8 @@ +// Do strong type WebWorker as soon as it is possible: +// https://github.com/microsoft/TypeScript/issues/14877 +// At the moment "webworker" lib conflicts with jest-environment-jsdom that uses "dom" lib +// + import type Message from '../common/messages.gen.js' import { Type as MType } from '../common/messages.gen.js' import { ToWorkerData, FromWorkerData } from '../common/interaction.js' @@ -19,7 +24,7 @@ const AUTO_SEND_INTERVAL = 10 * 1000 let sender: QueueSender | null = null let writer: BatchWriter | null = null let workerStatus: WorkerStatus = WorkerStatus.NotActive - +let afterSleepRestarts = 0 function finalize(): void { if (!writer) { return @@ -64,7 +69,8 @@ function initiateFailure(reason: string): void { let sendIntervalID: ReturnType | null = null let restartTimeoutID: ReturnType -self.onmessage = ({ data }: MessageEvent): any => { +// @ts-ignore +self.onmessage = ({ data }: any): any => { if (data == null) { finalize() return @@ -76,22 +82,27 @@ self.onmessage = ({ data }: MessageEvent): any => { } if (Array.isArray(data)) { - // Message[] - if (!writer) { - throw new Error('WebWorker: writer not initialised. Service Should be Started.') - } - const w = writer - data.forEach((message) => { - if (message[0] === MType.SetPageVisibility) { - if (message[1]) { - // .hidden - restartTimeoutID = setTimeout(() => initiateRestart(), 30 * 60 * 1000) - } else { - clearTimeout(restartTimeoutID) + if (writer !== null) { + const w = writer + data.forEach((message) => { + if (message[0] === MType.SetPageVisibility) { + if (message[1]) { + // .hidden + restartTimeoutID = setTimeout(() => initiateRestart(), 30 * 60 * 1000) + } else { + clearTimeout(restartTimeoutID) + } } + w.writeMessage(message) + }) + } + if (!writer) { + postMessage('not_init') + if (afterSleepRestarts === 0) { + afterSleepRestarts += 1 + initiateRestart() } - w.writeMessage(message) - }) + } return } diff --git a/tracker/tracker/src/webworker/tsconfig.json b/tracker/tracker/src/webworker/tsconfig.json index 52228f9af..5506aa82b 100644 --- a/tracker/tracker/src/webworker/tsconfig.json +++ b/tracker/tracker/src/webworker/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig-base.json", "compilerOptions": { - "lib": ["es6", "webworker"] + "lib": ["es6"] }, "references": [{ "path": "../common" }] } diff --git a/tracker/tracker/tsconfig-base.json b/tracker/tracker/tsconfig-base.json index 9af9edb73..fa70026d2 100644 --- a/tracker/tracker/tsconfig-base.json +++ b/tracker/tracker/tsconfig-base.json @@ -10,5 +10,6 @@ "target": "es6", "module": "es6", "moduleResolution": "nodenext" - } + }, + "exclude": ["**/*.test.ts"] } diff --git a/utilities/Dockerfile b/utilities/Dockerfile index 08ccba56f..edbaae03c 100644 --- a/utilities/Dockerfile +++ b/utilities/Dockerfile @@ -1,6 +1,6 @@ FROM node:18-alpine LABEL Maintainer="KRAIEM Taha Yassine" -RUN apk add --no-cache tini git libc6-compat && ln -s /lib/libc.musl-x86_64.so.1 /lib/ld-linux-x86-64.so.2 +RUN apk add --no-cache tini ARG envarg ENV ENTERPRISE_BUILD=${envarg} \ diff --git a/utilities/build.sh b/utilities/build.sh index 98f244749..87ff7f3e6 100644 --- a/utilities/build.sh +++ b/utilities/build.sh @@ -6,7 +6,8 @@ # Usage: IMAGE_TAG=latest DOCKER_REPO=myDockerHubID bash build.sh -git_sha1=${IMAGE_TAG:-$(git rev-parse HEAD)} +git_sha=$(git rev-parse --short HEAD) +image_tag=${IMAGE_TAG:-git_sha} check_prereq() { which docker || { echo "Docker not installed, please install docker." @@ -26,15 +27,18 @@ function build_api(){ [[ $1 == "ee" ]] && { cp -rf ../ee/utilities/* ./ } - docker build -f ./Dockerfile -t ${DOCKER_REPO:-'local'}/assist:${git_sha1} . + docker build -f ./Dockerfile --build-arg GIT_SHA=$git_sha -t ${DOCKER_REPO:-'local'}/assist:${image_tag} . cd ../utilities rm -rf ../${destination} [[ $PUSH_IMAGE -eq 1 ]] && { - docker push ${DOCKER_REPO:-'local'}/assist:${git_sha1} - docker tag ${DOCKER_REPO:-'local'}/assist:${git_sha1} ${DOCKER_REPO:-'local'}/assist:latest + docker push ${DOCKER_REPO:-'local'}/assist:${image_tag} + docker tag ${DOCKER_REPO:-'local'}/assist:${image_tag} ${DOCKER_REPO:-'local'}/assist:latest docker push ${DOCKER_REPO:-'local'}/assist:latest } + [[ $SIGN_IMAGE -eq 1 ]] && { + cosign sign --key $SIGN_KEY ${DOCKER_REPO:-'local'}/assist:${image_tag} + } echo "build completed for assist" } diff --git a/utilities/package-lock.json b/utilities/package-lock.json index 9f01499cb..aba9e43fe 100644 --- a/utilities/package-lock.json +++ b/utilities/package-lock.json @@ -1,19 +1,19 @@ { - "name": "utilities-server", + "name": "assist-server", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "utilities-server", + "name": "assist-server", "version": "1.0.0", "license": "Elastic License 2.0 (ELv2)", "dependencies": { - "@maxmind/geoip2-node": "^3.4.0", - "express": "^4.18.1", - "jsonwebtoken": "^8.5.1", - "socket.io": "^4.5.1", - "ua-parser-js": "^1.0.2" + "@maxmind/geoip2-node": "^3.5.0", + "express": "^4.18.2", + "jsonwebtoken": "^9.0.0", + "socket.io": "^4.6.0", + "ua-parser-js": "^1.0.33" } }, "node_modules/@maxmind/geoip2-node": { @@ -37,14 +37,17 @@ "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" }, "node_modules/@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dependencies": { + "@types/node": "*" + } }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" + "version": "18.14.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.1.tgz", + "integrity": "sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ==" }, "node_modules/accepts": { "version": "1.3.8", @@ -167,9 +170,9 @@ } }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { "node": ">= 0.6" } @@ -251,9 +254,9 @@ } }, "node_modules/engine.io": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz", - "integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz", + "integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==", "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", @@ -264,16 +267,16 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.0.3", - "ws": "~8.2.3" + "ws": "~8.11.0" }, "engines": { "node": ">=10.0.0" } }, "node_modules/engine.io-parser": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", - "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", "engines": { "node": ">=10.0.0" } @@ -408,9 +411,9 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -496,24 +499,18 @@ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "dependencies": { "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" + "node": ">=12", + "npm": ">=6" } }, "node_modules/jsonwebtoken/node_modules/ms": { @@ -554,40 +551,21 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + "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.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + "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==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } }, "node_modules/map-obj": { "version": "4.3.0", @@ -695,9 +673,9 @@ } }, "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "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" } @@ -810,11 +788,17 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/send": { @@ -878,15 +862,15 @@ } }, "node_modules/socket.io": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.4.tgz", - "integrity": "sha512-m3GC94iK9MfIEeIBfbhJs5BqFibMtkRk8ZpKwG2QwxV0m/eEhPIV4ara6XCF1LWNAus7z58RodiZlAH71U3EhQ==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", + "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "debug": "~4.3.2", - "engine.io": "~6.2.1", - "socket.io-adapter": "~2.4.0", + "engine.io": "~6.4.1", + "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.1" }, "engines": { @@ -894,14 +878,17 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", - "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dependencies": { + "ws": "~8.11.0" + } }, "node_modules/socket.io-parser": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz", - "integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", + "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -1000,9 +987,9 @@ } }, "node_modules/ua-parser-js": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", - "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", "funding": [ { "type": "opencollective", @@ -1055,9 +1042,9 @@ } }, "node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", "engines": { "node": ">=10.0.0" }, @@ -1073,6 +1060,11 @@ "optional": true } } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/utilities/package.json b/utilities/package.json index f798e73dd..b06c8cae5 100644 --- a/utilities/package.json +++ b/utilities/package.json @@ -1,5 +1,5 @@ { - "name": "utilities-server", + "name": "assist-server", "version": "1.0.0", "description": "assist server to get live sessions & sourcemaps reader to get stack trace", "main": "peerjs-server.js", @@ -18,10 +18,10 @@ }, "homepage": "https://github.com/openreplay/openreplay#readme", "dependencies": { - "@maxmind/geoip2-node": "^3.4.0", - "express": "^4.18.1", - "jsonwebtoken": "^8.5.1", - "socket.io": "^4.5.1", - "ua-parser-js": "^1.0.2" + "@maxmind/geoip2-node": "^3.5.0", + "express": "^4.18.2", + "jsonwebtoken": "^9.0.0", + "socket.io": "^4.6.0", + "ua-parser-js": "^1.0.33" } } diff --git a/utilities/servers/websocket.js b/utilities/servers/websocket.js index 615390996..f5d029bc2 100644 --- a/utilities/servers/websocket.js +++ b/utilities/servers/websocket.js @@ -268,7 +268,7 @@ module.exports = { debug && console.log(`notifying new agent about no SESSIONS`); io.to(socket.id).emit(EVENTS_DEFINITION.emit.NO_SESSIONS); } - socket.join(socket.peerId); + await socket.join(socket.peerId); if (io.sockets.adapter.rooms.get(socket.peerId)) { debug && console.log(`${socket.id} joined room:${socket.peerId}, as:${socket.identity}, members:${io.sockets.adapter.rooms.get(socket.peerId).size}`); }